<?php
/**
 * Main file of the plugin
 *
 * @package categoryposts.
 *
 * @since 1.0
 */

/*
Plugin Name: Category Posts Widget
Plugin URI: https://wordpress.org/plugins/category-posts/
Description: Adds a widget that shows the most recent posts from a single category.
Author: TipTopPress
Version: 4.8.5
Author URI: http://tiptoppress.com
Text Domain: category-posts
Domain Path: /languages
*/

namespace categoryPosts;

// Don't call the file directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

const VERSION        = '4.8.5';
const DOC_URL        = 'http://tiptoppress.com/category-posts-widget/documentation-4-8?utm_source=widget_cpw&utm_campaign=documentation_4_8_cpw&utm_medium=form';
const PRO_URL        = 'http://tiptoppress.com/term-and-category-based-posts-widget/?utm_source=widget_cpw&utm_campaign=get_pro_cpw&utm_medium=action_link';
const SUPPORT_URL    = 'https://wordpress.org/support/plugin/category-posts';
const SHORTCODE_NAME = 'catposts';
const SHORTCODE_META = 'categoryPosts-shorcode';
const WIDGET_BASE_ID = 'category-posts';

/**
 *  Adds the "Customize" link to the Toolbar on edit mode.
 *
 *  @since 4.6
 */
function wp_admin_bar_customize_menu() {
	global $wp_admin_bar;

	if ( ! isset( $_GET['action'] ) || ( 'edit' !== $_GET['action'] ) ) {
		return;
	}

	if ( ! current_user_can( 'customize' ) || ! is_admin() || ! is_user_logged_in() || ! is_admin_bar_showing() ) {
		return;
	}

	$current_url = '';
	if ( isset( $_GET['post'] ) && ( '' !== $_GET['post'] ) ) {
		$current_url = get_permalink( absint( $_GET['post'] ) );
	}
	$customize_url = add_query_arg( 'url', rawurlencode( $current_url ), wp_customize_url() );

	$p = isset( $_GET['post'] ) && ! empty( $_GET['post'] ) ? get_post( absint( $_GET['post'] ) ) : false;
	$names = $p ? shortcode_names( SHORTCODE_NAME, $p->post_content ) : array();
	if ( empty( $names ) ) {
		return;
	}

	$wp_admin_bar->add_menu( array(
		'id'    => 'customize',
		'title' => __( 'Customize' ),
		'href'  => $customize_url,
		'meta'  => array(
			'class' => 'hide-if-no-customize',
		),
	) );
	add_action( 'wp_before_admin_bar_render', 'wp_customize_support_script' );
}

add_action( 'admin_bar_menu', __NAMESPACE__ . '\wp_admin_bar_customize_menu', 35 );

/**
 * Print out required CSS, hooked on the wp_head hook.
 */
function wp_head() {

	$widget_repository = new virtualWidgetsRepository();

	$styles = array();

	foreach ( $widget_repository->getShortcodes() as $widget ) {
		$widget->getCSSRules( true, $styles );
	}

	foreach ( $widget_repository->getWidgets() as $widget ) {
		$widget->getCSSRules( false, $styles );
	}

	if ( ! empty( $styles ) ) {
	?>
<style type="text/css">
	<?php
	foreach ( $styles as $rules ) {
		foreach ( $rules as $rule ) {
			echo "$rule\n"; // Xss ok. raw css output, can not be html escaped.
		}
	}
	?>
</style>
	<?php
	}
}

add_action( 'wp_head', __NAMESPACE__ . '\register_virtual_widgets', 0 );

/**
 *  Register virtual widgets for all widgets and shortcodes that are going to be displayed on the page
 *
 *  @return void
 *
 *  @since 4.7
 */
function register_virtual_widgets() {
	global $post;
	global $wp_registered_widgets;

	$repository = new virtualWidgetsRepository();

	// check first for shortcode settings.
	if ( is_singular() ) {
		$names = shortcode_names( SHORTCODE_NAME, $post->post_content );

		foreach ( $names as $name ) {
			$meta = shortcode_settings( $name );
			if ( is_array( $meta ) ) {
				$id = WIDGET_BASE_ID . '-shortcode-' . get_the_ID(); // needed to make a unique id for the widget html element.
				if ( '' !== $name ) { // if not default name append to the id.
					$id .= '-' . sanitize_title( $name ); // sanitize to be on the safe side, not sure where when and how this will be used.
				}
				$repository->addShortcode( $name, new virtualWidget( $id, WIDGET_BASE_ID . '-shortcode', $meta ) );
			}
		}
	}

	$sidebars_widgets = wp_get_sidebars_widgets();

	if ( is_array( $sidebars_widgets ) ) {
		foreach ( $sidebars_widgets as $sidebar => $widgets ) {
			if ( 'wp_inactive_widgets' === $sidebar || 'orphaned_widgets' === substr( $sidebar, 0, 16 ) ) {
				continue;
			}

			if ( is_array( $widgets ) ) {
				foreach ( $widgets as $widget ) {
					$widget_base = _get_widget_id_base( $widget );
					if ( WIDGET_BASE_ID === $widget_base ) {
						$class = __NAMESPACE__ . '\Widget';
						$widgetclass = new $class();
						$allsettings = $widgetclass->get_settings();
						$settings = isset( $allsettings[ str_replace( $widget_base . '-', '', $widget ) ] ) ? $allsettings[ str_replace( $widget_base . '-', '', $widget ) ] : false;
						$repository->addWidget( $widget, new virtualWidget( $widget, $widget, $settings ) );
					}
				}
			}
		}
	}
}

add_action( 'wp_head', __NAMESPACE__ . '\wp_head' );

/**
 * Enqueue widget related scripts for the widget admin page and customizer.
 *
 * @param string $hook the name of the admin hook for which the function was triggered.
 */
function admin_scripts( $hook ) {

	if ( 'widgets.php' === $hook || 'post.php' === $hook ) { // enqueue only for widget admin and customizer. (add if post.php: fix make widget SiteOrigin Page Builder plugin, GH issue #181)

		// control open and close the widget section.
		wp_register_script( 'category-posts-widget-admin-js', plugins_url( 'js/admin/category-posts-widget.js', __FILE__ ), array( 'jquery' ), VERSION, true );
		wp_enqueue_script( 'category-posts-widget-admin-js' );

		$js_data = array( 'accordion' => false );
		$meta = get_user_meta( get_current_user_id(), __NAMESPACE__, true );
		if ( is_array( $meta ) && isset( $meta['panels'] ) ) {
			$js_data['accordion'] = true;
		}
		$js_data['template_tags'] = get_template_tags();
		$js_data[ __NAMESPACE__ ] = $js_data; // To make accessing the data in JS easier to understand.

		wp_localize_script( 'category-posts-widget-admin-js', 'tiptoppress', $js_data );
		wp_enqueue_media();
		wp_localize_script( 'category-posts-widget-admin-js', 'cwp_default_thumb_selection', array(
			'frame_title'  => __( 'Select a default thumbnail', 'category-posts' ),
			'button_title' => __( 'Select', 'category-posts' ),
			'none'         => __( 'None', 'category-posts' ),
		) );
	}
}

add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\admin_scripts' ); // "called on widgets.php and costumizer since 3.9.

add_action( 'admin_init', __NAMESPACE__ . '\load_textdomain' );

/**
 * Load plugin textdomain.
 *
 * @return void
 *
 * @since 4.1
 **/
function load_textdomain() {
	load_plugin_textdomain( 'category-posts', false, plugin_basename( dirname( __FILE__ ) ) . '/languages' );
}

/*
 * Add styles for widget sections
 */

add_action( 'admin_print_styles-widgets.php', __NAMESPACE__ . '\admin_styles' );

// fix make widget SiteOrigin Page Builder plugin, GH issue #181
add_action('siteorigin_panel_enqueue_admin_scripts', __NAMESPACE__ . '\admin_styles' );

/**
 * Add required admin styles.
 *
 * @since 4.1
 **/
function admin_styles() {
?>
<style>
.category-widget-cont h4 {
	padding: 12px 15px;
	cursor: pointer;
	margin: 5px 0;
	border: 1px solid #E5E5E5;
}
.category-widget-cont h4:first-child {
	margin-top: 10px;
}
.category-widget-cont h4:last-of-type {
	margin-bottom: 10px;
}
.category-widget-cont h4:after {
	float:right;
	font-family: "dashicons";
	content: '\f140';
	-ms-transform: translate(-1px,1px);
	-webkit-transform: translate(-1px,1px);
	-moz-transform: translate(-1px,1px);
	transform: translate(-1px,1px);
	-ms-transition: all 600ms;
	-webkit-transition: all 600ms;
	-moz-transition: all 600ms;
	transition: all 600ms;
}
.category-widget-cont h4.open:after {
	-ms-transition: all 600ms;
	-webkit-transition: all 600ms;
	-moz-transition: all 600ms;
	transition: all 600ms;
	-ms-transform: rotate(180deg);
	-webkit-transform: rotate(180deg);
	-moz-transform: rotate(180deg);
	transform: rotate(180deg);
}
.category-widget-cont > div {
	display:none;
}
.category-widget-cont > div.open {
	display:block;
}
.category-widget-cont th,
.category-widget-cont tr {
	vertical-align: baseline;
	text-align:start;
}

.cat-post-template-help {display:none;}

.categoryPosts-template textarea {width:100%}

.category-widget-cont .open-template-help {
	border:0;
	padding:0;
	cursor: pointer;
}

</style>
<?php
}

/**
 * Get image size
 *
 * @param int $thumb_w The width of the thumbnail in the widget settings.
 * @param int $thumb_h The height of the thumbnail in the widget settings.
 * @param int $image_w The width of the actual image being displayed.
 * @param int $image_h The height of the actual image being displayed.
 *
 * @return array An array with the width and height of the element containing the image
 */
function get_image_size( $thumb_w, $thumb_h, $image_w, $image_h ) {

	$image_size = array(
		'image_h'    => $thumb_h,
		'image_w'    => $thumb_w,
		'marginAttr' => '',
		'marginVal'  => '',
	);
	$relation_thumbnail = $thumb_w / $thumb_h;
	$relation_cropped = $image_w / $image_h;

	if ( $relation_thumbnail < $relation_cropped ) {
		// crop left and right site
		// thumbnail width/height ration is smaller, need to inflate the height of the image to thumb height
		// and adjust width to keep aspect ration of image.
		$image_size['image_h'] = $thumb_h;
		$image_size['image_w'] = $thumb_h / $image_h * $image_w;
		$image_size['marginAttr'] = 'margin-left';
		$image_size['marginVal'] = ( $image_size['image_w'] - $thumb_w ) / 2;
	} else {
		// crop top and bottom
		// thumbnail width/height ration is bigger, need to inflate the width of the image to thumb width
		// and adjust height to keep aspect ration of image.
		$image_size['image_w'] = $thumb_w;
		$image_size['image_h'] = $thumb_w / $image_w * $image_h;
		$image_size['marginAttr'] = 'margin-top';
		$image_size['marginVal'] = ( $image_size['image_h'] - $thumb_h ) / 2;
	}

	return $image_size;
}

/**
 *  Get the tags which might be used in the template.
 *
 *  @since 4.8
 *
 *  @return array Array of strings of the tags.
 */
function get_template_tags() {
	return array( 'author', 'title', 'date', 'thumb', 'excerpt', 'commentnum', 'post_tag', 'category' );
}

/**
 *  Get a regex to parse the template in order to find the tags used in it.
 *
 *  @since 4.8
 *
 *  @return string The template parsing regex.
 */
function get_template_regex() {
	$tags = get_template_tags();

	$regexp = '';
	foreach ( $tags as $t ) {
		if ( ! empty( $regexp ) ) {
			$regexp .= '|';
		}
		$regexp .= '%' . $t . '%';
	}

	$regexp = '@(' . $regexp . ')@i';

	return $regexp;
}

/**
 * Convert pre 4.8 settings into template
 *
 * @param  array $instance Array which contains the various settings.
 *
 * @since 4.8
 */
function convert_settings_to_template( $instance ) {

	$template = '';

	if ( isset( $instance['thumb'] ) && $instance['thumb'] ) {
		if ( isset( $instance['thumbTop'] ) && $instance['thumbTop'] ) {
			$template .= "%thumb%\n\n";
			if ( ! ( isset( $instance['hide_post_titles'] ) && $instance['hide_post_titles'] ) ) {
				$template .= "%title%\n";
			}
		} elseif ( isset( $instance['date'] ) && $instance['date'] ) {
			if ( ! ( isset( $instance['hide_post_titles'] ) && $instance['hide_post_titles'] ) ) {
				$template .= "%title%\n\n";
			}
			$template .= "%date%\n\n";
			$template .= "%thumb%\n";
		} elseif ( ! ( isset( $instance['hide_post_titles'] ) && $instance['hide_post_titles'] ) ) {
			$template .= "%thumb%\n%title%\n";
		}
	} else {
		if ( ! ( isset( $instance['hide_post_titles'] ) && $instance['hide_post_titles'] ) ) {
			$template .= "%title%\n";
		}
		if ( isset( $instance['date'] ) && $instance['date'] ) {
			$template .= "%date%\n\n";
		}
	}
	if ( isset( $instance['excerpt'] ) && $instance['excerpt'] ) {
		$template .= '%excerpt%';
	}
	if ( isset( $instance['comment_num'] ) && $instance['comment_num'] ) {
		$template .= "%commentnum%\n\n";
	}
	if ( isset( $instance['author'] ) && $instance['author'] ) {
		$template .= "%author%\n\n";
	}

	return $template;
}

/**
 * Category Posts Widget Class
 *
 * Shows the single category posts with some configurable options
 */
class Widget extends \WP_Widget {

	/**
	 * Widget constructor.
	 */
	public function __construct() {
		$widget_ops = array(
			'classname'   => 'cat-post-widget',
			'description' => __( 'List single category posts', 'category-posts' ),
		);
		parent::__construct( WIDGET_BASE_ID, __( 'Category Posts', 'category-posts' ), $widget_ops );
	}

