<?php

namespace WpieFw\Helpers;

use WpieFw\Helpers\WpieFormHelper;
use WpieFw\Templates\WpieTemplate;
use WpieFw\PostTypes\WpiePostType;
use WpieFw\Helpers\WpieMiscHelper;
use WpieFw\Templates\Files\WpieTemplatesFileFinder;
use WpieFw\Exceptions\WpieExceptionInterface;
use WpieFw\Exceptions\WpieExceptionLogger;
use WpieFw\Exceptions\WpieInvalidArgumentException;
use WpieFw\Exceptions\WpieUnexpectedValueException;

if( ! defined( 'ABSPATH' ) ) exit;

/**
 * WpieWpFormHelper Class
 *
 * Helper class for rendering WordPress specific formfields
 *
 * @author $Author: Vincent Weber <weepie-plugins@outlook.com> $
 * @since 0.1
 */
final class WpieWpFormHelper extends WpieFormHelper
{
	/**
	 * Placeholder for filename value
	 *
	 * @since 1.0
	 *
	 * @var string
	 */
	const VALUE_MASK_FILENAME = '{filename}';

	/**
	 * Template file name for one Custom Post Type list item
	 *
	 * @since 1.2.1
	 *
	 * @var string
	 */
	const TEMPL_FILE_NAME_LIST_ITEMS = 'wpie-tpl-list-items.php';

	/**
	 * Template file name for one list item
	 *
	 * @since 1.2.1
	 *
	 * @var string
	 */
	const TEMPL_FILE_NAME_LIST_ITEM = 'wpie-tpl-list-item.php';

	/**
	 * file name that is used by the file/image upload fields
	 *
	 * @since 1.0
	 *
	 * @var string
	 */
	private static $filename = '';

	/**
	 * the file extension based on the filename
	 *
	 * @since 1.2
	 *
	 * @var string
	 */
	private static $fileExt = '';

	/**
	 * Element types that dont need the 'name' and 'value' attribute
	 *
	 * @since 1.0
	 *
	 * @var array
	 */
	protected static $attributesEscapeNameValue = [ 'wptermlist_edit', 'wpcpostlist_edit' ];

	/**
	 * Element types that don't need tabindex attribute
	 *
	 * @since 1.1.5
	 *
	 * @var array
	 */
	protected static $tabindexEscapeFields = [];

	/* (non-PHPdoc)
	 * @see FormHelper::formField()
	 *
	 * @since 0.1
	 */
	public static function formField( $type = '', $name = '', $value = '', $nameSpace = '', $attributes = [], $innerHtml = '', $options = [], $render = true )
	{
		parent::$attributesEscapeNameValue = array_unique(array_merge(parent::$attributesEscapeNameValue, self::$attributesEscapeNameValue));
		parent::formField( $type, $name, $value, $nameSpace, $attributes, $innerHtml, $options, false );
		self::_prepareAttributes();

		if( 'wptextareabasic' === parent::$type ) {
			parent::$hasInnerHtml = true;
			parent::$innerHtml = parent::$value;
		}

		if( 'wpcpostlist' === parent::$type || 'wpcpostlist_edit' === parent::$type ) {
			if( 0 < (int)parent::$value ) {
				parent::$value = (int)parent::$value;
			}
		}

		if( ( 'wpfile' === parent::$type || 'wpfilebasic' === parent::$type || 'wpimage' === parent::$type ) && '' !== parent::$value ) {
			self::$filename = esc_html( wp_basename( parent::$value ) );
			self::$fileExt	= WpieMiscHelper::getFileExt( self::$filename );
		}

		return self::_render();
	}

	/**
	 * Get a taxonomy selectbox with terms
	 *
	 * See the documentation for {@link wp_dropdown_categories()}
	 *
	 * @param string $id
	 * @param string $name
	 * @param string $orderby
	 * @param (bool|int) $echo
	 * @param int $selected
	 * @param string $tax
	 * @param array $args
	 *
	 * @uses wp_parse_args()
	 * @uses wp_dropdown_categories()
	 *
	 * @since 0.1
	 *
	 * @return void|string
	 */
	public static function getTermDropdown( $id, $name, $orderby='ID', $echo=1, $selected=0, $tax='category', $args=[] )
	{
		$defaults = [
			'orderby'            => $orderby,
			'echo'               => $echo,
			'selected'           => $selected,
			'name'               => $name,
			'id'                 => $id,
			'taxonomy'           => $tax,
			'hide_empty'		 => 0
		];

		if( !empty( $args ) ) {
			$args = wp_parse_args( $args, $defaults );
		} else {
			$args = $defaults;
		}

		if( $echo ) {
			wp_dropdown_categories( $args );
		} else {
			return wp_dropdown_categories( $args );
		}
	}

	/**
	 * Get a HTML list with taxonomy terms
	 *
	 * @param string $tax, the taxonomy to retrieve the terms for
	 * @param string $orderby
	 * @param array $args optional arguments to pass to get_terms()
	 *
	 * @uses wp_parse_args()
	 * @uses get_terms()
	 * @uses WpieWpFormHelper::_getTermListRow()
	 *
	 * @since 1.0
	 *
	 * @return string
	 */
	public static function getTermList( $tax='category', $orderby='ID', $args=[] )
	{
		$defaults = [
			'orderby'   => $orderby,
			'taxonomy'  => $tax,
			'get'		=> 'all'
		];

		if( !empty( $args ) ) {
			$args = wp_parse_args($args, $defaults);
		} else {
			$args = $defaults;
		}

		$terms = get_terms( $tax, $args );

		if( empty($terms) || is_wp_error($terms) ) {
			return sprintf( 'No term list possible for taxonomy "%s".', $tax );
		}

		$list = '';

		$list .= '<ul '.parent::ATTR_MASK.'>';
		foreach( $terms as $term ) {
			$list .= sprintf(self::_getTermListRow(), $term->term_id, $term->term_id, $term->name, $term->name);
		}
		$list .= '</ul>';

		return $list;
	}

	/**
	 * Get a editable list with custom post type items
	 *
	 * @access	public
	 *
	 * @param string	$postType		the custom post type
	 * @param string	$layout 		the type of layout
	 * @param string	$outerW 		the outer width (%)
	 * @param string	$context 		the unique context, like products, cookies, bullets etc.
	 * @param bool		$canAdd			flag if user can add rows to the list
	 * @param bool		$canDel			flag if user can delete rows from the list
	 * @param bool		$canSave		flag if user can update rows from the list
	 * @param bool		$clickSelect	flag if items should implement click select
	 * @param bool		$hasTitle		flag if the list rows should have the post title field
	 * @param bool		$hasMedia		flag if media JS should be enqueued
	 * @param bool		$groupMeta		flag if meta data should be grouped and saved as one entry
	 * @param string	$groupMetaKey	the wp_postmeta "meta_key" column
	 * @param bool		$hasHeading		flag if has headings
	 *
	 * @since	1.2.1
	 *
	 * @return 	string|void
	 */
	public static function getCustomPostTypeList( $postType = 'post', $layout = 'normal', $outerW = '20', $context = '', $canAdd = true, $canDel = true, $canSave = true, $clickSelect = false, $hasTitle = false, $hasMedia = false, $groupMeta = false, $groupMetaKey = '', $hasHeading = false )
	{
		// all post (objects)
		$havePosts = false;
		$headingsAr = [];
		$htmlListItems = '';

		try {
			if( !post_type_exists( $postType ) ) {
				throw new WpieInvalidArgumentException( 'Invalid Post Type "'.$postType.'" for custom post type list' );
			}

			$postTypeObj = new WpiePostType( $postType );

			/**
			 * Let others filter the WP_Query args array to retieve the custom post type posts
			 *
			 * @param array $args
			 *
			 * @since 2.1.2
			 *
			 * @return array
			 */
			$args = apply_filters( 'wpie_wp_query_args_' . $context,  [ 'orderby' => 'ID' ] );

			// query for the posts
			$posts = $postTypeObj->retrieveCustom( $args );

			// flag if posts are available
			if( ! empty( $posts ) ) {
				$havePosts = true;
			}

			// if have posts and $hasMedia is true, enqeue/print the necessary scripts
			if( $havePosts && $hasMedia ) {
				self::enqueueMediaScripts();
				self::printInputImageJs();
			}

			/**
			 * @todo description for hook wpie_before_list_custom_posts_{$context}
			 * @todo description for hook wpie_after_list_custom_posts_{$context}
			 */
			$hook_before_list_posts = apply_filters( 'wpie_before_list_custom_posts_' . $context , '', $postType );
			$hook_after_list_posts = apply_filters( 'wpie_after_list_custom_posts_' . $context , '', $postType );

			// alternative method to go 2 dirs back
			// $wfPath = dirname(dirname(dirname( __FILE__ )));
			$wfPath = dirname( __FILE__ ) . "/../../.";

			$finder = new WpieTemplatesFileFinder( $wfPath . '/templates', '', '', self::TEMPL_FILE_NAME_LIST_ITEMS );
			$templ = new WpieTemplate( $finder, self::TEMPL_FILE_NAME_LIST_ITEMS );

			$selected = ( $clickSelect ) ? parent::$value : 0;

			// Populate the list items HTML
			if( $havePosts ) {
				$finder = new WpieTemplatesFileFinder( $wfPath . '/templates', '', '', self::TEMPL_FILE_NAME_LIST_ITEM );
				foreach( $posts as $postId => $post ) {
					$templListItem = new WpieTemplate( $finder, self::TEMPL_FILE_NAME_LIST_ITEM );
					$templListItem->setVars(
							[
								'post_id' => $postId,
								'post_type' => $postType,
								'title' => $post->post_title,
								'can_del' => $canDel,
								'can_save' => $canSave,
								'has_title' => $hasTitle,
								'context' => $context,
								'layout' => $layout,
								'group_meta_key' => $groupMetaKey,
								'click_select' => $clickSelect,
								'selected' => $selected
					 		] );
					$htmlListItems .= $templListItem->render( false, true );
				}
			}

			// get heading items
			if( $hasHeading ) {
				/**
				 * Let others add headings to the list
				 *
				 * @param array $headingsAr
				 *
				 * @since 2.1.2
				 *
				 * @return array
				 */
				$headingsAr = apply_filters( 'wpie_list_headings_' . $context , [] );
				if( 0 < count( $headingsAr ) && ( $canDel || $canSave ) ) {
					$headingsAr[] = '';
				} else {
					$hasHeading = false;
				}
			}

			$templ->setVar( 'selected', $selected );
			$templ->setVar( 'layout', $layout );
			$templ->setVar( 'outer_w', $outerW );
			$templ->setVar( 'context', $context );
			$templ->setVar( 'post_type', $postType );
			$templ->setVar( 'have_posts', $havePosts );
			$templ->setVar( 'can_add', $canAdd );
			$templ->setVar( 'can_del', $canDel );
			$templ->setVar( 'can_save', $canSave );
			$templ->setVar( 'click_select', $clickSelect );
			$templ->setVar( 'has_title', $hasTitle );
			$templ->setVar( 'has_media', $hasMedia );
			$templ->setVar( 'group_meta', $groupMeta );
			$templ->setVar( 'group_meta_key', $groupMetaKey );
		    $templ->setVar( 'html_list_items', $htmlListItems );
			$templ->setVar( 'hook_before_list_posts', $hook_before_list_posts );
			$templ->setVar( 'hook_after_list_posts', $hook_after_list_posts );
			$templ->setVar( 'has_heading', $hasHeading );
			$templ->setVar( 'headings', $headingsAr );

			return $templ->render( false, true );
		} catch( WpieExceptionInterface $e ) {
			WpieExceptionLogger::log( $e );
			return $e->getMessage();
		}
	}

	/**
	 * Get a selectbox with Post Types
	 *
	 * @uses get_post_types()
	 *
	 * @since 1.0
	 * @since 1.4.7 renamed to getPostTypeTypesDropDown()
	 *
	 * @return string
	 */
	public static function getPostTypeTypesDropDown()
	{
		$args = [ 'public' => true ];

		parent::$options = get_post_types( $args );

		$select = '<select '.parent::ATTR_MASK.'>'.parent::SELECT_OPTIONS_MASK.'</select>';

		return $select;
	}

	/**
	 * Get the HTML for a selectbox with pages
	 *
	 * @param string $id
	 * @param string $name
	 * @param string $sortColumn
	 * @param (bool|int) $echo
	 * @param int $selected
	 *
	 * @uses wp_dropdown_pages()
	 *
	 * @since 0.1
	 *
	 * @return void|string
	 */
	public static function getPageDropdown( $id, $name, $sortColumn='ID', $echo=1, $selected=0  )
	{
		$args = [
			'id' => $id,
			'name' => $name,
			'sort_column' => $sortColumn,
			'echo' => $echo,
			'show_option_no_change' => '-- '. __( 'Select', 'weepie' ) .' --',
			'selected' => $selected
		];

		if( $echo ) {
			wp_dropdown_pages( $args );
		} else {
			return wp_dropdown_pages( $args );
		}
	}