	/**
	 * Calculate the HTML for showing the thumb of a post item.
	 *
	 * Used as a filter for the thumb wordpress API to add css based stretching and cropping
	 * when the image is not at the requested dimensions
	 *
	 * @param string       $html The original HTML generated by the core APIS.
	 * @param int          $post_id the ID of the post of which the thumb is a featured image.
	 * @param int          $post_thumbnail_id The id of the featured image attachment.
	 * @param string|array $size The requested size identified by name or (width, height) array.
	 * @param mixed        $attr ignored in this context.
	 * @return string The HTML for the thumb related to the post
	 *
	 * @since 4.1
	 */
	public function post_thumbnail_html( $html, $post_id, $post_thumbnail_id, $size, $attr ) {
		
		$use_css_cropping = isset( $this->instance['use_css_cropping'] ) && $this->instance['use_css_cropping'];
		$empty_dimensions = empty( $this->instance['thumb_w'] ) || empty( $this->instance['thumb_h'] );
		$thumb = isset( $this->instance['template'] ) && preg_match( '/%thumb%/', $this->instance['template'] );
		
		if ( ! ( $use_css_cropping && ! $empty_dimensions && $thumb ) ) {
			return $html; // If no full dimensions defined, just do not cropping for that image
		}
		$meta = image_get_intermediate_size( $post_thumbnail_id, $size );

		if ( empty( $meta ) ) {
			$post_img = wp_get_attachment_metadata( $post_thumbnail_id, $size );
			$meta['file'] = basename( $post_img['file'] );
		}

		$origfile = get_attached_file( $post_thumbnail_id, true ); // the location of the full file.
		$file = dirname( $origfile ) . '/' . $meta['file']; // the location of the file displayed as thumb.
		if ( file_exists( $file ) ) {
			list( $width, $height ) = getimagesize( $file );  // get actual size of the thumb file.

			if ( isset( $this->instance['use_css_cropping'] ) && $this->instance['use_css_cropping'] ) {
				$image = get_image_size( $this->instance['thumb_w'], $this->instance['thumb_h'], $width, $height );

				// replace srcset.
				$array = array();
				preg_match( '/width="([^"]*)"/i', $html, $array );
				$pattern = '/' . $array[1] . 'w/';
				$html = preg_replace( $pattern, $image['image_w'] . 'w', $html );
				// replace size.
				$pattern = '/' . $array[1] . 'px/';
				$html = preg_replace( $pattern, $image['image_w'] . 'px', $html );
				// replace width.
				$pattern = '/width="[0-9]*"/';
				$html = preg_replace( $pattern, "width='" . $image['image_w'] . "'", $html );
				// replace height.
				$pattern = '/height="[0-9]*"/';
				$html = preg_replace( $pattern, "height='" . $image['image_h'] . "'", $html );
				// set margin.
				$html = str_replace( '<img ', '<img style="' . $image['marginAttr'] . ':-' . $image['marginVal'] . 'px;height:' . $image['image_h']
					. 'px;clip:rect(auto,' . ( $this->instance['thumb_w'] + $image['marginVal'] ) . 'px,auto,' . $image['marginVal']
				. 'px);width:auto;max-width:initial;" ', $html );
				// wrap span with post format.
				$show_post_format   = isset( $this->instance['show_post_format'] ) && ( 'none' !== $this->instance['show_post_format'] );
				if ( $show_post_format || $this->instance['thumb_hover'] ) {
					$format = get_post_format() ? : 'standard';
					$post_format_class = 'cat-post-format cat-post-format-' . $format;
				}
				$html = '<span class="cat-post-crop ' . $post_format_class . '" style="width:' . $this->instance['thumb_w'] . 'px;height:' . $this->instance['thumb_h'] . 'px;">'
					. $html . '</span>';
			} else {
				// use_css_cropping is not used.
				// wrap span.
				$html = '<span>' . $html . '</span>';
			}
		}
		return $html;
	}

	/*
	 * wrapper to execute the the_post_thumbnail with filters.
	 */
	/**
	 * Calculate the HTML for showing the thumb of a post item.
	 *
	 * It is a wrapper to execute the the_post_thumbnail with filters
	 *
	 * @param  string|array $size The requested size identified by name or (width, height) array.
	 *
	 * @return string The HTML for the thumb related to the post and empty string if it can not be calculated
	 *
	 * @since 4.1
	 */
	public function the_post_thumbnail( $size = 'post-thumbnail' ) {
		if ( empty( $size ) ) { // if junk value, make it a normal thumb.
			$size = 'post-thumbnail';
		} elseif ( is_array( $size ) && ( 2 === count( $size ) ) ) {  // good format at least.
			// normalize to ints first.
			$size[0] = (int) $size[0];
			$size[1] = (int) $size[1];
			if ( ( 0 === $size[0] ) && ( 0 === $size[1] ) ) { // Both values zero then revert to thumbnail.
				$size = array( get_option( 'thumbnail_size_w', 150 ), get_option( 'thumbnail_size_h', 150 ) );
			} elseif ( ( 0 === $size[0] ) && ( 0 !== $size[1] ) ) {
				// if one value is zero make a square using the other value.
				$size[0] = $size[1];
			} elseif ( ( 0 !== $size[0] ) && ( 0 === $size[1] ) ) {
				$size[1] = $size[0];
			}
		} else {
			$size = array( get_option( 'thumbnail_size_w', 150 ), get_option( 'thumbnail_size_h', 150 ) ); // yet another form of junk.
		}

		$post_thumbnail_id = get_post_thumbnail_id( get_the_ID() );
		if ( ! $post_thumbnail_id && $this->instance['default_thunmbnail'] ) {
			$post_thumbnail_id = $this->instance['default_thunmbnail'];
		}

		do_action( 'begin_fetch_post_thumbnail_html', get_the_ID(), $post_thumbnail_id, $size );
		$html = wp_get_attachment_image( $post_thumbnail_id, $size, false, '' );
		if ( ! $html ) {
			$ret = '';
		} else {
			$ret = $this->post_thumbnail_html( $html, get_the_ID(), $post_thumbnail_id, $size, '' );
		}
		do_action( 'end_fetch_post_thumbnail_html', get_the_ID(), $post_thumbnail_id, $size );

		return $ret;
	}

	/**
	 * Excerpt more link filter
	 *
	 * @param string $more The "more" text passed by the filter.
	 *
	 * @return string The link to the post with the "more" text configured in the widget.
	 */
	public function excerpt_more_filter( $more ) {
		return ' <a class="cat-post-excerpt-more more-link" href="' . get_permalink() . '">' . esc_html( $this->instance['excerpt_more_text'] ) . '</a>';
	}

	/**
	 * Apply the_content filter for excerpt
	 * This should show sharing buttons which comes with other widgets in the widget output in the same way as on the main content
	 *
	 * @param string $text The HTML with other applied excerpt filters.
	 *
	 * @return string If option hide_social_buttons is unchecked applay the_content filter.
	 *
	 * @since 4.6
	 */
	public function apply_the_excerpt( $text ) {
		$ret = '';
		if ( isset( $this->instance['hide_social_buttons'] ) && $this->instance['hide_social_buttons'] ) {
			$ret = $text;
		} else {
			$ret = apply_filters( 'the_content', $text );
		}
		return $ret;
	}

	/**
	 * Excerpt allow HTML
	 *
	 * @param string $text The HTML with other applied excerpt filters.
	 */
	public function allow_html_excerpt( $text ) {
		global $post, $wp_filter;
		$new_excerpt_length = ( isset( $this->instance['excerpt_length'] ) && $this->instance['excerpt_length'] > 0 ) ? $this->instance['excerpt_length'] : 55;
		if ( '' === $text ) {
			$text = get_the_content( '' );
			$text = strip_shortcodes( $text );
			$text = apply_filters( 'the_content', $text );
			$text = str_replace( '\]\]\>', ']]&gt;', $text );
			$text = preg_replace( '@<script[^>]*?>.*?</script>@si', '', $text );
			$cphtml = array(
				'&lt;a&gt;',
				'&lt;br&gt;',
				'&lt;em&gt;',
				'&lt;i&gt;',
				'&lt;ul&gt;',
				'&lt;ol&gt;',
				'&lt;li&gt;',
				'&lt;p&gt;',
				'&lt;img&gt;',
				'&lt;script&gt;',
				'&lt;style&gt;',
				'&lt;video&gt;',
				'&lt;audio&gt;',
			);
			$allowed_HTML = '';
			foreach ( $cphtml as $index => $name ) {
				if ( in_array( (string) ( $index ), $this->instance['excerpt_allowed_elements'], true ) ) {
					$allowed_HTML .= $cphtml[ $index ];
				}
			}
			$text = strip_tags( $text, htmlspecialchars_decode( $allowed_HTML ) );
			$excerpt_length = $new_excerpt_length;

			if ( ! empty( $this->instance['excerpt_more_text'] ) ) {
				$excerpt_more = $this->excerpt_more_filter( $this->instance['excerpt_more_text'] );
			} elseif ( $filterName = key( $wp_filter['excerpt_more'][10] ) ) {
				$excerpt_more = $wp_filter['excerpt_more'][10][ $filterName ]['function']( 0 );
			} else {
				$excerpt_more = '[...]';
			}

			$words = explode( ' ', $text, $excerpt_length + 1 );
			if ( count( $words ) > $excerpt_length ) {
				array_pop( $words );
				array_push( $words, $excerpt_more );
				$text = implode( ' ', $words );
			}
		}

		return '<p>' . $text . '</p>';
	}

	/**
	 * Calculate the wp-query arguments matching the filter settings of the widget
	 *
	 * @param  array $instance Array which contains the various settings.
	 * @return array The array that can be fed to wp_Query to get the relevant posts
	 *
	 * @since 4.6
	 */
	public function queryArgs( $instance ) {

		$valid_sort_orders = array( 'date', 'title', 'comment_count', 'rand' );
		if ( isset( $instance['sort_by'] ) && in_array( $instance['sort_by'], $valid_sort_orders, true ) ) {
			$sort_by = $instance['sort_by'];
		} else {
			$sort_by = 'date';
		}
		$sort_order = ( isset( $instance['asc_sort_order'] ) && $instance['asc_sort_order'] ) ? 'ASC' : 'DESC';

		// Get array of post info.
		$args = array(
			'orderby'             => $sort_by,
			'order'               => $sort_order,
			'ignore_sticky_posts' => 1, // Make sure we do not get stickies out of order.
		);

		$non_default_valid_status = array(
			'publish',
			'future',
			'publish,future',
			'private',
			'private,publish',
			'private,publish,future',
		);
		if ( isset( $instance['status'] ) && in_array( $instance['status'], $non_default_valid_status, true ) ) {
			$args['post_status'] = $instance['status'];
		}

		if ( isset( $instance['num'] ) ) {
			$args['showposts'] = (int) $instance['num'];
		}

		if ( isset( $instance['offset'] ) && ( (int) $instance['offset'] > 1 ) ) {
			$args['offset'] = (int) $instance['offset'] - 1;
		}
		if ( isset( $instance['cat'] ) ) {
			if ( isset( $instance['no_cat_childs'] ) && $instance['no_cat_childs'] ) {
				$args['category__in'] = (int) $instance['cat'];
			} else {
				$args['cat'] = (int) $instance['cat'];
			}
		}

		if ( is_singular() && isset( $instance['exclude_current_post'] ) && $instance['exclude_current_post'] ) {
			$args['post__not_in'] = array( get_the_ID() );
		}

		if ( isset( $instance['hideNoThumb'] ) && $instance['hideNoThumb'] ) {
			$args = array_merge( $args, array(
				'meta_query' => array(
					array(
						'key'     => '_thumbnail_id',
						'compare' => 'EXISTS',
					),
				),
			) );
		}

		return $args;
	}

	/**
	 * Calculate the HTML of the title based on the widget settings
	 *
	 * @param  string $before_title The sidebar configured HTML that should come
	 *                              before the title itself.
	 * @param  string $after_title The sidebar configured HTML that should come
	 *                              after the title itself.
	 * @param  array  $instance Array which contains the various settings.
	 * @return string The HTML for the title area
	 *
	 * @since 4.6
	 */
	public function titleHTML( $before_title, $after_title, $instance ) {
		$ret = '';

		if ( ! isset( $instance['cat'] ) ) {
			$instance['cat'] = 0;
		}

		// If no title, use the name of the category.
		if ( ! isset( $instance['title'] ) || ! $instance['title'] ) {
			$instance['title'] = '';
			if ( 0 !== $instance['cat'] ) {
				$category_info = get_category( $instance['cat'] );
				if ( $category_info && ! is_wp_error( $category_info ) ) {
					$instance['title'] = $category_info->name;
				} else {
					$instance['cat'] = 0; // For further processing treat it like "all categories".
					$instance['title'] = __( 'Recent Posts', 'category-posts' );
				}
			} else {
				$instance['title'] = __( 'Recent Posts', 'category-posts' );
			}
		}

		if ( ! ( isset( $instance['hide_title'] ) && $instance['hide_title'] ) ) {
			$ret = $before_title;
			if ( isset( $instance['is_shortcode'] ) ) {
				$title = esc_html( $instance['title'] );
			} else {
				$title = apply_filters( 'widget_title', $instance['title'] );
			}

			if ( isset( $instance['title_link'] ) && $instance['title_link'] ) {
				if ( 0 !== $instance['cat'] ) {
					$ret .= '<a href="' . get_category_link( $instance['cat'] ) . '">' . $title . '</a>';
				} elseif ( isset( $instance['title_link_url'] ) && $instance['title_link_url'] ) {
					$ret .= '<a href="' . esc_url( $instance['title_link_url'] ) . '">' . $title . '</a>';
				} else {
					$ret .= '<a href="' . esc_url( $this->blog_page_url() ) . '">' . $title . '</a>';
				}
			} else {
				$ret .= $title;
			}

			$ret .= $after_title;
		}

		return $ret;
	}

	/**
	 * Get the URL of the blog page or home page if no explicit blog page is defined.
	 *
	 * @return string The URL of the blog page
	 *
	 * @since 4.8
	 */
	private function blog_page_url() {

		$blog_page = get_option( 'page_for_posts' );
		if ( $blog_page ) {
			$url = get_permalink( $blog_page );
		} else {
			$url = home_url();
		}

		return $url;
	}

	/**
	 * Calculate the HTML of the footer based on the widget settings
	 *
	 * @param  array $instance Array which contains the various settings.
	 * @return string The HTML for the footer area
	 *
	 * @since 4.6
	 */
	public function footerHTML( $instance ) {

		$ret = '';
		$url = '';
		$text = '';

		if ( isset( $instance['footer_link'] ) ) {
			$url = $instance['footer_link'];
		}

		if ( isset( $instance['footer_link_text'] ) ) {
			$text = $instance['footer_link_text'];
		}

		// if url is set, but no text, just use the url as text.
		if ( empty( $text ) && ! empty( $url ) ) {
			$text = $url;
		}

		// if no url is set but just text, assume the url should be to the relevant archive page
		// category archive for categories filter and home page or blog page when "all categories"
		// is used.
		if ( ! empty( $text ) && empty( $url ) ) {
			if ( isset( $instance['cat'] ) && ( 0 !== $instance['cat'] ) && ( null !== get_category( $instance['cat'] ) ) ) {
				$url = get_category_link( $instance['cat'] );
			} else {
				$url = $this->blog_page_url();
			}
		}

		if ( ! empty( $url ) ) {
			$ret .= '<a class="cat-post-footer-link" href="' . esc_url( $url ) . '">' . esc_html( $text ) . '</a>';
		}

		return $ret;
	}

	/**
	 * Current post item date string based on the format requested in the settings
	 *
	 * @param  array $instance Array which contains the various settings.
	 * @param  bool  $everything_is_link Indicates whether the return string should avoid links.
	 *
	 * @since 4.8
	 */
	public function itemDate( $instance, $everything_is_link ) {
		$ret = '';

		if ( ! isset( $instance['preset_date_format'] ) ) {
			$preset_date_format = 'other';
		} else {
			$preset_date_format = $instance['preset_date_format'];
		}

		switch ( $preset_date_format ) {
			case 'sitedateandtime':
				$date = get_the_time( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) );
				break;
			case 'sitedate':
				$date = get_the_time( get_option( 'date_format' ) );
				break;
			case 'sincepublished':
				$date = human_time_diff( get_the_time( 'U' ), current_time( 'timestamp' ) );
				break;
			default:
				if ( isset( $instance['date_format'] ) && strlen( trim( $instance['date_format'] ) ) > 0 ) {
					$date_format = $instance['date_format'];
				} else {
					$date_format = 'j M Y';
				}
				$date = get_the_time( $date_format );
				break;
		}
		$ret .= '<span class="cat-post-date">';
		if ( isset( $instance['date_link'] ) && $instance['date_link'] && ! $everything_is_link ) {
			$ret .= '<a href="' . \get_the_permalink() . '">';
		}
		$ret .= $date;