	/**
	 * Get the HTML for a selectbox with pages
	 *
	 * @param string $id
	 * @param string $name
	 * @param string $sortColumn
	 * @param (bool|int) $echo
	 * @param int $selected
	 *
	 * @uses wp_dropdown_pages()
	 *
	 * @since 1.4.7
	 *
	 * @return void|string
	 */
	public static function getCustomPostTypeDropdown( $id, $name, $selected=0, $echo=0, $postType='post' )
	{
		// all post (objects)
		$select = '';
		$options = [];

		try {
			if( !post_type_exists( $postType ) ) {
				throw new WpieInvalidArgumentException( 'Invalid Post Type "'.$postType.'" for custom post type dropdown' );
			}

			/**
			 * Let others filter the WP_Query args array to retieve the custom post type posts
			 *
			 * @param array $args
			 *
			 * @since 1.4.7
			 *
			 * @return array
			 */
			$args = apply_filters( 'wpie_wp_query_args_post_type_dropdown',  [
					'orderby' => 'ID',
					'order' => 'DESC',
					'post_type' => $postType,
					'post_status' => 'publish' ] );

			if( !isset( $args['post_type'] ) ) {
				throw new WpieUnexpectedValueException( sprintf( 'Post Type not set for custom post type dropdown', $postType ) );
			}

			$postType = $args['post_type'];
			$postTypeObj = new WpiePostType( $postType );

			// query for the posts
			$posts = $postTypeObj->retrieveCustom( $args, false, false );

			// flag if posts are available
			if( ! empty( $posts ) ) {
				$options[-1] = __( '-- Select --', 'weepie' );
				foreach( $posts as $postId => $post ) {
					$isPdf = ( wp_attachment_is( 'pdf', $postId ) );
					if( 'attachment' === $postType && !$isPdf ) {
						continue;
					}
					if( 'attachment' === $postType && $isPdf ) {
						$title = basename( get_post_meta( $postId, '_wp_attached_file', true ) );
					} else {
						$title = $post->post_title;
					}
					$options[$postId] = $title;
				}
				parent::$options = $options;
				$select = '<select '.parent::ATTR_MASK.'>'.parent::SELECT_OPTIONS_MASK.'</select>';
			}

			if( $echo ) {
				echo $select;
			} else {
				return $select;
			}
		} catch( WpieExceptionInterface $e ) {
			WpieExceptionLogger::log( $e );

			if( $echo ) {
				echo $e->getMessage();
			} else {
				return $e->getMessage();
			}
		}
	}

	/**
	 * Enqueue WordPress media scripts
	 *
	 * @access public
	 *
	 * @uses wp_enqueue_media()
	 *
	 * @since 1.1.5
	 */
	public static function enqueueMediaScripts()
	{
		static $isEnqueued = false;

		if( !$isEnqueued ) {

			wp_enqueue_media();
			$isEnqueued = true;
		}
	}

	/**
	 * Print JavaScript that fixes WP editors not being updated
	 *
	 * @see https://github.com/webRtistik/weepie-framework/issues/41
	 *
	 * @since 2.0
	 */
	public static function printWpEditorAjaxSaveFix()
	{
		static $isPrinted = false;

		if( !$isPrinted ):
		?>
		<script type='text/javascript'>
		jQuery(function($) {
			$('#submit').on('mousedown', function() {
				try {
				    if (window.tinyMCE) {
				        window.tinyMCE.triggerSave();
				      }
				} catch(exc) {
					WPIE.log(exc.message);
				}
			});
		});
		</script>
		<?php
		$isPrinted = true;
		endif;
	}

	/**
	 * Callback for the admin_print_footer_scripts hook
	 *
	 * Print jQuery JavaScript that is needed for an image/file upload field
	 *
	 * The code can handle multiple uploader instances at one page
	 *
	 * To force extensions, add the HTML data attribute 'data-force-ext' with comma separated extensions that are allowed, simplified example: <input type="file" data-force-ext="png,jpg">
	 *
	 * @since 1.0
	 */
	public static function printInputImageJs()
	{
		static $isPrinted = false;

		if( !$isPrinted ):
		?>
		<script type='text/javascript'>
		jQuery(function($) {

		// prevent the media popup triggering when hitting 'Enter' key
		$('.wrap').on('keypress', function(e) {
			if (13 === (e.keyCode || e.which) && !$(e.target).hasClass('wp_upload_button') ) return false;
		});

		// code from http://www.webmaster-source.com/2013/02/06/using-the-wordpress-3-5-media-uploader-in-your-plugin-or-theme/
		// Slightly modified
		var custom_uploader;

  	$('.wrap').on('click', '.wp_upload_button', function(e) {
			e.preventDefault();

		  var thiz = $(this),
	    	  type = thiz.data('button-type'),
	    	  input = thiz.siblings('input.wp'+type),
	    	  filenameEl = thiz.siblings('.filename'),
	    	  previewEl = thiz.siblings('.preview'),
	    	  haveForcedExt = false,
	    	  haveForcedDim = false,
	    	  showPreview = false;

			// get forced extensions
			if(input.data('force-ext')) {
				var extstr = input.data('force-ext'),
					forcedext = extstr.split(','),
					haveForcedExt = true;
			}
			// get forced dimensions
			if(input.data('force-dim')) {
				var extstr = input.data('force-dim'),
					forceddimAr = extstr.split('x'),
					forcedDimW = forceddimAr[0],
					forcedDimH = forceddimAr[1],
					haveForcedDim = true;
			}

			if(input.data('show-preview')) {
				showPreview = (true == input.data('show-preview')) ? true : false;

				var previewMaxWh = parseInt(input.data('preview-max-wh'));
				previewMaxWh = (!isNaN( previewMaxWh )) ? previewMaxWh : false;
			}

      // If the uploader object has already been created, reopen the dialog
      custom_uploader = ( 'undefined' !== typeof thiz.data('custom_uploader')) ? thiz.data('custom_uploader') : false;

      if ( custom_uploader ) {
      	custom_uploader.open();
       	return;
      }

      // Extend the wp.media object
      custom_uploader = wp.media.frames.file_frame = wp.media({
          title: 'Choose '+type,
	      button: {
	      	text: 'Choose '+type
	      },
	      multiple: false
	    });
      thiz.data('custom_uploader', custom_uploader);

      // When a file is selected, grab the URL and set it as the text field's value
      custom_uploader.on('select', function() {

 	    	var attachment = custom_uploader.state().get('selection').first().toJSON();
	        // filename
			var name = attachment.url.split('/').pop(),
				relUrl = attachment.url.split('/uploads/').pop();

			input.data('attachment-id', attachment.id);
			input.attr('data-attachment-id', attachment.id);

          if(haveForcedExt) {
          	// get ext from url
			var ext = attachment.url.split('.').pop();

			if(ext && -1 !== forcedext.indexOf(ext)) {
					input.val(relUrl);
					filenameEl.html(name);
				} else {
					alert('Only files with "'+extstr+'" extension are allowed');
				}
          } else if(haveForcedDim) {
              //
          } else {
			input.val(relUrl);
			if(showPreview) {
				var preview = $('<img src="'+attachment.url+'" />'),
					ratio = (attachment.height/attachment.width).toFixed(2);
				if(previewMaxWh && attachment.width < previewMaxWh && attachment.height < previewMaxWh) {
  					preview.attr({ 'width':attachment.width, 'height':attachment.height });
				} else {
					preview.attr({ 'width':previewMaxWh+'px', 'height': Math.round(previewMaxWh*ratio)+'px' });
				}
				previewEl.append(preview);
			} else {
				filenameEl.html(name);
			}
          }
      });
	  // Open the uploader dialog
	  custom_uploader.open();
	  });
	});
	</script>
	<?php
	$isPrinted = true;
	endif;
	}