		if ( isset( $instance['date_link'] ) && $instance['date_link'] && ! $everything_is_link ) {
			$ret .= '</a>';
		}
		$ret .= '</span>';
		return $ret;
	}


	/**
	 * Calculate the HTML for showing the thumb of a post item.
	 * Expected to be called from a loop with globals properly set.
	 *
	 * @param  array $instance Array which contains the various settings.
	 * @param  bool  $no_link  indicates whether the thumb should be wrapped in a link or a span.
	 * @return string The HTML for the thumb related to the post
	 *
	 * @since 4.6
	 */
	public function itemThumb( $instance, $no_link ) {
		$ret = '';

		if ( ( isset( $instance['default_thunmbnail'] ) && ( 0 !== $instance['default_thunmbnail'] ) ) || has_post_thumbnail() ) {
			$class              = '';
			$use_css_cropping   = isset( $this->instance['use_css_cropping'] ) && $this->instance['use_css_cropping'];
			$disable_css        = isset( $instance['disable_css'] ) && $instance['disable_css'];

			if ( isset( $this->instance['thumb_hover'] ) && ! $disable_css ) {
				$class = 'class="cat-post-thumbnail cat-post-' . $instance['thumb_hover'] . '"';
			} else {
				$class = 'class="cat-post-thumbnail"';
			}

			$title_args = array( 'echo' => false );

			if ( $no_link ) {
				$ret .= '<span ' . $class . '>';
			} else {
				$ret .= '<a ' . $class . ' href="' . get_the_permalink() . '" title="' . the_title_attribute( $title_args ) . '">';
			}

			$ret .= $this->the_post_thumbnail( array( $this->instance['thumb_w'], $this->instance['thumb_h'] ) );

			if ( $no_link ) {
				$ret .= '</span>';
			} else {
				$ret .= '</a>';
			}
		}

		return $ret;
	}

	/**
	 * Current post item categories string
	 *
	 * @param  array $instance Array which contains the various settings.
	 * @param  bool  $everything_is_link Indicates whether the return string should avoid links.
	 *
	 * @since 4.8
	 */
	public function itemCategories( $instance, $everything_is_link ) {

		$ret = '<span class="cat-post-tax-category">';
		$cat_ids = wp_get_post_categories( get_the_ID(), array( 'number' => 0 ) );
		foreach ( $cat_ids as $cat_id ) {
			if ( $everything_is_link ) {
				$ret .= ' ' . get_cat_name( $cat_id );
			} else {
				$ret .= " <a href='" . get_category_link( $cat_id ) . "'>" . get_cat_name( $cat_id ) . '</a>';
			}
		}
		$ret .= '</span>';
		return $ret;
	}

	/**
	 * Current post item tags string
	 *
	 * @param  array $instance Array which contains the various settings.
	 * @param  bool  $everything_is_link Indicates whether the return string should avoid links.
	 *
	 * @since 4.8
	 */
	public function itemTags( $instance, $everything_is_link ) {

		$ret = '<span class="cat-post-tax-post_tag">';
		$tag_ids = wp_get_post_tags( get_the_ID(), array( 'number' => 0 ) );
		foreach ( $tag_ids as $tag_id ) {
			if ( $everything_is_link ) {
				$ret .= ' ' . $tag_id->name;
			} else {
				$ret .= " <a href='" . get_tag_link( $tag_id->term_id ) . "'>" . $tag_id->name . '</a>';
			}
		}
		$ret .= '</span>';
		return $ret;
	}

	/**
	 * Current post item comment number string
	 *
	 * @param  array $instance Array which contains the various settings.
	 * @param  bool  $everything_is_link Indicates whether the return string should avoid links.
	 *
	 * @since 4.8
	 */
	public function itemCommentNum( $instance, $everything_is_link ) {
		global $post;

		$ret = '<span class="cat-post-comment-num">';

		if ( $everything_is_link ) {
			$ret .= '(' . \get_comments_number() . ')';
		} else {
			$link = sprintf(
				'<a href="%1$s" title="%2$s">(%3$d)</a>',
				esc_url( get_comments_link( $post->ID ) ),
				esc_attr( sprintf( __( '(%d) comments to this post' ), get_comments_number() ) ),
				get_comments_number()
			);
			$ret .= $link;
		}

		$ret .= '</span>';
		return $ret;
	}

	/**
	 * Current post item author string
	 *
	 * @param  array $instance Array which contains the various settings.
	 * @param  bool  $everything_is_link Indicates whether the return string should avoid links.
	 *
	 * @since 4.8
	 */
	public function itemAuthor( $instance, $everything_is_link ) {

		$ret = '<span class="cat-post-author">';

		if ( $everything_is_link ) {
			$ret .= get_the_author();
		} else {
			$link = get_the_author_posts_link();
			$ret .= $link;
		}
		$ret .= '</span>';
		return $ret;
	}

	/**
	 * Current post item excerpt string
	 *
	 * @param  array $instance Array which contains the various settings.
	 * @param  bool  $everything_is_link Indicates whether the return string should avoid links.
	 *
	 * @since 4.8
	 */
	public function itemExcerpt( $instance, $everything_is_link ) {
		global $post;

		// use the_excerpt filter to get the "normal" excerpt of the post
		// then apply our filter to let users customize excerpts in their own way.
		if ( isset( $instance['excerpt_length'] ) && ( $instance['excerpt_length'] > 0 ) ) {
			$length = (int) $instance['excerpt_length'];
		} else {
			$length = 55; // Use the wordpress default.
		}

		if ( ! isset( $instance['excerpt_filters'] ) || $instance['excerpt_filters'] ) { // pre 4.7 widgets has filters on.
			$excerpt = apply_filters( 'the_excerpt', \get_the_excerpt() );
		} else { // if filters off replicate functionality of core generating excerpt.
			$more_text = '[&hellip;]';
			if ( isset( $instance['excerpt_more_text'] ) && $instance['excerpt_more_text'] ) {
				$more_text = ltrim( $instance['excerpt_more_text'] );
			}

			if ( $everything_is_link ) {
				$excerpt_more_text = ' <span class="cat-post-excerpt-more">' . $more_text . '</span>';
			} else {
				$excerpt_more_text = ' <a class="cat-post-excerpt-more" href="' . get_permalink() . '" title="' . sprintf( __( 'Continue reading %s' ), get_the_title() ) . '">' . $more_text . '</a>';
			}
			if ( '' === $post->post_excerpt ) {
				$text = get_the_content( '' );
				$text = strip_shortcodes( $text );
				$excerpt = \wp_trim_words( $text, $length, $excerpt_more_text );
				// adjust html output same way as for the normal excerpt,
				// just force all functions depending on the_excerpt hook.
				$excerpt = shortcode_unautop( wpautop( convert_chars( convert_smilies( wptexturize( $excerpt ) ) ) ) );
			} else {
				$excerpt = $post->post_excerpt . $excerpt_more_text;
				$excerpt = shortcode_unautop( wpautop( convert_chars( convert_smilies( wptexturize( $excerpt ) ) ) ) );
			}
		}
		$ret = apply_filters( 'cpw_excerpt', $excerpt, $this );
		return $ret;
	}

	/**
	 * Current post item title string
	 *
	 * @param  array $instance Array which contains the various settings.
	 * @param  bool  $everything_is_link Indicates whether the return string should avoid links.
	 *
	 * @since 4.8
	 */
	public function itemTitle( $instance, $everything_is_link ) {

		$ret = '';

		if ( $everything_is_link ) {
			$ret .= '<span class="cat-post-title">' . get_the_title() . '</span>';
		} else {
			$ret .= '<a class="cat-post-title"';
			$ret .= ' href="' . get_the_permalink() . '" rel="bookmark">' . get_the_title();
			$ret .= '</a>';
		}
		return $ret;
	}

	/**
	 * Calculate the HTML for a post item based on the widget settings and post.
	 * Expected to be called in an active loop with all the globals set.
	 *
	 * @param  array        $instance Array which contains the various settings.
	 * @param  null|integer $current_post_id If on singular page specifies the id of
	 *                      the post, otherwise null.
	 * @return string The HTML for item related to the post
	 *
	 * @since 4.6
	 */
	public function itemHTML( $instance, $current_post_id ) {
		global $post;

		$everything_is_link = isset( $instance['everything_is_link'] ) && $instance['everything_is_link'];
		$wrap = isset( $instance['text_do_not_wrap_thumb'] ) && $instance['text_do_not_wrap_thumb'];

		$template = '';
		if ( isset( $instance['template'] ) ) {
			$template = $instance['template'];
		} else {
			$template = convert_settings_to_template( $instance );
		}
		$ret = '<li ';

		// Current post.
		if ( $current_post_id === $post->ID ) {
			$ret .= "class='cat-post-item cat-post-current'";
		} else {
			$ret .= "class='cat-post-item'";
		}
		$ret .= '>'; // close the li opening tag.

		if ( $everything_is_link ) {
			$ret .= '<a class="cat-post-everything-is-link" href="' . get_the_permalink() . '" title="">';
		}

		// Try to do smart formatting for floating thumb based on its location.
		if ( $wrap ) {
			if( preg_match( '#(\%thumb\%)#', $template ) && ! preg_match( '#(\%thumb\%$)#', $template ) ) {
				$thumb_flex = explode( '%thumb%', $template );
				if( count( $thumb_flex ) == 1) {
					$template = '<div class="cat-post-do-not-wrap-thumbnail">%thumb%<div>' . $thumb_flex[0] . '</div></div>';
				}					
				if( count( $thumb_flex ) == 2) {
					$template =  $thumb_flex[0] . '<div class="cat-post-do-not-wrap-thumbnail">%thumb%<div>' . $thumb_flex[1] . '</div></div>';
				}
			}
		}

		// Post details (Template).
		$widget = $this;
		$template_res = preg_replace_callback( get_template_regex(), function ( $matches ) use ( $widget, $instance, $everything_is_link ) {
			switch ( $matches[0] ) {
				case '%title%':
					return $widget->itemTitle( $instance, $everything_is_link );
				case '%author%':
					return $widget->itemAuthor( $instance, $everything_is_link );
				case '%commentnum%':
					return $widget->itemCommentNum( $instance, $everything_is_link );
				case '%date%':
					return $widget->itemDate( $instance, $everything_is_link );
				case '%thumb%':
					return $widget->itemThumb( $instance, $everything_is_link );
				case '%post_tag%':
					return $widget->itemTags( $instance, $everything_is_link );
				case '%category%':
					return $widget->itemCategories( $instance, $everything_is_link );
				case '%excerpt%':
					return $widget->itemExcerpt( $instance, $everything_is_link );
				default:
					return $matches[0];
			}
		}, $template );

		// Replace empty line with closing and opening DIV.
		$template_res = trim( $template_res );
		$template_res = str_replace( "\n\r", '</div><div>', $template_res ); // in widget areas
		$template_res = str_replace( "\n\n", '</div><div>', $template_res ); // as shortcode
		$template_res = '<div>' . $template_res . '</div>';

		// replace new lines with spaces.
		$template_res = str_replace( "\n\r", ' ', $template_res ); // in widget areas
		$template_res = str_replace( "\n\n", ' ', $template_res ); // as shortcode

		$ret .= $template_res;

		if ( $everything_is_link ) {
			$ret .= '</a>';
		}

		$ret .= '</li>';
		return $ret;
	}

	/**
	 * Filter to set the number of words in an excerpt
	 *
	 * @param  int $length The number of words as configured by wordpress core or set by previous filters.
	 * @return int The number of words configured for the widget,
	 *             or the $length parameter if it is not configured or garbage value.
	 *
	 * @since 4.6
	 */
	public function excerpt_length_filter( $length ) {
		if ( isset( $this->instance['excerpt_length'] ) && $this->instance['excerpt_length'] > 0 ) {
			$length = $this->instance['excerpt_length'];
		}
		return $length;
	}

	/**
	 * Set the proper excerpt filters based on the settings
	 *
	 * @param  array $instance widget settings.
	 * @return void
	 *
	 * @since 4.6
	 */
	public function setExcerpFilters( $instance ) {

		if ( isset( $instance['excerpt'] ) && $instance['excerpt'] ) {

			// Excerpt length filter.
			if ( isset( $instance['excerpt_length'] ) && ( (int) $instance['excerpt_length'] ) > 0 ) {
				add_filter( 'excerpt_length', array( $this, 'excerpt_length_filter' ) );
			}

			if ( isset( $instance['excerpt_more_text'] ) && ( '' !== ltrim( $instance['excerpt_more_text'] ) ) ) {
				add_filter( 'excerpt_more', array( $this, 'excerpt_more_filter' ) );
			}

			if ( isset( $instance['excerpt_allow_html'] ) ) {
				remove_filter( 'get_the_excerpt', 'wp_trim_excerpt' );
				add_filter( 'the_excerpt', array( $this, 'allow_html_excerpt' ) );
			} else {
				add_filter( 'the_excerpt', array( $this, 'apply_the_excerpt' ) );
			}
		}
	}

	/**
	 * Remove the excerpt filter
	 *
	 * @param  array $instance widget settings.
	 * @return void
	 *
	 * @since 4.6
	 */
	public function removeExcerpFilters( $instance ) {
		remove_filter( 'excerpt_length', array( $this, 'excerpt_length_filter' ) );
		remove_filter( 'excerpt_more', array( $this, 'excerpt_more_filter' ) );
		add_filter( 'get_the_excerpt', 'wp_trim_excerpt' );
		remove_filter( 'the_excerpt', array( $this, 'allow_html_excerpt' ) );
		remove_filter( 'the_excerpt', array( $this, 'apply_the_excerpt' ) );
	}

	/**
	 * The main widget display controller
	 *
	 * Called by the sidebar processing core logic to display the widget.
	 *
	 * @param array $args An array containing the "environment" setting for the widget,
	 *                     namely, the enclosing tags for the widget and its title.
	 * @param array $instance The settings associate with the widget.
	 *
	 * @since 4.1
	 */
	public function widget( $args, $instance ) {
		
		if ( 0 === count( $instance ) ) {
			$instance = default_settings();
		}		

		extract( $args );
		$this->instance = $instance;

		$args = $this->queryArgs( $instance );
		$cat_posts = new \WP_Query( $args );

		if ( ! isset( $instance['hide_if_empty'] ) || ! $instance['hide_if_empty'] || $cat_posts->have_posts() ) {
			echo $before_widget; // Xss ok. This is how widget actually expected to behave.
			echo $this->titleHTML( $before_title, $after_title, $instance );

			$current_post_id = null;
			if ( is_singular() ) {
				$current_post_id = get_the_ID();
			}

			if ( ! ( isset( $instance['is_shortcode'] ) && $instance['is_shortcode'] ) ) { // the internal id is needed only for widgets.
				echo '<ul id="' . esc_attr( WIDGET_BASE_ID ) . '-' . esc_attr( $this->number ) . '-internal" class="' . esc_attr( WIDGET_BASE_ID ) . '-internal' . "\">\n";
			} else {
				echo '<ul>';
			}

			// set widget filters.
			if ( ! isset( $instance['excerpt_filters'] ) || $instance['excerpt_filters'] ) { // pre 4.7 widgets has filters on.
				$this->setExcerpFilters( $instance );
			}

			while ( $cat_posts->have_posts() ) {
				$cat_posts->the_post();
				echo $this->itemHTML( $instance, $current_post_id );
			}
			echo "</ul>\n";

			echo $this->footerHTML( $instance );
			echo $after_widget; // Xss ok. This is how widget actually expected to behave.

			// remove widget filters.
			if ( ! isset( $instance['excerpt_filters'] ) || $instance['excerpt_filters'] ) { // pre 4.7 widgets has filters on.
				$this->removeExcerpFilters( $instance );
			}

			wp_reset_postdata();

			$use_css_cropping = isset( $this->instance['use_css_cropping'] ) && $this->instance['use_css_cropping'];
			$empty_dimensions = empty( $this->instance['thumb_w'] ) || empty( $this->instance['thumb_h'] );
			$thumb = isset( $this->instance['template'] ) && preg_match( '/%thumb%/', $this->instance['template'] );

			if ( $use_css_cropping && ! $empty_dimensions && $thumb ) {
				// enqueue relevant scripts and parameters to perform cropping
				// once we support only 4.5+ it can be refactored to use wp_add_inline_script.
				$number = $this->number;
				// a temporary hack to handle difference in the number in a true widget
				// and the number format expected at the rest of the places.
				if ( is_numeric( $number ) ) {
					$number = WIDGET_BASE_ID . '-' . $number;
				}

				// add Javascript to change change cropped image dimensions on load and window resize.
				$thumb_w = $this->instance['thumb_w'];
				$thumb_h = $this->instance['thumb_h'];
				add_filter( 'cpw_crop_widgets',
					function ( $a ) use ( $number, $thumb_w, $thumb_h ) {
						$a[ $number ] = $thumb_w / $thumb_h;
						return $a;
					}
				);
				wp_enqueue_script( 'jquery' ); // just in case the theme or other plugins didn't enqueue it.
				add_action('wp_footer',function () use ($number,$instance) { __NAMESPACE__ . '\\' . change_cropped_image_dimensions($number,$instance);},100);
			}
		}
	}

	/**
	 * Update the options.
	 *
	 * @param  array $new_instance The new settings of the widget.
	 * @param  array $old_instance The current settings of the widget.
	 * @return array
	 */
	public function update( $new_instance, $old_instance ) {

		$new_instance['title'] = sanitize_text_field( $new_instance['title'] );  // sanitize the title like core widgets do.
		if ( ! isset( $new_instance['excerpt_filters'] ) ) {
			$new_instance['excerpt_filters'] = '';
		}
		if ( current_user_can( 'unfiltered_html' ) ) {
			$instance['text'] = $new_instance['template'];
		} else {
			$instance['text'] = wp_kses_post( $new_instance['template'] );
		}
		return $new_instance;
	}

	/**
	 * Output the title panel of the widget configuration form.
	 *
	 * @param  array $instance The widget's settings.
	 * @return void
	 *
	 * @since 4.6
	 */
	public function formTitlePanel( $instance ) {
		if ( isset( $instance['cat'] ) ) {
			$cat = $instance['cat'];
		} else {
			$cat = 0;
		}

		$hide_title = false;
		if ( isset( $instance['hide_title'] ) && $instance['hide_title'] ) {
			$hide_title = true;
		}
?>
	<h4 data-panel="title"><?php esc_html_e( 'Title', 'category-posts' ); ?></h4>
	<div>
		<?php echo $this->get_checkbox_block_html( $instance, 'hide_title', esc_html__( 'Hide title', 'category-posts' ), false, true ); ?>
		<div class="cpwp_ident categoryposts-data-panel-title-settings" <?php if ( $hide_title ) { echo 'style="display:none"'; } ?>>
			<?php echo $this->get_text_input_block_html( $instance, 'title', esc_html__( 'Title', 'category-posts' ), '', __( 'Recent Posts', 'category-posts' ), true ); ?>
			<?php echo $this->get_checkbox_block_html( $instance, 'title_link', esc_html__( 'Make widget title link', 'category-posts' ), false, 0 !== $cat ); ?>
				<?php echo $this->get_text_input_block_html( $instance, 'title_link_url', esc_html__( 'Title link URL', 'category-posts' ), '', '', 0 === $cat ); ?>
			</div>
		</div>
<?php
	}

	/**
	 * Output the filter panel of the widget configuration form.
	 *
	 * @param  array $instance The parameters configured for the widget.
	 * @return void
	 *
	 * @since 4.6
	 */
	public function formFilterPanel( $instance ) {
		$instance = wp_parse_args( (array) $instance, array( 'cat' => 0 ) );
		$cat = $instance['cat'];
?>
	<h4 data-panel="filter"><?php esc_html_e( 'Filter', 'category-posts' ); ?></h4>
	<div>
		<p>
			<label>
				<?php esc_html_e( 'Category', 'category-posts' ); ?>:
				<?php
					wp_dropdown_categories( array(
						'show_option_all' => __( 'All categories', 'category-posts' ),
						'hide_empty'      => 0,
						'name'            => $this->get_field_name( 'cat' ),
						'selected'        => $instance['cat'],
						'class'           => 'categoryposts-data-panel-filter-cat',
					) );
				?>
			</label>
		</p>
		<?php
			echo $this->get_checkbox_block_html( $instance, 'no_cat_childs', esc_html__( 'Exclude child categories', 'category-posts' ), false, ! empty( $instance['cat'] ) );
			echo $this->get_select_block_html( $instance, 'status', esc_html__( 'Status', 'category-posts' ), array(
				'default'                => esc_html__( 'WordPress Default', 'category-posts' ),
				'publish'                => esc_html__( 'Published', 'category-posts' ),
				'future'                 => esc_html__( 'Scheduled', 'category-posts' ),
				'private'                => esc_html__( 'Private', 'category-posts' ),
				'publish,future'         => esc_html__( 'Published or Scheduled', 'category-posts' ),
				'private,publish'        => esc_html__( 'Published or Private', 'category-posts' ),
				'private,future'         => esc_html__( 'Private or Scheduled', 'category-posts' ),
				'private,publish,future' => esc_html__( 'Published, Private or Scheduled', 'category-posts' ),
			), 'default', true );
			echo $this->get_number_input_block_html( $instance, 'num', esc_html__( 'Number of posts to show', 'category-posts' ), get_option( 'posts_per_page' ), 1, '', '', true );
			echo $this->get_number_input_block_html( $instance, 'offset', esc_html__( 'Start with post', 'category-posts' ), 1, 1, '', '', true );
			echo $this->get_select_block_html( $instance, 'sort_by', esc_html__( 'Sort by', 'category-posts' ), array(
				'date'          => esc_html__( 'Date', 'category-posts' ),
				'title'         => esc_html__( 'Title', 'category-posts' ),
				'comment_count' => esc_html__( 'Number of comments', 'category-posts' ),
				'rand'          => esc_html__( 'Random', 'category-posts' ),
			), 'date', true );
			echo $this->get_checkbox_block_html( $instance, 'asc_sort_order', esc_html__( 'Reverse sort order (ascending)', 'category-posts' ), false, true );
			echo $this->get_checkbox_block_html( $instance, 'exclude_current_post', esc_html__( 'Exclude current post', 'category-posts' ), false, true );
			echo $this->get_checkbox_block_html( $instance, 'hideNoThumb', esc_html__( 'Exclude posts which have no thumbnail', 'category-posts' ), false, true );
			?>
		</div>
<?php
	}

	/**
	 * Generate the wrapper P around a form input element
	 *
	 * @since 4.8
	 * @param string $html    The HTML to wrap.
	 * @param string $key     The key to use as the prefix to the class.
	 * @param bool   $visible Indicates if the element should be visible when rendered.
	 *
	 * @return string HTML with P element contaning the html being passed with class based on the key
	 *                and style set to display:none if visibility is off.
	 */
	private function get_wrap_block_html( $html, $key, $visible ) {

		$cl = ' class="' . __NAMESPACE__ . '-' . esc_attr( $key ) . '"';

		$style = '';
		if ( ! $visible ) {
			$style = ' style="display:none"';
		}
		$ret = '<p' . $cl . $style . ">\n" . $html . "</p>\n";

		return $ret;
	}

	/**
	 * Generate a form P element containing a select element
	 *
	 * @since 4.8
	 * @param array  $instance  The instance.
	 * @param string $key       The key in the instance array.
	 * @param string $label     The label to display and associate with the input.
	 * @param array  $list      An array of pairs value (index) => label to be used for the options.
	 *                          The labels are expected to be html escaped.
	 * @param int    $default   The value to use if the key is not set in the instance.
	 * @param bool   $visible   Indicates if the element should be visible when rendered.
	 *
	 * @return string HTML a P element contaning the select, its label, class based on the key
	 *                and style set to display:none if visibility is off.
	 */
	private function get_select_block_html( $instance, $key, $label, $list, $default, $visible ) {
		$value = $default;

		if ( isset( $instance[ $key ] ) ) {
			$value = $instance[ $key ];
		}

		if ( ! array_key_exists( $value, $list ) ) {
			$value = $default;
		}

		$ret = '<label for="' . $this->get_field_id( $key ) . "\">\n" .
					$label .
				"</label>\n" .
				'<select id="' . $this->get_field_id( $key ) . '" name="' . $this->get_field_name( $key ) . '"  autocomplete="off">' . "\n";
		foreach ( $list as $v => $l ) {
			$ret .= '<option value="' . esc_attr( $v ) . '" ' . selected( $v, $value, false ) . '>' . $l . "</option>\n";
		}
		$ret .= "</select>\n";

		return $this->get_wrap_block_html( $ret, $key, $visible );
	}

	/**
	 * Generate a form P element containing a textarea input
	 *
	 * @since 4.8
	 * @param array  $instance      The instance.
	 * @param string $key           The key in the instance array.
	 * @param string $label         The label to display and associate with the input (should be html escaped).
	 * @param int    $default       The value to use if the key is not set in the instance.
	 * @param string $placeholder   The placeholder to use in the input (should be attribute escaped).
	 * @param bool   $visible       Indicates if the element should be visible when rendered.
	 * @param int    $num_rows      Number of rows.
	 *
	 * @return string HTML a P element containing the input, its label, class based on the key
	 *                and style set to display:none if visibility is off.
	 */
	private function get_textarea_html( $instance, $key, $label, $default, $placeholder, $visible, $num_rows ) {

		$value = $default;

		if ( isset( $instance[ $key ] ) ) {
			$value = $instance[ $key ];
		}

		$ret = '<label for="' . esc_attr( $this->get_field_id( $key ) ) . '">' . $label . '</label>' .
					'<textarea rows="' . esc_attr( $num_rows ) . '" placeholder="' . $placeholder . '" id="' . esc_attr( $this->get_field_id( $key ) ) . '" name="' . esc_attr( $this->get_field_name( $key ) ) . '" autocomplete="off">' . esc_textarea( $value ) . '</textarea>';

		return $this->get_wrap_block_html( $ret, $key, $visible );
	}

	/**
	 * Generate a form P element containing a text input
	 *
	 * @since 4.8
	 * @param array  $instance  The instance.
	 * @param string $key       The key in the instance array.
	 * @param string $label     The label to display and associate with the input.
	 *                          Should be html escaped.
	 * @param int    $default   The value to use if the key is not set in the instance.
	 * @param string $placeholder The placeholder to use in the input. should be attribute escaped.
	 * @param bool   $visible   Indicates if the element should be visible when rendered.
	 *
	 * @return string HTML a P element contaning the input, its label, class based on the key
	 *                and style set to display:none if visibility is off.
	 */
	private function get_text_input_block_html( $instance, $key, $label, $default, $placeholder, $visible ) {

		$value = $default;

		if ( isset( $instance[ $key ] ) ) {
			$value = $instance[ $key ];
		}

		$ret = '<label for="' . $this->get_field_id( $key ) . "\">\n" .
					$label .
					'<input placeholder="' . $placeholder . '" id="' . $this->get_field_id( $key ) . '" name="' . $this->get_field_name( $key ) . '" type="text" value="' . esc_attr( $value ) . '" autocomplete="off"/>' . "\n" .
				"</label>\n";

		return $this->get_wrap_block_html( $ret, $key, $visible );
	}

	/**
	 * Generate a form P element containing a number input
	 *
	 * @since 4.8
	 * @param array  $instance  The instance.
	 * @param string $key       The key in the instance array.
	 * @param string $label     The label to display and associate with the input.
	 *                          expected to be escaped.
	 * @param int    $default   The value to use if the key is not set in the instance.
	 * @param int    $min       The minimum value allowed to be input.
	 * @param int    $max       The maximum value allowed to be input.
	 * @param string $placeholder The placeholder string to be used. expected to be escaped.
	 * @param bool   $visible   Indicates if the element should be visible when rendered.
	 *
	 * @return string HTML a P element contaning the input, its label, class based on the key
	 *                and style set to display:none if visibility is off.
	 */
	private function get_number_input_block_html( $instance, $key, $label, $default, $min, $max, $placeholder, $visible ) {

		$value = $default;

		if ( isset( $instance[ $key ] ) ) {
			$value = $instance[ $key ];
		}

		$minmax = '';
		if ( '' !== $min ) {
			$minmax .= ' min="' . $min . '"';
		}
		if ( '' !== $max ) {
			$minmax .= ' max="' . $max . '"';
		}

		$ret = '<label for="' . $this->get_field_id( $key ) . "\">\n" .
					esc_html( $label ) . "\n" .
					'<input placeholder="' . $placeholder . '" id="' . esc_attr( $this->get_field_id( $key ) ) . '" name="' . esc_attr( $this->get_field_name( $key ) ) . '" class="' . esc_attr( $key ) . '" type="number"' . $minmax . ' value="' . esc_attr( $value ) . '" autocomplete="off" />' . "\n" .
				"</label>\n";

		return $this->get_wrap_block_html( $ret, $key, $visible );
	}

	/**
	 * Generate a form P element containing a checkbox input
	 *
	 * @since 4.8
	 * @param array  $instance  The instance.
	 * @param string $key       The key in the instance array.
	 * @param string $label     The label to display and associate with the checkbox.
	 *                          should be escaped string.
	 * @param bool   $default   The value to use if the key is not set in the instance.
	 * @param bool   $visible   Indicates if the element should be visible when rendered.
	 *
	 * @return string HTML a P element contaning the checkbox, its label, class based on the key
	 *                and style set to display:none if visibility is off.
	 */
	private function get_checkbox_block_html( $instance, $key, $label, $default, $visible ) {

		$value = $default;

		if ( array_key_exists( $key, $instance ) ) {
			if ( $instance[ $key ] ) {
				$value = true;
			} else {
				$value = false;
			}
		}
		$ret = '<label for="' . esc_attr( $this->get_field_id( $key ) ) . "\">\n" .
					'<input id="' . esc_attr( $this->get_field_id( $key ) ) . '" name="' . esc_attr( $this->get_field_name( $key ) ) . '" type="checkbox" ' . checked( $value, true, false ) . '/>' . "\n" .
					$label .
				"</label>\n";

		return $this->get_wrap_block_html( $ret, $key, $visible );
	}

	/**
	 * The widget configuration form back end.
	 *
	 * @param  array $instance The parameters associated with the widget.
	 * @return void
	 */
	public function form( $instance ) {
		if ( 0 === count( $instance ) ) { // new widget, use defaults.
			$instance = default_settings();
		} else { // updated widgets come from =< 4.6 excerpt filter is on.
			if ( ! isset( $instance['excerpt_filters'] ) ) {
				$instance['excerpt_filters'] = 'on';
			}
		}

		$instance = wp_parse_args( (array) $instance, array(
			'hide_post_titles'         => '',
			'excerpt'                  => '',
			'excerpt_more_text'        => '',
			'excerpt_filters'          => '',
			'date'                     => '',
			'date_format'              => '',
			'disable_css'              => '',
			'disable_font_styles'      => '',
			'hide_if_empty'            => '',
			'hide_social_buttons'      => '',
			'preset_date_format'       => 'other',
			'thumb'                    => false,
			'thumb_w'                  => get_option( 'thumbnail_size_w', 150 ),
			'thumb_h'                  => get_option( 'thumbnail_size_h', 150 ),
			'default_thunmbnail'       => 0,
			'use_css_cropping'         => true,
			'text_do_not_wrap_thumb'   => false,
		) );

		$hide_post_titles                = $instance['hide_post_titles'];
		$excerpt                         = $instance['excerpt'];
		$excerpt_more_text               = $instance['excerpt_more_text'];
		$excerpt_filters                 = $instance['excerpt_filters'];
		$date                            = $instance['date'];
		$date_format                     = $instance['date_format'];
		$disable_css                     = $instance['disable_css'];
		$disable_font_styles             = $instance['disable_font_styles'];
		$hide_if_empty                   = $instance['hide_if_empty'];
		$preset_date_format              = $instance['preset_date_format'];
		$thumb                           = ! empty( $instance['thumb'] );
		$thumb_w                         = $instance['thumb_w'];
		$thumb_h                         = $instance['thumb_h'];
		$default_thunmbnail              = $instance['default_thunmbnail'];
		$use_css_cropping                = $instance['use_css_cropping'];
		$text_do_not_wrap_thumb          = $instance['text_do_not_wrap_thumb'];

		$cat = $instance['cat'];

		if ( ! isset( $style_done ) ) { // what an ugly hack, but can't figure out how to do it nicer on 4.3.
		?>
		<style type="text/css">
		.cpwp_ident {
			color: #6A6A6A;
			background: #F1F1F1;
			padding: 5px;
		}
		.cpwp_ident > .cpwp_ident {
			border-left:5px solid #B3B3B3;
			padding: 0 10px;
		}
		.cpwp_ident > p {
			margin: 5px 0;
		}
		.cpwp_ident > label {
			line-height: 2.75;
			display: inline-block;
		}
		.cpwp_ident_top {
			margin-top:-1em;
			padding-top:1em;
		}

		.category-widget-cont input[type="number"] {
			width:5em;
			text-align:center;
		}

		.categoryposts-template-help th {
			text-align:start;
			font-weight:bold;
		}

		.categoryposts-template-help td {
			padding:2px;
		}

		.categoryPosts-template textarea {
			font-size:16px;
			line-height:20px;
		}
		</style>

		<?php
			$style_done = true;
		}
		?>

		<div class="category-widget-cont">
			<p><a target="_blank" href="http://tiptoppress.com/term-and-category-based-posts-widget/?utm_source=widget_cpw&utm_campaign=get_pro_cpw&utm_medium=form"><?php esc_html_e( 'Get the Pro version', 'category-posts' ); ?></a></p>
			<?php
			$this->formTitlePanel( $instance );
			$this->formFilterPanel( $instance );
			?>
			<h4 data-panel="details"><?php esc_html_e( 'Post details', 'category-posts' ); ?></h4>
			<div>
				<?php
				$template = '';
				if ( ! isset( $instance['template'] ) ) {
					$template = convert_settings_to_template( $instance );
				} else {
					$template = $instance['template'];
				}
				?>
				<p><?php esc_html_e( 'Displayed parts', 'category-posts' ); ?></p>
				<div class="cpwp_ident">
					<?php
					echo $this->get_textarea_html( $instance, 'template', esc_html__( 'Template', 'category-posts' ) . ' <a href="#" class="dashicons toggle-template-help dashicons-editor-help imgedit-help-toggle"><span class="screen-reader-text">' . esc_html__( 'Show template help', 'category-posts' ) . '</span></a>', $template, '', true, 8 );
					preg_match_all( get_template_regex(), $template, $matches );
					$tags = array();
					if ( ! empty( $matches[0] ) ) {
						$tags = array_flip( $matches[0] );
					}
					?>
					<div class="cat-post-template-help">
						<p><?php esc_html_e( 'The following text will be replaced with the relevant information. In addition you can use any text and html (if you have the permisions) anywhere you want', 'category-posts' ); ?>
						</p>
						<table>
							<tr>
								<th><?php esc_html_e( 'New line', 'category-posts' ); ?></th>
								<td><?php esc_html_e( 'Space', 'category-posts' ); ?></td>
							</tr>
							<tr>
								<th><?php esc_html_e( 'Empty line', 'category-posts' ); ?></th>
								<td><?php esc_html_e( 'Next line is a paragraph', 'category-posts' ); ?></td>
							</tr>
							<tr>
								<th>%title%</th>
								<td><?php esc_html_e( 'Post title', 'category-posts' ); ?></td>
							</tr>
							<tr>
								<th>%thumb%</th>
								<td><?php esc_html_e( 'Post thumbnail possibly wrapped by text', 'category-posts' ); ?></td>
							</tr>
							<tr>
								<th>%date%</th>
								<td><?php esc_html_e( 'Post publish date', 'category-posts' ); ?></td>
							</tr>
							<tr>
								<th>%excerpt%</th>
								<td><?php esc_html_e( 'Post excerpt', 'category-posts' ); ?></td>
							</tr>
							<tr>
								<th>%author%</th>
								<td><?php esc_html_e( 'Post author', 'category-posts' ); ?></td>
							</tr>
							<tr>
								<th>%commentnum%</th>
								<td><?php esc_html_e( 'The number of comments to the post', 'category-posts' ); ?></td>
							</tr>
							<tr>
								<th>%post_tag%</th>
								<td><?php esc_html_e( 'Post tags', 'category-posts' ); ?></td>
							</tr>
							<tr>
								<th>%category%</th>
								<td><?php esc_html_e( 'Post categories', 'category-posts' ); ?></td>
							</tr>
						</table>
					</div>
					<div class="cat-post-premade_templates">
						<p><label><?php esc_html_e( 'Select premade Template', 'category-posts' ); ?></label></p>
						<select>
							<option value="title"><?php esc_html_e( 'Only the title', 'category-posts' ); ?></option>
							<option value="title_excerpt"><?php esc_html_e( 'Title and Excerpt', 'category-posts' ); ?></option>
							<option value="title_thumb"><?php esc_html_e( 'Title and Thumbnail', 'category-posts' ); ?></option>
							<option value="title_thum_excerpt"><?php esc_html_e( 'Title, Thumbnail and Excerpt', 'category-posts' ); ?></option>
							<option value="everything"><?php esc_html_e( 'All with icons', 'category-posts' ); ?></option>
						</select>
						<p><button type="button" class="button"><?php esc_html_e( 'Select this template', 'category-posts' ); ?></button></p>
					</div>
				</div>

				<?php // Excerpt settings. ?>
				<div class="categoryposts-data-panel-excerpt" style="display:<?php echo ( isset( $tags['%excerpt%'] ) ) ? 'block' : 'none'; ?>">
					<p><?php esc_html_e( 'Excerpt settings', 'category-posts' ); ?></p>
					<div class="cpwp_ident">
					<?php
					echo $this->get_number_input_block_html( $instance, 'excerpt_length', esc_html__( 'Excerpt length (words):', 'category-posts' ), get_option( 'posts_per_page' ), 1, 200, '', true );
					echo $this->get_text_input_block_html( $instance, 'excerpt_more_text', esc_html__( 'Excerpt \'more\' text:', 'category-posts' ), '', esc_attr__( '...', 'category-posts' ), true );
					echo $this->get_checkbox_block_html( $instance, 'excerpt_filters', esc_html__( 'Don\'t override Themes and plugin filters', 'category-posts' ), false, true );
					?>
					</div>
				</div>
				<div class="categoryposts-data-panel-date" style="display:<?php echo ( isset( $tags['%date%'] ) ) ? 'block' : 'none'; ?>">
					<p><?php esc_html_e( 'Date format settings', 'category-posts' ); ?></p>
					<div class="cpwp_ident">
						<?php
						echo $this->get_select_block_html( $instance, 'preset_date_format', esc_html__( 'Date format', 'category-posts' ), array(
							'sitedateandtime' => esc_html__( 'Site date and time', 'category-posts' ),
							'sitedate'        => esc_html__( 'Site date', 'category-posts' ),
							'sincepublished'  => esc_html__( 'Time since published', 'category-posts' ),
							'other'           => esc_html__( 'PHP style format', 'category-posts' ),
						), 'sitedateandtime', true );
						echo $this->get_text_input_block_html( $instance, 'date_format', esc_html__( 'PHP Style Date format', 'category-posts' ), '', 'j M Y', 'other' === $preset_date_format );
						?>
					</div>
				</div>

				<?php // Thumbnail settings. ?>
				<div class="categoryposts-data-panel-thumb" style="display:<?php echo ( isset( $tags['%thumb%'] ) ) ? 'block' : 'none'; ?>">
					<p><?php esc_html_e( 'Thumbnail settings', 'category-posts' ); ?></p>
					<div class="cpwp_ident">
						<p><?php esc_html_e( 'Thumbnail dimensions (pixel)', 'category-posts' ); ?></p>
						<?php
						echo $this->get_number_input_block_html( $instance, 'thumb_w', esc_html__( 'Width:', 'category-posts' ), get_option( 'thumbnail_size_w', 150 ), 1, '', '', true );
						echo $this->get_number_input_block_html( $instance, 'thumb_h', esc_html__( 'Height:', 'category-posts' ), get_option( 'thumbnail_size_h', 150 ), 1, '', '', true );

						echo $this->get_checkbox_block_html( $instance, 'text_do_not_wrap_thumb', esc_html__( 'Do not wrap thumbnail with overflowing text', 'category-posts' ), false, true );
						echo $this->get_checkbox_block_html( $instance, 'use_css_cropping', esc_html__( 'CSS crop to requested size', 'category-posts' ), false, false );
						echo $this->get_select_block_html( $instance, 'thumb_hover', esc_html__( 'Animation on mouse hover:', 'category-posts' ), array(
							'none'  => esc_html__( 'None', 'category-posts' ),
							'dark'  => esc_html__( 'Darker', 'category-posts' ),
							'white' => esc_html__( 'Brighter', 'category-posts' ),
							'scale' => esc_html__( 'Zoom in', 'category-posts' ),
							'blur'  => esc_html__( 'Blur', 'category-posts' ),
							'icon'  => esc_html__( 'Icon', 'category-posts' ),
						), 'none', true);
						echo $this->get_select_block_html( $instance, 'show_post_format', esc_html__( 'Indicate post format and position', 'category-posts' ), array(
							'none'        => esc_html__( 'None', 'category-posts' ),
							'topleft'     => esc_html__( 'Top left', 'category-posts' ),
							'bottomleft'  => esc_html__( 'Bottom left', 'category-posts' ),
							'ceter'       => esc_html__( 'Center', 'category-posts' ),
							'topright'    => esc_html__( 'Top right', 'category-posts' ),
							'bottomright' => esc_html__( 'Bottom right', 'category-posts' ),
							'nocss'       => esc_html__( 'HTML without styling', 'category-posts' ),
						), 'none', true );
						?>
						<p>
							<label style="display:block">
								<?php esc_html_e( 'Default thumbnail: ', 'category-posts' ); ?>
							</label>
							<input type="hidden" class="default_thumb_id" id="<?php echo esc_attr( $this->get_field_id( 'default_thunmbnail' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'default_thunmbnail' ) ); ?>" value="<?php echo esc_attr( $default_thunmbnail ); ?>"/>
							<span class="default_thumb_img">
								<?php
								if ( ! $default_thunmbnail ) {
									esc_html_e( 'None', 'category-posts' );
								} else {
									$img = wp_get_attachment_image_src( $default_thunmbnail );
									echo '<img width="60" height="60" src="' . esc_url( $img[0] ) . '" />';
								}
								?>
							</span>
						</p>
						<p>
							<button type="button" class="cwp_default_thumb_select button upload-button">
								<?php esc_html_e( 'Select image', 'category-posts' ); ?>
							</button>
							<button type="button" class="cwp_default_thumb_remove button upload-button" <?php if ( ! $default_thunmbnail ) { echo 'style="display:none"'; } ?> >
								<?php esc_html_e( 'No default', 'category-posts' ); ?>
							</button>
						</p>
					</div>
				</div>
				<?php
				echo $this->get_checkbox_block_html( $instance, 'everything_is_link', esc_html__( 'Everything is a link', 'category-posts' ), false, true );
				?>
			</div>
			<h4 data-panel="general"><?php esc_html_e( 'General', 'category-posts' ); ?></h4>
			<div>
				<div class="cpwp_ident">
					<?php echo $this->get_checkbox_block_html( $instance, 'disable_css', esc_html__( 'Disable the built-in CSS', 'category-posts' ), false, true ); ?>
					<?php echo $this->get_checkbox_block_html( $instance, 'disable_font_styles', esc_html__( 'Disable only font styles', 'category-posts' ), false, true ); ?>
				</div>
				<?php echo $this->get_checkbox_block_html( $instance, 'hide_if_empty', esc_html__( 'Hide if there are no matching posts', 'category-posts' ), false, true ); ?>
			</div>
			<h4 data-panel="footer"><?php esc_html_e( 'Footer', 'category-posts' ); ?></h4>
			<div>
				<?php echo $this->get_text_input_block_html( $instance, 'footer_link_text', esc_html__( 'Footer link text', 'category-posts' ), '', '', true ); ?>
				<?php echo $this->get_text_input_block_html( $instance, 'footer_link', esc_html__( 'Footer link URL', 'category-posts' ), '', '', true ); ?>
			</div>
			<p><a href="<?php echo esc_url( get_edit_user_link() ) . '#' . __NAMESPACE__; ?>"><?php esc_html_e( 'Widget admin behaviour settings', 'category-posts' ); ?></a></p>
			<p><a target="_blank" href="<?php echo esc_url( DOC_URL ); ?>"><?php esc_html_e( 'Documentation', 'category-posts' ); ?></a></p>
			<p><a target="_blank" href="<?php echo esc_url( SUPPORT_URL ); ?>"><?php esc_html_e( 'Support', 'category-posts' ); ?></a></p>
			<p><?php echo sprintf( wp_kses( __( 'We are on <a href="%1$s">Facebook</a> and <a href="%2$s">Twitter</a>.', 'category-posts' ), array( 'a' => array( 'href' => array() ) ) ), esc_url( 'https://www.facebook.com/TipTopPress' ), esc_url( 'https://twitter.com/TipTopPress' ) ); ?></p>
		</div>
		<?php
	}
}

/*
 * Plugin action links section
 */

/**
 *  Applied to the list of links to display on the plugins page (beside the activate/deactivate links).
 *
 *  @return array of the widget links
 *
 *  @since 4.6.3
 */
add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), __NAMESPACE__ . '\add_action_links' );

/**
 * Handle the add links filter, add our links to the links displayed under the plugin_basename
 * in the plugin admin screen.
 *
 * @param array $links The current links about to be displayed.
 */
function add_action_links( $links ) {
	$pro_link = array(
		'<a target="_blank" href="' . esc_url( PRO_URL ) . '">' . esc_html__( 'Get the Pro version', 'category-posts' ) . '</a>',
	);

	$links = array_merge( $pro_link, $links );

	return $links;
}

/**
 * Register the widget.
 */
function register_widget() {
	return \register_widget( __NAMESPACE__ . '\Widget' );
}

add_action( 'widgets_init', __NAMESPACE__ . '\register_widget' );

/**
 * Output js code to handle responsive thumbnails
 *
 * @return void
 *
 * @since 4.7
 */
function change_cropped_image_dimensions($number,$widgetsettings) {	?>
	<script type="text/javascript">

		if (typeof jQuery !== 'undefined')  {

			jQuery( document ).ready(function () {

<?php // namespace. ?>
				var cwp_namespace = window.cwp_namespace || {};
				cwp_namespace.fluid_images = cwp_namespace.fluid_images || {};

				cwp_namespace.fluid_images = {

<?php // variables. ?>
					Widgets : {},
					widget : null,

<?php // class. ?>
					Span : function (_self, _imageRatio) {

<?php // variables. ?>
						this.self = _self;
						this.imageRatio = _imageRatio;
					},

<?php // class. ?>
					WidgetPosts : function (widget, ratio) {

<?php // variables. ?>
						this.Spans = {};
						this.allSpans = widget.find( '.cat-post-crop' );
						this.firstSpan = this.allSpans.first();
						this.maxSpanWidth = this.firstSpan.width();
						this.firstListItem = this.firstSpan.closest( 'li' );
						this.ratio = ratio;

						for( var i = 0; i < this.allSpans.length; i++ ){
							var imageRatio = this.firstSpan.width() / jQuery(this.allSpans[i]).find( 'img' ).height();
							this.Spans[i] = new cwp_namespace.fluid_images.Span( jQuery(this.allSpans[i]), imageRatio );
						}

<?php // functions. ?>
						this.changeImageSize = function changeImageSize() {

							this.listItemWidth = this.firstListItem.width();
							this.SpanWidth = this.firstSpan.width();

							if(this.listItemWidth < this.SpanWidth ||	<?php /* if the layout-width have not enough space to show the regular source-width */ echo "\r\n"; ?>
								this.listItemWidth < this.maxSpanWidth) {				<?php /* defined start and stop working width for the image: Accomplish only the image width will be get smaller as the source-width */ echo "\r\n"; ?>
									this.allSpans.width( this.listItemWidth );
									var spanHeight = this.listItemWidth / this.ratio;
									this.allSpans.height( spanHeight );

									for( var index in this.Spans ){
										var imageHeight = this.listItemWidth / this.Spans[index].imageRatio;
										jQuery(this.Spans[index].self).find( 'img' ).css({
											height: imageHeight,
											marginTop: -(imageHeight - spanHeight) / 2
										});
									};
							}
						}
					},
				}

<?php
				/**
				 *  The cpw_crop_widgets is an internal filter that is used
				 *  to gather the ids of the widgets to which apply cropping
				 *
				 *  For easier prevention of duplication, the widget id number should be an index
				 *  in the array while the ratio of width/height be the value
				 */
				$widgets_ids = apply_filters( 'cpw_crop_widgets', array() );
				foreach ( $widgets_ids as $num => $ratio ) {
					if($num != $number) {
						continue;
					} ?>
					cwp_namespace.fluid_images.widget = jQuery('#<?php echo esc_attr( $num ); ?>');
					cwp_namespace.fluid_images.Widgets['<?php echo esc_attr( $num ); ?>'] = new cwp_namespace.fluid_images.WidgetPosts(cwp_namespace.fluid_images.widget,<?php echo esc_attr( $ratio ); ?>);
<?php			} ?>

<?php			/* do on page load or on resize the browser window */ echo "\r\n"; ?>
				jQuery(window).on('load resize', function() {
					for (var widget in cwp_namespace.fluid_images.Widgets) {
						cwp_namespace.fluid_images.Widgets[widget].changeImageSize();
					}
				});
			});
		}
	</script>
<?php
}