	/**
	 * Callback for the admin_print_footer_scripts hook
	 *
	 * Print jQuery JavaScript that is needed for a  basic file upload field
	 *
	 * To force extensions, add the HTML data attribute 'data-force-ext' with comma separated extensions that are allowed, simplified example: <input type="file" data-force-ext="docx,pdf">
	 *
	 * @todo check if using .on() is needed
	 *
	 * @since 1.0
	 */
	public static function printInputFileJs()
	{
		?>
		<script type='text/javascript'>
		jQuery(function($) {

			$('.wpie-change-file').click(function(e) {

				$(this).prev('.wpfilebasic').trigger('click');
			});

		  $('.wpfilebasic').change(function(e) {

	      e.preventDefault();

	      var input = $(this),
	      		filename = input.val(),
	      		filenameEl = input.siblings('.filename');

    		// get forced extensions
    		if(input.data('force-ext')) {

				var extstr = input.data('force-ext'),
    				forcedext = extstr.split(',');

				// get ext from url
				var ext = filename.split('.')[1];

				// filename
				var name = filename.split('.')[0];

				if(ext && -1 !== forcedext.indexOf(ext)) {

					if( '' !== filenameEl.html() ) {
						filenameEl.html(name);
					}

				}	else {

					input.val(null);
					alert('Only files with "'+extstr+'" extension are allowed');
				}
    		}
		  });
		});
	</script>
	<?php
	}

	/**
	 * Callback for the admin_print_footer_scripts hook
	 *
	 * Print jQuery JavaScript that is needed for a term list with $edit is true, see {@link WpieWpFormHelper::_getWpTermlist()}
	 *
	 * @uses WpieWpFormHelper::_getInputTextNoNameValue()
	 * @uses WpieWpFormHelper::_getTermListRow()
	 * @uses WpieWpFormHelper::_getTermListRowActions()
	 *
	 * @since 1.0
	 */
	public static function printTermListJs()
	{
		?>
		<script type='text/javascript'>
		jQuery(function($) {

			var WeePieTermList = {

				termLists: [],
				namespaceAction: '',

				getTermId: function (el) {

					return el.data('term-id');
				},

				getTermLi: function (id) {

					var li = this.termLists.find('li[data-term-id="'+id+'"]');

					if(0 < li.length) {
						return li
					} else {
						 return false;
					}
				},

				switchEditUpdate: function (id, show) {

					var li = this.getTermLi(id);

					if(false !== li) {

						var btnUpdate = li.find('.wpie-action-upd');
						var btnEdit = li.find('.wpie-action-edit');

						switch(show) {
							case 'edit':
								btnUpdate.hide();
								btnEdit.show();
								break;
							case 'update':
								btnUpdate.show();
								btnEdit.hide();
								break;
						}
					}
				},

				handlerMouseenter: function (e) {

					var li = $(this),
							termListliActions = li.find('.wpie-list-row-actions');

					termListliActions.show();
				},


				handlerMouseleave: function (e) {

					var li = $(this),
							termListliActions = li.find('.wpie-list-row-actions');

					termListliActions.hide();
				},


				handlerKeydown: function (e) {

					var thiz = e.data.thiz;

					var li = $(this).parents('li'),
							termId = thiz.getTermId(li);

					thiz.switchEditUpdate(termId, 'update');
				},

				handlerBlur: function (e) {

					var thiz = e.data.thiz;

					var li = $(this).parents('li'),
							input = $(this),
							termNameNew = input.val(),
							termNameOri = li.data('term-name'),
							termId = thiz.getTermId(li);

					if('' === termNameNew) {

						thiz.switchEditUpdate(termId, 'edit');

						li.on('mouseenter', thiz.handlerMouseenter);
						li.on('mouseleave', thiz.handlerMouseleave);

						li.find('.wpie-term-name').val(termNameOri);
						li.find('.wpie-term-name').show();
						input.hide();
						li.trigger('mouseleave');
					}
				},

				handlerBtnDel: function (e) {

					var thiz = e.data.thiz;

					if (confirm(commonL10n.warnDelete)) {

						var li = $(this).parents('li.wpie-term-item'),
								termId = li.data('term-id'),
								tax = e.data.tax,
								termList = e.data.termList;

						if('' === termId || 'undefined' === typeof termId)
							return false;

						var args = {};
						args['action'] = 'wpie-action';
						args[thiz.namespaceAction] = 'wpie-del-term';
						args['nonce'] = wpieNonce;
						args['data'] = {termId:termId, tax:tax};

						$.ajax({ type: 'POST', url: ajaxurl, dataType: 'json', data: args, success: function(r) {

								//console.log('r: ',r);
						  	switch(r.state) {
						  		case '-1': 	alert(r.out); break;
						  		case '1':
										var termId = r.out.term_id;
										termList.find('li.wpie-term-'+termId).remove();
							  		break;
						  	}
					    },
					    error: function (XMLHttpRequest, textStatus, errorThrown) { }
					  });
					} // end confirm delete
				},

				handlerBtnEdit: function (e) {

					var thiz = e.data.thiz;

					var li = $(this).parents('li.wpie-term-item'),
							termId = li.data('term-id'),
							termName = li.data('term-name'),
							liSpan = li.find('.wpie-term-name'),
							input = $('<?php echo str_replace(parent::ATTR_MASK, 'class="wpie-edit-term" value=""', self::_getInputTextNoNameValue()) ?>');

					//console.log(termId, termName);

					li.off('mouseenter', thiz.handlerMouseenter);
					li.off('mouseleave', thiz.handlerMouseleave);

					liSpan.hide();
					liSpan.after(input);

					input.trigger('focus');
				},

				handlerBtnUpd: function (e) {

					var li = $(this).parents('li.wpie-term-item'),
							termId = li.data('term-id'),
							liSpan = li.find('.wpie-term-name'),
							input = li.find('input.wpie-edit-term'),
							termNameNew = input.val(),
							tax = e.data.tax;

					if('' === termId || 'undefined' === typeof termId || '' === termNameNew || 'undefined' === typeof termNameNew)
						return false;

					var args = {};
					args['action'] = 'wpie-action';
					args[thiz.namespaceAction] = 'wpie-upd-term';
					args['nonce'] = wpieNonce;
					args['data'] = {termId:termId, tax:tax, termName:termNameNew};

					$.ajax({ type: 'POST', url: ajaxurl, dataType: 'json', data: args, success: function(r) {

					  	switch(r.state) {
					  		case '-1': 	alert(r.out); break;
					  		case '1':
									liSpan.html(r.out.name).show();
									li.attr('data-term-name', r.out.name).data('term-name', r.out.name);
						  		break;
					  	}

					  	input.val('').hide();
				    },
				    error: function (XMLHttpRequest, textStatus, errorThrown) { }
				  });
				},

				handlerBtnAdd: function (e) {

					var termInput = $(this).prev('input.wpie-new-term'),
							termVal =	termInput.val();

					if('' === termVal)
						return false;

					var thiz = e.data.thiz,
							tax = e.data.tax,
							termList = e.data.termList;

					var args = {};
					args['action'] = 'wpie-action';
					args[thiz.namespaceAction] = 'wpie-add-term';
					args['nonce'] = wpieNonce;
					args['data'] = {tax:tax, termVal:termVal};

					$.ajax({ type: 'POST', url: ajaxurl, dataType: 'json', data: args, success: function(r) {

					  	switch(r.state) {
					  		case '-1': 	alert(r.out); break;
					  		case '1':
									var termId = r.out.term_id,
											termName = r.out.name,
					  					li = '<?php echo self::_getTermListRow()?>';

					  			li = li.replace(/%d/g, termId);
					  			li = li.replace(/%s/g, termName);
					  			termList.append(li);

					  			// Re-init all term lists again
					  			// The just newly created term li will then also have all logic binded
					  			thiz.init();
						  		break;
					  	}
							termInput.val('');
				    },
				    error: function (XMLHttpRequest, textStatus, errorThrown) { }
				  });
				},

				init: function () {

					if(0 < $('.wpie-term-list').length)
						this.termLists = $('.wpie-term-list');
					else
						return false;

					// WordPress AJAX action
					// wpieNamespace is an unique string passed bij WeePie Framework
					// So that multiple Instances of the Framework can use this JS
					// @todo: fix wpieNamespace does not exist
					this.namespaceAction = wpieNamespace+'_action';

					var thiz = this;

					this.termLists.each(function() {

						var termList = $(this),
								termInput = $(this).next('input.wpie-new-term'),
						 		btnAdd = termInput.next('.wpie-btn-add'),
								termListli = termList.find('li'),
								tax = termInput.data('tax');

						// Append row actions to each li
						termListli.each(function() {
							var li = $(this);
							if( 0 === li.find('.wpie-list-row-actions').length ) {
								li.append(' <?php echo self::_getTermListRowActions() ?>');
							}
						});

						var termListliActions = termListli.find('.wpie-list-row-actions'),
								btnDel = termListliActions.find('.wpie-action-del'),
								btnEdit = termListliActions.find('.wpie-action-edit'),
								btnUpd = termListliActions.find('.wpie-action-upd');


						termListli.on('keydown', 'input.wpie-edit-term', {thiz:thiz}, thiz.handlerKeydown);
						termListli.on('blur', 'input.wpie-edit-term', {thiz:thiz}, thiz.handlerBlur);

						termListli.on({
							mouseenter: thiz.handlerMouseenter,
							mouseleave: thiz.handlerMouseleave,
						});

						btnDel.on('click', {thiz:thiz, tax:tax, termList:termList}, thiz.handlerBtnDel);
						btnEdit.on('click', {thiz:thiz, tax:tax, termList:termList}, thiz.handlerBtnEdit);
						btnUpd.on('click', {thiz:thiz, tax:tax, termList:termList}, thiz.handlerBtnUpd);
						btnAdd.on('click', {thiz:thiz, tax:tax, termList:termList}, thiz.handlerBtnAdd);
					});	//end termLists each
				}
			};

			// Init all params and events and loop trew all term lists
			WeePieTermList.init();
		});
		</script>
		<?php
	}