/*
 * shortcode section.
 */

/**
 *  Get shortcode settings taking into account if it is being customized
 *
 *  When not customized returns the settings as stored in the meta, but when
 *  it is customized returns the setting stored in the virtual option used by the customizer
 *
 *  @param string $name The name of the shortcode to retun, empty string indicates the nameless.
 *
 *  @return array the shortcode settings if a short code exists or empty string, empty array if name not found
 *
 *  @since 4.6
 */
function shortcode_settings( $name ) {
	$meta = get_post_meta( get_the_ID(), SHORTCODE_META, true );

	if ( ! empty( $meta ) && ! is_array( reset( $meta ) ) ) {
		$meta = array( '' => $meta );  // the conversion.
	}

	if ( ! isset( $meta[ $name ] ) ) { // name do not exists? return empty array.
		return array();
	}

	$instance = $meta[ $name ];
	if ( is_customize_preview() ) {
		$o = get_option( '_virtual-' . WIDGET_BASE_ID );
		if ( is_array( $o ) ) {
			$instance = $o[ get_the_ID() ][ $name ];
		}
	}
	
	if ( isset( $instance['template'] ) && $instance['template'] ) {
		;
	} else {
		$instance['template'] = convert_settings_to_template( $instance );
	}

	return $instance;
}

/**
 *  Handle the shortcode
 *
 *  @param array  $attr Array of the attributes to the short code, none is expected.
 *  @param string $content The content enclosed in the shortcode, none is expected.
 *
 *  @return string An HTML of the "widget" based on its settings, actual or customized
 */
function shortcode( $attr, $content = null ) {
	$repository = new virtualWidgetsRepository();

	$shortcodes = $repository->getShortcodes();

	$name = '';
	if ( isset( $attr['name'] ) ) {
		$name = $attr['name'];
	}

	if ( is_singular() ) {
		if ( isset( $shortcodes[ $name ] ) ) {
			return $shortcodes[ $name ]->getHTML();
		}
	}

	return '';
}

add_shortcode( SHORTCODE_NAME, __NAMESPACE__ . '\shortcode' );

/**
 *  Find if a specific shortcode is used in a content
 *
 *  @param string $shortcode_name The name of the shortcode.
 *  @param string $content The content to look at.
 *
 *  @return array An array containing the name attributes of the shortcodes. Empty array is
 *                an indication there were no shourcodes
 *
 *  @since 4.7
 */
function shortcode_names( $shortcode_name, $content ) {

	$names = array();

	$regex_pattern = get_shortcode_regex();
	if ( preg_match_all( '/' . $regex_pattern . '/s', $content, $matches ) ) {
		foreach ( $matches[2] as $k => $shortcode ) {
			if ( SHORTCODE_NAME === $shortcode ) {
				$name = '';
				$atts = shortcode_parse_atts( $matches[3][ $k ] );
				if ( ! empty( $atts['name'] ) ) {
					$name = $atts['name'];
				}
				$names[] = $name;
			}
		}
	}

	return $names;
}

/**
 *  Organized way to have the default widget settings accessible
 *
 *  @since 4.6
 */
function default_settings() {
	return array(
		'title'                   => __( 'Recent Posts', 'category-posts' ),
		'title_link'              => false,
		'title_link_url'          => '',
		'hide_title'              => false,
		'cat'                     => 0,
		'num'                     => get_option( 'posts_per_page' ),
		'offset'                  => 1,
		'sort_by'                 => 'date',
		'status'                  => 'publish',
		'asc_sort_order'          => false,
		'exclude_current_post'    => false,
		'hideNoThumb'             => false,
		'footer_link_text'        => '',
		'footer_link'             => '',
		'thumb_w'                 => get_option( 'thumbnail_size_w', 150 ),
		'thumb_h'                 => get_option( 'thumbnail_size_h', 150 ),
		'use_css_cropping'        => true,
		'thumb_hover'             => 'none',
		'hide_post_titles'        => false,
		'excerpt_length'          => 55,
		'excerpt_more_text'       => __( '...', 'category-posts' ),
		'excerpt_filters'         => false,
		'comment_num'             => false,
		'date_link'               => false,
		'date_format'             => '',
		'disable_css'             => false,
		'disable_font_styles'     => false,
		'hide_if_empty'           => false,
		'show_post_format'        => 'none',
		'hide_social_buttons'     => '',
		'no_cat_childs'           => false,
		'everything_is_link'      => false,
		'preset_date_format'      => 'sitedateandtime',
		'template'                => "%title%\n\n%thumb%",
		'text_do_not_wrap_thumb'  => false,
	);
}

/**
 *  Manipulate the relevant meta related to the short code when a post is save
 *
 *  If A post has a short code, a meta holder is created, If it does not the meta holder is deleted
 *
 *  @param integer $pid  The post ID of the post being saved.
 *  @param WP_Post $post The post being saved.
 *  @return void
 *
 *  @since 4.6
 */
function save_post( $pid, $post ) {

	// ignore revisions and auto saves.
	if ( wp_is_post_revision( $pid ) || wp_is_post_autosave( $pid ) ) {
		return;
	}

	$meta = get_post_meta( $pid, SHORTCODE_META, true );
	if ( empty( $meta ) ) {
		$meta = array();
	}

	// check if only one shortcode format - non array of arrays, and convert it.
	if ( ! empty( $meta ) && ! is_array( reset( $meta ) ) ) {
		$meta = array( '' => $meta );  // the conversion.
	}

	$old_names = array_keys( $meta ); // keep list of current shortcode names to delete lter whatever was deleted.
	$names = shortcode_names( SHORTCODE_NAME, $post->post_content );

	// remove setting for unused names.
	$to_delete = array_diff( $old_names, $names );
	foreach ( $to_delete as $k ) {
		unset( $meta[ $k ] );
	}

	foreach ( $names as $name ) {
		if ( ! isset( $meta[ $name ] ) ) {
			$meta[ $name ] = default_settings();
		}
	}

	delete_post_meta( $pid, SHORTCODE_META );
	if ( ! empty( $meta ) ) {
		add_post_meta( $pid, SHORTCODE_META, $meta, true );
	}
}

add_action( 'save_post', __NAMESPACE__ . '\save_post', 10, 2 );

/**
 * Called on Customizer init to do related registrations.
 *
 * @param mixed $wp_customize The customizer object.
 */