	/**
	 * Callback for the admin_print_footer_scripts hook
	 *
	 * Print jQuery JavaScript that is needed for a color picker field
	 *
	 * @uses wpColorPicker()
	 *
	 * @since 1.0
	 */
	public static function printColorPickerJs()
	{
	?>
	<script type='text/javascript'>
	jQuery(function($) {
		$('.colorpicker').wpColorPicker();
	});
	</script>
	<?php
	}

	/* (non-PHPdoc)
	 * @see FormHelper::_prepareAttributes()
	 *
	 * @since 0.1
	 */
	protected static function _prepareAttributes()
	{
		//add default classes
		if( 'wpajaxbutton' === self::$type ) {
			self::$attributes['class'] = ( isset(self::$attributes['class']) ) ? self::$attributes['class'].' button' : 'button';
		}
		if( 'wpimage' === self::$type ) {
			self::$attributes['class'] = ( isset(self::$attributes['class']) ) ? self::$attributes['class'].' wpimage' : 'wpimage';
		}
		if( 'wpfile' === self::$type ) {
			self::$attributes['class'] = ( isset(self::$attributes['class']) ) ? self::$attributes['class'].' wpfile' : 'wpfile';
		}
		if( 'wpfilebasic' === self::$type ) {
			self::$attributes['class'] = ( isset(self::$attributes['class']) ) ? self::$attributes['class'].' wpfilebasic' : 'wpfilebasic';
		}
		if( 'wppageselect' === self::$type ) {
			self::$attributes['class'] = ( isset(self::$attributes['class']) ) ? self::$attributes['class'].' wppageselect' : 'wppageselect';
		}
		if( 'wptermselect' === self::$type ) {
			self::$attributes['class'] = ( isset(self::$attributes['class']) ) ? self::$attributes['class'].' wptermselect' : 'wptermselect';
			self::$attributes['_tax'] = ( taxonomy_exists(self::$attributes['_tax']) ) ? self::$attributes['_tax'] : 'category';
		}
		if( 'wptermlist' === self::$type || 'wptermlist_edit' === self::$type ) {
			self::$attributes['class'] = ( isset(self::$attributes['class']) ) ? self::$attributes['class'].' wpie-term-list' : 'wpie-term-list';
			self::$attributes['_tax'] = ( taxonomy_exists(self::$attributes['_tax']) ) ? self::$attributes['_tax'] : 'category';
		}
		if( 'wpcpostlist_edit' === self::$type ) {
			self::$attributes['class'] = ( isset(self::$attributes['class']) ) ? self::$attributes['class'].' wpie-cpostype-list' : 'wpie-cpostype-list';

			self::$attributes['_layout'] = ( isset( self::$attributes['_layout'] ) ) ? self::$attributes['_layout'] : 'normal';
			self::$attributes['_outer_w'] = ( isset( self::$attributes['_outer_w'] ) ) ? self::$attributes['_outer_w'] : 20;
			self::$attributes['_context'] = ( isset( self::$attributes['_context'] ) ) ? self::$attributes['_context'] : '';
			self::$attributes['_post_type'] = ( isset( self::$attributes['_post_type'] ) && '' !== self::$attributes['_post_type'] ) ? self::$attributes['_post_type'] : '';

			self::$attributes['_can_add'] = ( !isset( self::$attributes['_can_add'] ) || ( isset( self::$attributes['_can_add'] ) && 'true' === self::$attributes['_can_add'] ) ) ? true : false;
			self::$attributes['_can_del'] = ( !isset( self::$attributes['_can_del'] ) || ( isset( self::$attributes['_can_del'] ) && 'true' === self::$attributes['_can_del'] ) ) ? true : false;
			self::$attributes['_can_save'] = ( !isset( self::$attributes['_can_save'] ) || ( isset( self::$attributes['_can_save'] ) && 'true' === self::$attributes['_can_save'] ) ) ? true : false;
			self::$attributes['_click_select'] = ( isset( self::$attributes['_click_select'] ) && 'true' === self::$attributes['_click_select'] ) ? true : false;

			self::$attributes['_has_title'] = ( !isset( self::$attributes['_has_title'] ) || ( isset( self::$attributes['_has_title'] ) && 'true' === self::$attributes['_has_title'] ) ) ? true : false;
			self::$attributes['_has_media'] = ( !isset( self::$attributes['_has_media'] ) || ( isset( self::$attributes['_has_media'] ) && 'false' === self::$attributes['_has_media'] ) ) ? false : true;

			self::$attributes['_group_meta'] = ( !isset( self::$attributes['_group_meta'] ) || ( isset( self::$attributes['_group_meta'] ) && 'false' === self::$attributes['_group_meta'] ) ) ? false : true;
			self::$attributes['_group_meta_key'] = ( isset( self::$attributes['_group_meta_key'] ) ) ? self::$attributes['_group_meta_key'] : '';

			self::$attributes['_headings'] = ( isset( self::$attributes['_headings'] ) ) ? self::$attributes['_headings'] : '';
			self::$attributes['_has_heading'] = ( !isset( self::$attributes['_has_heading'] ) || ( isset( self::$attributes['_has_heading'] ) && 'false' === self::$attributes['_has_heading'] ) ) ? false : true;

		}
		if( 'wpposttypeselect' === self::$type ) {
			self::$attributes['class'] = ( isset(self::$attributes['class']) ) ? self::$attributes['class'].' wpposttypeselect' : 'wpposttypeselect';
		}
		if( 'wpcustomptselect' === self::$type ) {
			self::$attributes['_post_type'] = ( isset( self::$attributes['_post_type'] ) && '' !== self::$attributes['_post_type'] ) ? self::$attributes['_post_type'] : 'post';
		}
		if( 'wpdate' === self::$type || 'wpdatepicker' === self::$type ) {
			self::$attributes['class'] = ( isset(self::$attributes['class']) ) ? self::$attributes['class'].' dateinput datepicker' : 'dateinput datepicker';
		}
		if( 'wpcolor' === self::$type || 'wpcolorpicker' === self::$type ) {
			self::$attributes['class'] = ( isset(self::$attributes['class']) ) ? self::$attributes['class'].' colorpicker' : 'colorpicker';
		}
		if( 'wptextarea' === self::$type ) {

		}
		if( 'wptextareabasic' === self::$type ) {
			unset( self::$attributes['value'] );
		}
	}