function customize_register( $wp_customize ) {

	class shortCodeControl extends \WP_Customize_Control {
		public $form;
		public $title_postfix;

		public function render_content() {
			$widget_title = 'Category Posts Shortcode' . $this->title_postfix;
			?>
			<div class="widget-top">
			<div class="widget-title"><h3><?php echo esc_html( $widget_title ); ?><span class="in-widget-title"></span></h3></div>
			</div>
			<div class="widget-inside" style="display: block;">
				<div class="form">
					<div class="widget-content">
						<?php echo $this->form; ?>
					</div>
				</div>
			</div>
			<?php
		}
	}

	$args = array(
		'post_type'              => 'any',
		'post_status'            => 'any',
		'posts_per_page'         => -1,
		'update_post_term_cache' => false,
		'meta_query'             => array(
			array(
				'key'     => SHORTCODE_META,
				'compare' => 'EXISTS',
			),
		),

	);
	$posts = get_posts( $args );

	if ( count( $posts ) > 0 ) {
		$wp_customize->add_panel( __NAMESPACE__, array(
			'title'      => __( 'Category Posts Shortcode', 'category-posts' ),
			'priority'   => 300,
			'capability' => 'edit_theme_options',
		) );

		foreach ( $posts as $p ) {
			$widget = new Widget();
			$meta = get_post_meta( $p->ID, SHORTCODE_META, true );
			if ( ! is_array( $meta ) ) {
				continue;
			}

			if ( ! is_array( reset( $meta ) ) ) { // 4.6 format.
				$meta = array( '' => $meta );
			}

			foreach ( $meta as $k => $m ) {
			
				if ( isset( $m['template'] ) && $m['template'] ) {
					;
				} else {
					$m['template'] = convert_settings_to_template( $m );
				}
			
				$m = wp_parse_args( $m, default_settings() );

				if ( 0 === count( $meta ) ) { // new widget, use defaults.
					;
				} else { // updated widgets come from =< 4.6 excerpt filter is on.
					if ( ! isset( $m['excerpt_filters'] ) ) {
						$m['excerpt_filters'] = 'on';
					}
				}

				$section_title = $k;
				if ( '' === $section_title ) {
					$section_title = __( '[shortcode]', 'category-posts' );
				}

				$wp_customize->add_section( __NAMESPACE__ . '-' . $p->id . '-' . $k, array(
					'title'      => $section_title,
					'priority'   => 10,
					'capability' => 'edit_theme_options',
					'panel'      => __NAMESPACE__,
				) );

				ob_start();

				// For the form method to handle generation gracefully, the number needs to be a simple string, and the name might include other chars as well, so for simplisity md5 it.
				if ( '' !== $k ) {
					$widget->number = $p->ID . '_' . md5( $k );
				} else {
					$widget->number = $p->ID . '_';
				}

				$widget->form( $m );
				$form = ob_get_clean();
				$form = preg_replace_callback('/<(input|select|textarea)\s+.*name=("|\').*\[\w*\]\[([^\]]*)\][^>]*>/',
					function ( $matches ) use ( $p, $wp_customize, $m, $k ) {
						$setting = '_virtual-' . WIDGET_BASE_ID . '[' . $p->ID . '][' . $k . '][' . $matches[3] . ']';
						if ( ! isset( $m[ $matches[3] ] ) ) {
							$m[ $matches[3] ] = null;
						}
						$wp_customize->add_setting( $setting, array(
							'default' => $m[ $matches[3] ], // set default to current value.
							'type'    => 'option',
						) );

						return str_replace( '<' . $matches[1], '<' . $matches[1] . ' data-customize-setting-link="' . $setting . '"', $matches[0] );
					},
					$form
				);

				$args = array(
					'label'           => __( 'Layout', 'twentyfourteen' ),
					'section'         => __NAMESPACE__ . '-' . $p->id . '-' . $k,
					'form'            => $form,
					'settings'        => '_virtual-' . WIDGET_BASE_ID . '[' . $p->ID . '][' . $k . '][title]',
					'active_callback' => function () use ( $p ) {
						return is_singular() && ( get_the_ID() === $p->ID );
					},
				);

				if ( get_option( 'page_on_front' ) === $p->ID ) {
					$args['active_callback'] = function () {
						return is_front_page();
					};
				}

				$sc = new shortCodeControl(
					$wp_customize,
					'_virtual-' . WIDGET_BASE_ID . '[' . $p->ID . '][' . $k . '][title]',
					$args
				);

				if ( '' !== $k ) {
					$sc->title_postfix = ' ' . $k;
				}
				$wp_customize->add_control( $sc );
			}
		}
	}
}

add_action( 'customize_register', __NAMESPACE__ . '\customize_register' );

/**
 *  Save the virtual option used by the customizer into the proper meta values.
 *
 *  The customizer actually saves only the changed values, so a merge needs to be done.
 *  After everything is updated the virtual option is deleted to leave a clean slate
 *
 *  @return void
 *
 *  @since 4.6
 */
function customize_save_after() {
	$virtual = get_option( '_virtual-' . WIDGET_BASE_ID );

	if ( is_array( $virtual ) ) {
		foreach ( $virtual as $pid => $instance ) {
			$meta = get_post_meta( $pid, SHORTCODE_META, true );
			if ( ! empty( $meta ) && ! is_array( reset( $meta ) ) ) {
				$meta = array( '' => $meta );  // the conversion.
			}

			foreach ( $instance as $name => $new ) {
				if ( isset( $meta[ $name ] ) ) { // unlikely but maybe that short code was deleted by other session.
					$meta[ $name ] = array_merge( $meta[ $name ], $new );
				}
			}
		}
		update_post_meta( $pid, SHORTCODE_META, $meta );
	}

	delete_option( '_virtual-' . WIDGET_BASE_ID );
}

add_action( 'customize_save_after', __NAMESPACE__ . '\customize_save_after', 100 );

/*
 * tinymce related functions.
 */

/**
 *  Uninstall handler, cleanup DB from options and meta
 *
 *  @return void
 *
 *  @since 4.7
 */
function uninstall() {
	delete_option( 'widget-' . WIDGET_BASE_ID ); // delete the option storing the widget options.
	delete_post_meta_by_key( SHORTCODE_META ); // delete the meta storing the shortcode.
	delete_metadata( 'user', 0, __NAMESPACE__, '', true );  // delete all user metadata.
}

register_uninstall_hook( __FILE__, __NAMESPACE__ . 'uninstall' );

/**
 *  Register the tinymce shortcode plugin
 *
 *  @param array $plugin_array An array containing the current plugins to be used by tinymce.
 *
 *  @return array An array containing the plugins to be used by tinymce, our plugin added to the $plugin_array parameter
 *
 *  @since 4.7
 */
function mce_external_plugins( $plugin_array ) {
	if ( current_user_can( 'edit_theme_options' ) ) { // don't load the code if the user can not customize the shortcode.
		// enqueue TinyMCE plugin script with its ID.
		$meta = get_user_meta( get_current_user_id(), __NAMESPACE__, true );
		if ( is_array( $meta ) && isset( $meta['editor'] ) ) {
			;
		} else {
			$plugin_array[ __NAMESPACE__ ] = plugins_url( 'js/admin/tinymce.js?ver=' . VERSION, __FILE__ );
		}
	}

	return $plugin_array;
}

add_filter( 'mce_external_plugins', __NAMESPACE__ . '\mce_external_plugins' );

/**
 *  Register the tinymce buttons for the add shortcode
 *
 *  @param array $buttons An array containing the current buttons to be used by tinymce.
 *
 *  @return array An array containing the buttons to be used by tinymce, our button added to the $buttons parameter
 *
 *  @since 4.7
 */
function mce_buttons( $buttons ) {
	if ( current_user_can( 'edit_theme_options' ) ) { // don't load the code if the user can not customize the shortcode
		// register buttons with their id.
		$meta = get_user_meta( get_current_user_id(), __NAMESPACE__, true );
		if ( is_array( $meta ) && isset( $meta['editor'] ) ) {
			;
		} else {
			array_push( $buttons, __NAMESPACE__ );
		}
	}

	return $buttons;
}

add_filter( 'mce_buttons', __NAMESPACE__ . '\mce_buttons' );

/**
 *  Register the tinymcetranslation file
 *
 *  @param array $locales An array containing the current translations to be used by tinymce.
 *
 *  @return array An array containing the translations to be used by tinymce, our localization added to the $locale parameter
 *
 *  @since 4.7
 */
function mce_external_languages( $locales ) {
	if ( current_user_can( 'edit_theme_options' ) ) { // don't load the code if the user can not customize the shortcode.
		$meta = get_user_meta( get_current_user_id(), __NAMESPACE__, true );
		if ( is_array( $meta ) && isset( $meta['editor'] ) ) {
			;
		} else {
			$locales['cat-posts'] = plugin_dir_path( __FILE__ ) . 'tinymce_translations.php';
		}
	}

	return $locales;
}

add_filter( 'mce_external_languages', __NAMESPACE__ . '\mce_external_languages' );

/*
 * user profile related functions.
 */

add_action( 'show_user_profile', __NAMESPACE__ . '\show_user_profile' );
add_action( 'edit_user_profile', __NAMESPACE__ . '\show_user_profile' );

/**
 *  Display the user specific setting on its profile page
 *
 *  @param WP_user $user The user for which the profile page displays information.
 *
 *  @since 4.7
 */
function show_user_profile( $user ) {

	if ( ! current_user_can( 'edit_user', $user->ID ) ) {
		return;
	}

	if ( ! current_user_can( 'edit_theme_options', $user->ID ) ) {
		return;
	}

	$meta = get_the_author_meta( __NAMESPACE__, $user->ID );

	if ( empty( $meta ) ) {
		$meta = array();
	}

	$accordion = false;
	if ( isset( $meta['panels'] ) ) {
		$accordion = true;
	}

	$editor = false;
	if ( isset( $meta['editor'] ) ) {
		$editor = true;
	}
?>
	<h3 id="<?php echo __NAMESPACE__; ?>"><?php esc_html_e( 'Category Posts Widget behaviour settings', 'category-posts' ); ?></h3>

	<table class="form-table">
		<tr>
			<th><label for="<?php echo __NAMESPACE__; ?>[panels]"><?php esc_html_e( 'Open panels behavior', 'category-posts' ); ?></label></th>
			<td>
				<input type="checkbox" name="<?php echo __NAMESPACE__; ?>[panels]" id="<?php echo __NAMESPACE__; ?>[panels]" <?php checked( $accordion ); ?>>
				<label for=<?php echo __NAMESPACE__; ?>[panels]><?php esc_html_e( 'Close the curremtly open panel when opening a new one', 'category-posts' ); ?></label>
			</td>
		</tr>
		<tr>
			<th><label for="<?php echo __NAMESPACE__; ?>[editor]"><?php esc_html_e( 'Visual editor button', 'category-posts' ); ?></label></th>
			<td>
				<input type="checkbox" name="<?php echo __NAMESPACE__; ?>[editor]" id="<?php echo __NAMESPACE__; ?>[editor]" <?php checked( $editor ); ?>>
				<label for="<?php echo __NAMESPACE__; ?>[editor]"><?php esc_html_e( 'Hide the "insert shortcode" button from the editor', 'category-posts' ); ?></label>
			</td>
		</tr>
	</table>
<?php
}

add_action( 'personal_options_update', __NAMESPACE__ . '\personal_options_update' );
add_action( 'edit_user_profile_update', __NAMESPACE__ . '\personal_options_update' );

/**
 *  Handles saving user related settings as was set in the profile page.
 *
 *  @param int $user_id the ID of the user for which the data is saved..
 *
 *  @since 4.7
 */
function personal_options_update( $user_id ) {

	if ( ! current_user_can( 'edit_user', $user_id ) ) {
		return false;
	}

	if ( ! current_user_can( 'edit_theme_options', $user_id ) ) {
		return;
	}

	if ( isset( $_POST[ __NAMESPACE__ ] ) ) {
		update_user_meta( $user_id, __NAMESPACE__, wp_unslash( $_POST[ __NAMESPACE__ ] ) );
	} else {
		delete_user_meta( $user_id, __NAMESPACE__ );
	}
}

/*
 * external API.
 */

/**
 *  Class that represent a virtual widget. Each widget being created will have relevant
 *  CSS output in the header, but still requires a call for getHTML method or renderHTML
 *  to get or output the HTML
 *
 *  @since 4.7
 */
class virtualWidget {

	/**
	 * A container for all the "active" objects
	 *
	 * @var Array
	 *
	 * @since 4.7
	 */
	private static $collection = array();

	/**
	 * The identifier use as the id of the root html element when the HTML is generated.
	 *
	 * @var string
	 *
	 * @since 4.7
	 */
	private $id;

	/**
	 * A container for all the "active" objects
	 *
	 * @var string The class name to be use us the class attribute on the root html element.
	 *
	 * @since 4.7
	 */
	private $class;

	/**
	 *  Construct the virtual widget. This should happen before wp_head action with priority
	 *  10 is executed if any CSS output should be generated.
	 *
	 *  @param string $id    The identifier use as the id of the root html element when the HTML
	 *                       is generated.
	 *
	 *  @param string $class The class name to be use us the class attribute on the root html element.
	 *
	 *  @param array  $args  The setting to be applied to the widget.
	 *
	 *  @since 4.7
	 */
	public function __construct( $id, $class, $args ) {
		$this->id = $id;
		$this->class = $class;
		self::$collection[ $id ] = wp_parse_args( $args, default_settings() );
	}

	/**
	 *  Do what ever cleanup needed when the object is destroyed.
	 *
	 *  @since 4.7
	 */
	public function __destruct() {
		unset( self::$collection[ $this->id ] );
	}

	/**
	 *  Return the HTML of the widget as is generated based on the settings passed at construction time
	 *
	 *  @return string
	 *
	 *  @since 4.7
	 */
	public function getHTML() {

		$widget = new Widget();
		$widget->number = $this->id; // needed to make a unique id for the widget html element.
		ob_start();
		$args = self::$collection[ $this->id ];
		$args['is_shortcode'] = true;  // indicate that we are doing shortcode processing to outputting funtions.
		$widget->widget(array(
			'before_widget' => '',
			'after_widget'  => '',
			'before_title'  => '',
			'after_title'   => '',
		), $args);
		$ret = ob_get_clean();
		$ret = '<div id="' . esc_attr( $this->id ) . '" class="' . esc_attr( $this->class ) . '">' . $ret . '</div>';
		return $ret;
	}

	/**
	 *  Output the widget HTML
	 *
	 *  Just a wrapper that output getHTML
	 *
	 *  @return void
	 *
	 *  @since 4.7
	 */
	public function renderHTML() {
		echo $this->getHTML();
	}