	/* (non-PHPdoc)
	 * @see FormHelper::_substituteAttributes()
	 *
	 * @since 1.0
	 */
	protected static function _substituteAttributes( &$field )
	{
		if( preg_match_all('/{([a-zA-Z]+?)}/', $field, $matches) ) {
			foreach( $matches[0] as $k => $attr ) {
				switch( $attr ) {
					case self::VALUE_MASK_FILENAME:
						$field = preg_replace( "/$attr/", self::$filename, $field );
						break;
				}
			}
		}

		parent::_substituteAttributes( $field );
	}

	/* (non-PHPdoc)
	 * @see FormHelper::_reset()
	 *
	 * @since 1.0
	 */
	protected static function _reset()
	{
		self::$filename = '';

		parent::_reset();
	}

	/**
	 * Get the HTML for the WordPress image upload button
	 *
	 * @access private
	 *
	 * @uses WpieWpFormHelper::enqueueMediaScripts()
	 *
	 * @since 0.1
	 *
	 * @return string
	 */
	private static function _getWpInputImage()
	{
		self::enqueueMediaScripts();
		add_action( 'admin_print_footer_scripts', [__CLASS__, 'printInputImageJs'] );

		$tabindex		= ( isset( parent::$attributes['tabindex'] ) ) ? ' tabindex="'.parent::$attributes['tabindex'].'"' : '';
		$previewMaxWh	= ( isset( parent::$attributes['data-preview-max-wh'] ) && is_numeric( parent::$attributes['data-preview-max-wh'] ) ) ? parent::$attributes['data-preview-max-wh'] : false;
		$previewW		= ( isset( parent::$attributes['data-preview-w'] ) && is_numeric( parent::$attributes['data-preview-w'] ) ) ? parent::$attributes['data-preview-w'] : false;
		$previewH		= ( isset( parent::$attributes['data-preview-h'] ) && is_numeric( parent::$attributes['data-preview-h'] ) ) ? parent::$attributes['data-preview-h'] : false;
		$ratio			= ( isset( parent::$attributes['data-preview-ratio'] ) && is_numeric( parent::$attributes['data-preview-ratio'] ) ) ? parent::$attributes['data-preview-ratio'] : 1;

		$w = ( $previewMaxWh && $previewW && $previewMaxWh <= $previewW ) ? $previewMaxWh : $previewW;
		$h = ( $previewMaxWh && $previewH && $previewMaxWh <= $previewH ) ? round( $previewMaxWh*$ratio ) : $previewH;

		$style = '';
		if( $previewMaxWh && 'svg' === self::$fileExt ) {
			$w = $previewMaxWh;
			$h = '';
			$style = " style='max-width:{$previewMaxWh}px; max-height:{$previewMaxWh}px'";
		}

		$preview = ( isset( parent::$attributes['data-show-preview'] ) && true == parent::$attributes['data-show-preview'] && parent::$value ) ? '<img width="'.$w.'" height="'.$h.'" src="'.WP_CONTENT_URL . '/uploads/' . parent::VALUE_MASK.'"'.$style.' />' : '';
		unset( parent::$attributes['tabindex'] );

		$input  = '<input '.parent::ATTR_MASK.' type="hidden" name="'.parent::NAME_MASK.'" value="'.parent::VALUE_MASK.'" />';
		$input .= '<button class="button wp_upload_button upload_img_button" data-button-type="image"'.$tabindex.'>'.parent::INNER_HTML_MASK.'</button><span class="filename">'. (( '' === $preview ) ? self::VALUE_MASK_FILENAME : '') . '</span><span class="preview">'.$preview.'</span>';

		return $input;
	}

	/**
	 * Get the HTML for the WordPress file upload button
	 *
	 * @access private
	 *
	 * @uses WpieWpFormHelper::enqueueMediaScripts()
	 *
	 * @since 1.0
	 *
	 * @return string
	 */
	private static function _getWpInputFile()
	{
		self::enqueueMediaScripts();
		add_action( 'admin_print_footer_scripts', [ __CLASS__, 'printInputFileJs' ] );

		$disabled = ( parent::$isDisabled ) ? ' disabled="disabled"' : '';

		$input  = '<input '.parent::ATTR_MASK.' type="hidden" name="'.parent::NAME_MASK.'" value="'.parent::VALUE_MASK.'" /><span class="filename">'.self::VALUE_MASK_FILENAME.'</span>';
		$input .= '<button class="button wp_upload_button upload_file_button" data-button-type="file"'.$disabled.'>'.parent::INNER_HTML_MASK.'</button>';

		return $input;
	}

	/**
	 * Get the HTML for a basic file upload button
	 *
	 * @access private
	 *
	 * @uses FormHelper::_getInputFile()
	 * @uses FormHelper::_getLinkButton()
	 * @uses FormHelper::_substituteAttributesManual()
	 *
	 * @since 1.0
	 *
	 * @return string
	 */
	private static function _getWpInputFileBasic()
	{
		add_action('admin_print_footer_scripts', [__CLASS__, 'printInputFileJs']);

		$input = '';
		if( '' !== self::$filename ) {
			$input  = '<span class="filename">'.self::VALUE_MASK_FILENAME.'</span>';
			$file .= parent::_getInputFile();

			if( isset(parent::$attributes['style']) ) {
				parent::$attributes['style'] = parent::$attributes['style'] . ';display:none;';
			} else {
				parent::$attributes['style'] = 'display:none';
			}

			$change = parent::_getLinkButton();
			$disabled = (parent::$isDisabled) ? 'disabled' : '';

			parent::_substituteAttributesManual($change, parent::ATTR_MASK, 'class="button wpie-change-file '.$disabled.'"');

			$input .= $file.$change;
		} else {
			$input .= parent::_getInputFile();
		}

		return $input;
	}

	/**
	 * Get the HTML for a WordPress link (a) button with ajax loader gif
	 *
	 * @access private
	 *
	 * @since 0.1
	 *
	 * @return string
	 */
	private static function _getWpInputAjaxButton()
	{
		return '<a '.self::ATTR_MASK.'>'.self::INNER_HTML_MASK.'</a><img alt="" class="ajax-loading" src="'.admin_url('images/wpspin_light.gif').'" />';
	}

	/**
	 * Get the HTML for a selectbox with WordPress pages
	 *
	 * @access private
	 *
	 * @since 0.1
	 *
	 * @return string
	 */
	private static function _getWpPageSelect()
	{
		return self::getPageDropdown( self::$attributes['id'], self::$attributes['name'], 'ID', 0, self::$attributes['value'] );
	}

	/**
	 * Get the HTML for a selectbox with WordPress pages
	 *
	 * @access private
	 *
	 * @since 1.4.7
	 *
	 * @return string
	 */
	private static function _getWpCustomPostTypeSelect()
	{
		$id       = self::$attributes['id'];
		$name     = self::$attributes['name'];
		$selected = self::$attributes['value'];
		$postType = self::$attributes['_post_type'];

		return self::getCustomPostTypeDropdown( $id, $name, $selected, 0, $postType );
	}

	/**
	 * Get the HTML for a selectbox with Post Types
	 *
	 * @uses WpieWpFormHelper::getPostTypeTypesDropDown()
	 *
	 * @since 1.0
	 * @since 1.4.7 renamed to _getWpPostTypeTypesSelect()
	 *
	 * @return string
	 */
	private static function _getWpPostTypeTypesSelect()
	{
		return self::getPostTypeTypesDropDown( self::$attributes['id'], self::$attributes['name'], 'ID', 0, self::$attributes['value'] );
	}

	/**
	 * Get the HTML for a selectbox with WordPress terms of given taxonomy
	 *
	 * @access private
	 *
	 * @since 0.1
	 *
	 * @return string
	 */
	private static function _getWpTermSelect()
	{
		return self::getTermDropdown( self::$attributes['id'], self::$attributes['name'], 'ID', 0, self::$attributes['value'], self::$attributes['_tax'] );
	}

	/**
	 * Get a WordPress editor
	 *
	 * @uses wp_editor()
	 *
	 * @since 1.0
	 *
	 * @todo possibility to change wp_editor paramaters
	 *
	 * @return string
	 */
	private static function _getWpTextarea()
	{
		$value = html_entity_decode( self::$value, ENT_QUOTES, 'UTF-8' );

		$name_in_ar = ( preg_match( '/^[^\[]+\[(.+)]$/', self::$attributes['name'], $m ) ) ? $m[1] : '';
		$editor_id = 	( '' !== $name_in_ar ) ? $name_in_ar : self::$name;

		// attributes
		$teeny  	= ( isset( self::$attributes['_teeny'] ) 	&& ( 'true' === self::$attributes['_teeny'] 	|| true === self::$attributes['_teeny'] ) 	) ? true : false;
		$minimal	= ( isset( self::$attributes['_minimal'] ) 	&& ( 'true' === self::$attributes['_minimal']	|| true === self::$attributes['_minimal'] ) ) ? true : false;
		$media		= ( isset( self::$attributes['_media'] ) 	&& ( 'false' === self::$attributes['_media']	|| true === self::$attributes['_media'] ) ) ? false : true;
		$rows		= ( isset( self::$attributes['_rows'] ) 	&& ( is_numeric( self::$attributes['_rows'] ) ) ) ? strval( self::$attributes['_rows'] ) : '5';
		$settings 	= [ 'wpautop' => true, 'media_buttons' => true, 'quicktags' => true, 'textarea_rows' => $rows, 'textarea_name' => self::$attributes['name'], 'teeny' => $teeny ];

		if( $minimal ) {
			$settings['media_buttons'] 	= false;
			$settings['tinymce']		= false;
			$settings['quicktags'] 		= [ 'buttons' => 'strong,em,link,block,del,ins,img,ul,ol,li,code,close' ];
		} else {
			$settings['media_buttons'] 	= $media;
			// fix AJAX saving issue
			add_action( 'before_wp_tiny_mce', [ __CLASS__, 'printWpEditorAjaxSaveFix' ] );
		}

		ob_start();
		wp_editor( $value, $editor_id, $settings );
		$editor = ob_get_contents();
		ob_end_clean();

		return $editor;
	}

	/**
	 * Get a default textarea but validated by WordPress
	 *
	 * @uses esc_textarea()
	 *
	 * @since 1.0.1
	 *
	 * @return string
	 */
	private static function _getWpTextareaBasic()
	{
		$textarea = parent::_getTextarea();

		parent::_substituteAttributesManual( $textarea, parent::INNER_HTML_MASK, esc_textarea( parent::$value ) );

		return $textarea;
	}

	/**
	 * Get the HTML for a datepicker element
	 *
	 * @uses FormHelper::_getInputText()
	 *
	 * @access private
	 *
	 * @since 1.0
	 *
	 * @return string
	 */
	private static function _getWpDatePicker()
	{
		return parent::_getInputText();
	}