	/**
	 *  Calculate the CSS rules required for the widget as is generated based on the settings passed at construction time
	 *
	 *  @param bool  $is_shortcode Indicated if rules are generated for a shortcode.
	 *  @param array $ret "returned" Collection of CSS rules.
	 *
	 *  @since 4.7
	 */
	public function getCSSRules( $is_shortcode, &$rules ) {
		$ret = array();
		$settings = self::$collection[ $this->id ];
		$widget_id = $this->id;
		if ( ! $is_shortcode ) {
			$widget_id .= '-internal';
		}
		$disable_css = isset( $settings['disable_css'] ) && $settings['disable_css'];

		if ( ! $disable_css ) { // checks if css disable is not set.

			$styles = array( // styles that should be applied to all widgets.
				'thumb_clenup'  => '.cat-post-item img {max-width: initial; max-height: initial; margin: initial;}',
				'author_clenup' => '.cat-post-author {margin-bottom: 0;}',
				'thumb'         => '.cat-post-thumbnail {margin: 5px 10px 5px 0;}',
				'item_clenup'   => '.cat-post-item:before {content: ""; clear: both;}',
			);

			if ( ! ( isset( $settings['disable_font_styles'] ) && $settings['disable_font_styles'] ) ) { // checks if disable font styles is not set.
				// add general styles which apply to font styling.
				$styles['title_font'] = '.cat-post-title {font-size: 15px;}';
				$styles['current_title_font'] = '.cat-post-current .cat-post-title {font-weight: bold; text-transform: uppercase;}';
				$styles['date_font'] = '.cat-post-date {font-size: 14px; line-height: 18px; font-style: italic; margin-bottom: 5px;}';
				$styles['comment_num_font'] = '.cat-post-comment-num {font-size: 14px; line-height: 18px;}';
			}

			/*
			 *	The twenty seventeen theme have a border between the LI elements of a widget,
			 *	so remove our border if we detect its use to avoid conflicting styling.
			 */
			if ( ! $is_shortcode && function_exists( 'twentyseventeen_setup' ) ) {
				$styles['item_style'] = '.cat-post-item {list-style: none; list-style-type: none; margin: 0;	padding: 3px 0;}';
			} else {
				$styles['item_style'] = '.cat-post-item {border-bottom: 1px solid #ccc;	list-style: none; list-style-type: none; margin: 3px 0;	padding: 3px 0;}';
				$styles['last_item_style'] = '.cat-post-item:last-child {border-bottom: none;}';
			}

			// everything link related styling
			// if we are dealing with "everything is a link" option, we need to add the clear:both to the a element, not the div.
			if ( isset( $settings['everything_is_link'] ) && $settings['everything_is_link'] ) {
				$styles['after_item'] = '.cat-post-item a:after {content: ""; display: table;	clear: both;}';
			} else {
				$styles['after_item'] = '.cat-post-item:after {content: ""; display: table;	clear: both;}';
			}

			// add post format css if needed.
			if ( isset( $settings['template'] ) && preg_match( '/%thumb%/', $settings['template'] ) ) {
				if ( ! isset( $settings['show_post_format'] ) || ( ( 'none' !== $settings['show_post_format'] ) && ( 'nocss' !== $settings['show_post_format'] ) ) ) {
					static $fonts_added = false;
					if ( ! $fonts_added ) {
						$fonturl = esc_url( plugins_url( 'icons/font', __FILE__ ) );
						$ret['post_format_font'] = "@font-face {\n" .
								"font-family: 'cat_post';\n" .
								"src: url('$fonturl/cat_post.eot?58348147');\n" .
								"src: url('$fonturl/cat_post.eot?58348147#iefix') format('embedded-opentype'),\n" .
								"	   url('$fonturl/cat_post.woff2?58348147') format('woff2'),\n" .
								"	   url('$fonturl/cat_post.woff?58348147') format('woff'),\n" .
								"	   url('$fonturl/cat_post.ttf?58348147') format('truetype');\n" .
								" font-weight: normal;\n" .
								" font-style: normal;\n" .
								"}\n";
					}
					$fonts_added = true;

					$placement = '';
					switch ( $settings['show_post_format'] ) {
						case 'topleft':
							$placement = 'top:10%; left:10%;';
							break;
						case 'bottomleft':
							$placement = 'bottom:10%; left:10%;';
							break;
						case 'ceter':
							$placement = 'top:calc(50% - 34px); left:calc(50% - 34px);';
							break;
						case 'topright':
							$placement = 'top:10%; right:10%;';
							break;
						case 'bottomright':
							$placement = 'bottom:10%; right:10%;';
							break;
					}
					$styles['post_format_thumb'] = '.cat-post-thumbnail span {position:relative}';
					$styles['post_format_icon_styling'] = '.cat-post-format:before {font-family: "cat_post"; position:absolute; color:#FFFFFF; font-size:64px; line-height: 1; ' . $placement . '}';

					$styles['post_format_icon_aside'] = ".cat-post-format-aside:before { content: '\\f0f6'; }";
					$styles['post_format_icon_chat'] = ".cat-post-format-chat:before { content: '\\e802'; }";
					$styles['post_format_icon_gallery'] = ".cat-post-format-gallery:before { content: '\\e805'; }";
					$styles['post_format_icon_link'] = ".cat-post-format-link:before { content: '\\e809'; }";
					$styles['post_format_icon_image'] = ".cat-post-format-image:before { content: '\\e800'; }";
					$styles['post_format_icon_quote'] = ".cat-post-format-quote:before { content: '\\f10d'; }";
					$styles['post_format_icon_status'] = ".cat-post-format-status:before { content: '\\e80a'; }";
					$styles['post_format_icon_video'] = ".cat-post-format-video:before { content: '\\e801'; }";
					$styles['post_format_icon_audio'] = ".cat-post-format-audio:before { content: '\\e803'; }";
					
				}
			}

			foreach ( $styles as $key => $style ) {
				$ret[ $key ] = '#' . $widget_id . ' ' . $style;
			}

			if ( $is_shortcode ) {
				// Twenty Sixteen Theme adds underlines to links with box whadow wtf ...
				$ret['twentysixteen_thumb'] = '#' . $widget_id . ' .cat-post-thumbnail {box-shadow:none}'; // this for the thumb link.
				if ( ! ( isset( $settings['disable_font_styles'] ) && $settings['disable_font_styles'] ) ) { // checks if disable font styles is not set.
					$ret['twentysixteen_tag_link'] = '#' . $widget_id . ' .cat-post-tax-post_tag a {box-shadow:none}';     // this for the tag link.
					$ret['twentysixteen_tag_span'] = '#' . $widget_id . ' .cat-post-tax-post_tag span {box-shadow:none}';     // this for the tag link.
				}
				// Twenty Fifteen Theme adds border ...
				$ret['twentyfifteen_thumb'] = '#' . $widget_id . ' .cat-post-thumbnail {border:0}'; // this for the thumb link.
				if ( ! ( isset( $settings['disable_font_styles'] ) && $settings['disable_font_styles'] ) ) { // checks if disable font styles is not set.
					$ret['twentysixteen_tag_link'] = '#' . $widget_id . ' .cat-post-tax-post_tag a {border:0}';     // this for the tag link.
					$ret['twentysixteen_tag_span'] = '#' . $widget_id . ' .cat-post-tax-post_tag span {border:0}';     // this for the tag link.
				}
			}

			// probably all Themes have too much margin on their p element when used in the shortcode or widget.
			$ret['p_styling'] = '#' . $widget_id . ' p {margin:5px 0 0 0}'; // since on bottom it will make the spacing on cover
																// bigger (add to the padding) use only top for now.
			$ret['div_styling'] = '#' . $widget_id . ' li > div {margin:5px 0 0 0; clear:both;}'; // Add margin between the rows.
			
			// use WP dashicons in the template (e.g. for premade Template 'All and icons')
			$ret['dashicons'] = '#' . $widget_id . ' .dashicons {vertical-align:middle;}';
		}

		// Regardless if css is disabled we need some styling for the thumbnail
		// to make sure cropping is properly done, and they fit the allocated space.
		if ( isset( $settings['template'] ) && preg_match( '/%thumb%/', $settings['template'], $m, PREG_OFFSET_CAPTURE ) ) {
			if ( isset( $settings['use_css_cropping'] ) && $settings['use_css_cropping'] ) {
				$ret['thumb_crop'] = '#' . $widget_id . ' .cat-post-crop {overflow: hidden; display:inline-block}';
			} else {
				$ret['thumb_overflow'] = '#' . $widget_id . ' .cat-post-thumbnail span {overflow: hidden; display:inline-block}';
			}
			$ret['thumb_styling'] = '#' . $widget_id . ' .cat-post-item img {margin: initial;}';

			// Thumbnail related positioning rules.
			$wrap = isset( $settings['text_do_not_wrap_thumb'] ) && $settings['text_do_not_wrap_thumb'];
			if ( $wrap ) {
				$ret['thumb_flex'] = '#' . $widget_id . ' .cat-post-do-not-wrap-thumbnail {display:flex;}'; // Thumbnail container should flex.
			}
			$ret['text_do_not_wrap_thumb'] = '#' . $widget_id . ' .cat-post-thumbnail {float:left;}';
		}

		// Some hover effect require css to work, add it even if CSS is disabled.
		if ( isset( $settings['thumb_hover'] ) ) {
			switch ( $settings['thumb_hover'] ) {
				case 'white':
					$ret['white_hover_background'] = '#' . $widget_id . ' .cat-post-white {background-color: white;}';
					$ret['white_hover_thumb'] = '#' . $widget_id . ' .cat-post-white img {padding-bottom: 0 !important; -webkit-transition: all 0.3s ease; -moz-transition: all 0.3s ease; -ms-transition: all 0.3s ease; -o-transition: all 0.3s ease; transition: all 0.3s ease;}';
					$ret['white_hover_transform'] = '#' . $widget_id . ' .cat-post-white:hover img {opacity: 0.8;}';
					break;
				case 'dark':
					$ret['dark_hover_thumb'] = '#' . $widget_id . ' .cat-post-dark img {padding-bottom: 0 !important; -webkit-transition: all 0.3s ease; -moz-transition: all 0.3s ease; -ms-transition: all 0.3s ease; -o-transition: all 0.3s ease; transition: all 0.3s ease;}';
					$ret['dark_hover_transform'] = '#' . $widget_id . ' .cat-post-dark:hover img {-webkit-filter: brightness(75%); -moz-filter: brightness(75%); -ms-filter: brightness(75%); -o-filter: brightness(75%); filter: brightness(75%);}';
					break;
				case 'scale':
					$ret['scale_hover_thumb'] = '#' . $widget_id . ' .cat-post-scale img {margin: initial; padding-bottom: 0 !important; -webkit-transition: all 0.3s ease; -moz-transition: all 0.3s ease; -ms-transition: all 0.3s ease; -o-transition: all 0.3s ease; transition: all 0.3s ease;}';
					$ret['scale_hover_transform'] = '#' . $widget_id . ' .cat-post-scale:hover img {-webkit-transform: scale(1.1, 1.1); -ms-transform: scale(1.1, 1.1); transform: scale(1.1, 1.1);}';
					break;
				case 'blur':
					$ret['blur_hover_thumb'] = '#' . $widget_id . ' .cat-post-blur img {padding-bottom: 0 !important; -webkit-transition: all 0.3s ease; -moz-transition: all 0.3s ease; -ms-transition: all 0.3s ease; -o-transition: all 0.3s ease; transition: all 0.3s ease;}';
					$ret['blur_hover_transform'] = '#' . $widget_id . ' .cat-post-blur:hover img {-webkit-filter: blur(2px); -moz-filter: blur(2px); -o-filter: blur(2px); -ms-filter: blur(2px); filter: blur(2px);}';
					break;
				case 'icon':
					$fonturl = esc_url( plugins_url( 'icons/font', __FILE__ ) );
					$ret['icon_hover_font'] = "@font-face {\n" .
							"font-family: 'cat_post';\n" .
							"src: url('$fonturl/cat_post.eot?58348147');\n" .
							"src: url('$fonturl/cat_post.eot?58348147#iefix') format('embedded-opentype'),\n" .
							"	   url('$fonturl/cat_post.woff2?58348147') format('woff2'),\n" .
							"	   url('$fonturl/cat_post.woff?58348147') format('woff'),\n" .
							"	   url('$fonturl/cat_post.ttf?58348147') format('truetype');\n" .
							" font-weight: normal;\n" .
							" font-style: normal;\n" .
							"}\n";

					$ret['icon_hover_thumb'] = '#' . $widget_id . ' .cat-post-format-standard:before {opacity:0; -webkit-transition: all 0.3s ease; -moz-transition: all 0.3s ease; -ms-transition: all 0.3s ease; -o-transition: all 0.3s ease; transition: all 0.3s ease;}';
					$ret['icon_hover_transform'] = '#' . $widget_id . ' .cat-post-thumbnail:hover .cat-post-format-standard:before {opacity:1;}';
					if ( isset( $settings['show_post_format'] ) && ( 'none' === $settings['show_post_format'] ) ) {
						$ret[] = '#' . $widget_id . ' .cat-post-thumbnail {position:relative}';
						$ret[] = '#' . $widget_id . ' .cat-post-icon .cat-post-format:before {font-family: "cat_post"; position:absolute; color:#FFFFFF; font-size:64px; line-height: 1; ' .
									'top:calc(50% - 34px); left:calc(50% - 34px);}';
					}
					$ret[] = '#' . $widget_id . " .cat-post-format-standard:before {padding-left:12px; content: '\\e806'; }";
					break;
			}
		}
		$rules[] = $ret;
	}

	/**
	 *  Output the widget CSS
	 *
	 *  Just a wrapper that output getCSSRules
	 *
	 * @param bool $is_shortcode Indicates if we are in the context os a shortcode.
	 *
	 *  @since 4.7
	 */
	public function outputCSS( $is_shortcode ) {
		$rules = array();
		getCSSRules( $is_shortcode, $rules );
		foreach ( $rules as $rule ) {
			echo "$rule\n";
		}
	}

	/**
	 *  Get the id the virtual widget was registered with
	 *
	 *  @return string
	 *
	 *  @since 4.7
	 */
	public function id() {
		return $this->id;
	}

	/**
	 *  Get all the setting of the virtual widgets in an array
	 *
	 *  @return array
	 *
	 *  @since 4.7
	 */
	public static function getAllSettings() {
		return self::$collection;
	}

}

/**
 *  Class that implement a simple repository for the virtual widgets representing
 *  actuall shortcode and widgets
 *
 *  @since 4.7
 */
class virtualWidgetsRepository {

	/**
	 * Collection of objects representing shortcodes.
	 *
	 * @var array
	 *
	 * @since 4.7
	 */
	private static $shortcodeCollection = array();

	/**
	 * Collection of objects representing widgets.
	 *
	 * @var array
	 *
	 * @since 4.7
	 */
	private static $widgetCollection = array();

	/**
	 *  Add a virtual widget representing a shortcode to the repository
	 *
	 *  @param string        $index  A name to identify the specific shortcode.
	 *  @param virtualWidget $widget The virtual widget for it.
	 *
	 *  @since 4.7
	 */
	public function addShortcode( $index, $widget ) {
		self::$shortcodeCollection[ $index ] = $widget;
	}

	/**
	 *  Get all the virtual widgets representing actual shortcodes
	 *
	 *  @return array
	 *
	 *  @since 4.7
	 */
	public function getShortcodes() {
		return self::$shortcodeCollection;
	}

	/**
	 *  Add a virtual widget representing awidget to the repository
	 *
	 *  @param string        $index A name to identify the specific widget.
	 *  @param virtualWidget $widget The virstual widget for it.
	 *
	 *  @since 4.7
	 */
	public function addWidget( $index, $widget ) {
		self::$widgetCollection[ $index ] = $widget;
	}

	/**
	 *  Get all the virtual widgets representing actual widgets
	 *
	 *  @return array
	 *
	 *  @since 4.7
	 */
	public function getWidgets() {
		return self::$widgetCollection;
	}

}

add_action( 'wp_loaded', __NAMESPACE__ . '\wp_loaded' );

/**
 *  Run after WordPress finished bootstrapping, do whatever is needed at this stage
 *  like registering the meta.
 */
function wp_loaded() {
	register_meta( 'post', SHORTCODE_META, null, '__return_false' ); // do not allow access to the shortcode meta
																	// use the pre 4.6 format for backward compatibility.
}