	/**
	 * Get the HTML for the 'add' fields used by formfield 'wptermlist_edit'
	 *
	 * @access private
	 *
	 * @since 1.0
	 *
	 * @return string
	 */
	private static function _getTermListActions()
	{
		$html = $input = $add = '';

		$input = parent::_getInputTextNoNameValue();
		parent::_substituteAttributesManual($input, parent::ATTR_MASK, sprintf('value="" id="%s" class="wpie-new-term" data-tax="%s"', parent::$name, parent::$attributes['_tax']));

		$add = parent::_getLinkButton();
		parent::_substituteAttributesManual($add, parent::INNER_HTML_MASK, 'add');
		parent::_substituteAttributesManual($add, parent::ATTR_MASK, 'class="button wpie-btn-add"');

		$html = $input.$add;

		return $html;
	}

	/**
	 * Get the HTML template for one row in a terms list
	 *
	 * @access private
	 *
	 * @since 1.0
	 *
	 * @return string
	 */
	private static function _getTermListRow()
	{
		return '<li data-term-id="%d" class="wpie-term-item wpie-term-%d" data-term-name="%s"><span class="wpie-term-name">%s</span></li>';
	}

	/**
	 * Get the HTML template for the actions in one row of a terms list
	 *
	 * @access private
	 *
	 * @since 1.0
	 *
	 * @return string
	 */
	private static function _getTermListRowActions()
	{
		return '<a class="wpie-list-row-actions" style="display:none"><span class="wpie-action-del" style="color:red">delete </span><span class="wpie-action-edit">| edit</span><span class="wpie-action-upd" style="display:none">| update</span></a>';
	}

	/**
	 * Get the HTML for a term list
	 *
	 * If $edit is true, JavaScript is added by WordPress hook "admin_print_footer_scripts" with {@link WpieWpFormHelper::printTermListJs()}
	 *
	 * @access private
	 *
	 * @param bool $edit flag if edit controls should be added or not
	 *
	 * @uses WpieWpFormHelper::getTermList()
	 * @uses WpieWpFormHelper::_getTermListActions()
	 * @uses WpieWpFormHelper::getTermList()
	 *
	 * @since 1.0
	 *
	 * @return string
	 */
	private static function _getWpTermlist( $edit = false )
	{
		if( $edit ) {
			add_action( 'admin_print_footer_scripts', [ __CLASS__, 'printTermListJs' ] );

			$list = self::getTermList( self::$attributes['_tax'] );
			$controls = self::_getTermListActions();

			return $list.$controls;
		} else {
			return self::getTermList( self::$attributes['_tax'] );
		}
	}

	/**
	 * Get the HTML for a custom post type weepie list
	 *
	 * @access	private
	 *
	 * @param bool $edit flag if edit controls should be added or not
	 *
	 * @since	1.2.1
	 *
	 * @uses	self::getCustomPostTypeList()
	 *
	 * @return string
	 */
	private static function _getWpCustomPostTypeList( $edit = false )
	{
		// general attributes
		$layout       = self::$attributes['_layout'];
		$outerW       = self::$attributes['_outer_w'];
		$context      = self::$attributes['_context'];
		$postType     = self::$attributes['_post_type'];
		$hasTitle     = self::$attributes['_has_title'];
		$groupMeta    = self::$attributes['_group_meta'];
		$groupMetaKey = self::$attributes['_group_meta_key'];
		$clickSelect  = self::$attributes['_click_select'];
		$hasHeading   = self::$attributes['_has_heading'];

		if( $edit ) {
			$canAdd	  = self::$attributes['_can_add'];
			$canDel	  = self::$attributes['_can_del'];
			$canSave  = self::$attributes['_can_save'];
			$hasMedia = self::$attributes['_has_media'];
		} else {
			$canAdd	= $canDel = $canSave = $hasMedia = false;
		}

		return self::getCustomPostTypeList( $postType, $layout, $outerW, $context, $canAdd, $canDel, $canSave, $clickSelect, $hasTitle, $hasMedia, $groupMeta, $groupMetaKey, $hasHeading );
	}

	/**
	 * Get HTMNL for a color picker
	 *
	 * JavaScript is added by WordPress hook "admin_print_footer_scripts" with {@link WpieWpFormHelper::printColorPickerJs()}
	 *
	 * @uses wp_enqueue_style to enqueue the color picker styles
	 * @uses wp_enqueue_script to enqueue the color picker scripts
	 * @uses WpieWpFormHelper::_getColorPicker()
	 *
	 * @since 1.0
	 *
	 * @return string
	 */
	private static function _getWpColorPicker()
	{
		wp_enqueue_style( 'wp-color-picker' );
		wp_enqueue_script( 'wp-color-picker' );

		add_action( 'admin_print_footer_scripts', [ __CLASS__, 'printColorPickerJs' ], 9999 );

		$input = parent::_getColorPicker();

		return $input;
	}

	/**
	 * Render the WordPress formfield
	 *
	 * @access private
	 *
	 * @uses WpieWpFormHelper::_substituteAttributes()
	 * @uses WpieWpFormHelper::_reset()
	 *
	 * @since 0.1
	 *
	 * @return string the formfield or a translatable error message
	 */
	private static function _render()
	{
		switch( parent::$type )
		{
			case 'wpimage':
					$field = self::_getWpInputImage();
				break;

			case 'wpfile':
					$field = self::_getWpInputFile();
				break;

			case 'wpfilebasic':
					$field = self::_getWpInputFileBasic();
				break;

			case 'wpajaxbutton':
				$field = self::_getWpInputAjaxButton();
				break;

			case 'wppageselect' :
				$field = self::_getWpPageSelect();
				break;

			case 'wpcustomptselect':
				$field = self::_getWpCustomPostTypeSelect();
				break;

			case 'wptermselect' :
				$field = self::_getWpTermSelect();
				break;

			case 'wpposttypeselect' :
				$field = self::_getWpPostTypeTypesSelect();
				break;

			case 'wptextarea':
				$field = self::_getWpTextarea();
				break;

			case 'wptextareabasic':
				$field = self::_getWpTextareaBasic();
				break;

			case 'wpdate':
			case 'wpdatepicker':
				$field = self::_getWpDatePicker();
				break;

			case 'wptermlist':
				$field = self::_getWpTermlist();
				break;

			case 'wptermlist_edit':
				$field = self::_getWpTermlist(true);
				break;

			case 'wpcpostlist':
				$field = self::_getWpCustomPostTypeList();
				break;

			case 'wpcpostlist_edit':
				$field = self::_getWpCustomPostTypeList(true);
				break;

			case 'wpcolor':
			case 'wpcolorpicker':
				$field = self::_getWpColorPicker();
				break;

			default:
				return sprintf( __( 'Invalid formfield type: %s', 'weepie' ), self::$type);
				break;
		}

		self::_substituteAttributes( $field );

		// flush members
		self::_reset();

		return $field;
	}
}