<?php

/**
 * Admin class that handle ajax calls.
 */
class Papi_Admin_Ajax {

	/**
	 * The action prefix for Papi ajax actions.
	 *
	 * @var string
	 */
	protected $action_prefix = 'papi/ajax/';

	/**
	 * The permalink structure.
	 *
	 * @var string
	 */
	protected $structure;

	/**
	 * The construct.
	 */
	public function __construct() {
		$this->setup_actions();
		$this->structure = get_option( 'permalink_structure' );
	}

	/**
	 * Add ajax endpoint.
	 */
	public function add_endpoint() {
		if ( empty( $this->structure ) ) {
			return;
		}

		add_rewrite_tag( '%action%', '([^/]*)' );
		add_rewrite_rule( 'papi-ajax/([^/]*)/?', 'index.php?action=$matches[1]', 'top' );
	}

	/**
	 * Add ajax url to Papi JavaScript object.
	 */
	public function ajax_url() {
		if ( empty( $this->structure ) ) {
			$url = esc_url( home_url( 'index.php', is_ssl() ? 'https' : 'http' ) );
		} else {
			$url = esc_url( home_url( '/papi-ajax/', is_ssl() ? 'https' : 'http' ) );
		}
		?>
		<script type="text/javascript">
			var papi = papi || {};
			papi.ajaxUrl = '<?php echo esc_html( $url ); ?>';
		</script>
		<?php
	}

	/**
	 * Handle Papi ajax.
	 */
	public function handle_papi_ajax() {
		if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
			return;
		}

		$ajax_action = '';

		if ( ! empty( $_GET['action'] ) ) {
			$ajax_action = sanitize_text_field( $_GET['action'] );
		}

		if ( is_user_logged_in() && has_action( $this->action_prefix . $ajax_action ) !== false ) {
			if ( ! defined( 'DOING_AJAX' ) ) {
				define( 'DOING_AJAX', true );
			}

			if ( ! defined( 'DOING_PAPI_AJAX' ) ) {
				define( 'DOING_PAPI_AJAX', true );
			}

			status_header( 200 );
			do_action( $this->action_prefix . $ajax_action );
			wp_die();
		}
	}

	/**
	 * Get entry type via GET.
	 *
	 * GET /papi-ajax/?action=get_entry_type
	 */
	public function get_entry_type() {
		$entry_type_id = papi_get_qs( 'entry_type' );
		$entry_type    = papi_get_entry_type_by_id( $entry_type_id );

		if ( empty( $entry_type ) ) {
			$this->render_error( 'No entry type found' );
			return;
		}

		wp_send_json( $entry_type );
	}

	/**
	 * Get posts via GET.
	 *
	 * Posts with empty title will be ignored.
	 *
	 * GET /papi-ajax/?action=get_posts
	 */
	public function get_posts() {
		$args   = papi_get_qs( 'query' ) ?: [];
		$args   = is_array( $args ) ? $args : [];
		$fields = papi_get_qs( 'fields' ) ?: [];
		$fields = is_array( $fields ) ? $fields : [];
		$posts  = ( new WP_Query( array_merge( [
			'posts_per_page'         => -1,
			'post_status'            => 'any',
			'post_type'              => ['post'],
			'no_found_rows'          => true,
			'update_post_meta_cache' => false,
			'update_post_term_cache' => false
		], $args ) ) )->posts;

		$posts = array_filter( $posts, function ( $post ) {
			return ! empty( $post->post_title );
		} );

		usort( $posts, function ( $a, $b ) {
			return strcmp( strtolower( $a->post_title ), strtolower( $b->post_title ) );
		} );

		if ( ! empty( $fields ) ) {
			foreach ( $posts as $index => $post ) {
				$item = [];

				foreach ( $fields as $field ) {
					$item[$field] = $post->$field;
				}

				$posts[$index] = $item;
			}
		}

		wp_send_json( $posts );
	}

	/**
	 * Get property html via GET.
	 *
	 * GET /papi-ajax/?action=get_property
	 */
	public function get_property() {
		$default_options = Papi_Core_Property::factory()->get_options();
		$keys            = array_keys( get_object_vars( $default_options ) );
		$options         = papi_get_qs( $keys, true );

		if ( $property = papi_property( $options ) ) {
			ob_start();

			$property->render_ajax_request();

			$html = ob_get_clean();

			wp_send_json( [
				'html' => utf8_encode( $html )
			] );

			return;
		}

		$this->render_error( 'No property found' );
	}

	/**
	 * Get properties via POST.
	 *
	 * POST /papi-ajax/?action=get_properties
	 */
	public function get_properties() {
		if ( ! papi_get_sanitized_post( 'properties' ) ) {
			$this->render_error( 'No properties found' );

			return;
		}

		$items = json_decode( stripslashes( $_POST['properties'] ), true );

		if ( empty( $items ) || ! is_array( $items ) ) {
			$this->render_error( 'No properties found' );

			return;
		}

		foreach ( $items as $key => $item ) {
			$property = papi_property( (array) $item );

			if ( ! papi_is_property( $property ) ) {
				unset( $items[$key] );
				continue;
			}

			ob_start();

			$property->render_ajax_request();

			$items[$key] = trim( ob_get_clean() );
		}

		$items = array_filter( $items );

		if ( empty( $items ) ) {
			$this->render_error( 'No properties found' );

			return;
		}

		wp_send_json( [
			'html' => $items
		] );
	}

	/**
	 * Get rules result via GET.
	 *
	 * GET /papi-ajax/?action=get_rules_result
	 */
	public function get_rules_result() {
		if ( ! papi_get_sanitized_post( 'data' ) ) {
			$this->render_error( 'No rule found' );

			return;
		}

		$data = json_decode( stripslashes( papi_get_sanitized_post( 'data' ) ), true );

		if ( empty( $data ) || ! is_array( $data ) || ! isset( $data['slug'] ) ) {
			$this->render_error( 'No rule found' );

			return;
		}

		$entry_type = papi_get_entry_type_by_meta_id();

		if ( empty( $entry_type ) ) {
			$entry_type_id = papi_get_qs( 'entry_type' );
			$entry_type    = papi_get_entry_type_by_id( $entry_type_id );
		}

		if ( $entry_type instanceof Papi_Entry_Type === false ) {
			$this->render_error( 'No rule found' );

			return;
		}

		if ( preg_match( '/\[\]$/', $data['slug'] ) ) {
			$data['slug'] = preg_replace( '/\[\]$/', '', $data['slug'] );
		}

		if ( $property = $entry_type->get_property( $data['slug'] ) ) {
			wp_send_json( [
				'render' => $property->render_is_allowed_by_rules( $data['rules'] )
			] );

			return;
		}

		$this->render_error( 'No rule found' );
	}

	/**
	 * Get shortcode via GET.
	 *
	 * GET /papi-ajax/?action=get_shortcode
	 */
	public function get_shortcode() {
		$shortcode = papi_get_qs( 'shortcode' ) ?: '';
		$shortcode = html_entity_decode( $shortcode );
		$shortcode = wp_unslash( $shortcode );

		wp_send_json( [
			'html' => do_shortcode( $shortcode )
		] );
	}

	/**
	 * Get terms via GET.
	 *
	 * GET /papi-ajax/?action=get_terms
	 */
	public function get_terms() {
		$query    = papi_get_qs( 'query' ) ?: [];
		$query    = is_array( $query ) ? $query : [];
		$taxonomy = papi_get_qs( 'taxonomy' ) ?: '';

		$args = array_merge( $query, [
			'fields' => 'id=>name'
		] );

		$terms = [];
		if ( taxonomy_exists( $taxonomy ) ) {
			$terms = get_terms( $taxonomy, $args );
		}

		wp_send_json( $terms );
	}

	/**
	 * Render error message.
	 *
	 * @param string $message
	 */
	public function render_error( $message ) {
		wp_send_json( [
			'error' => $message
		] );
	}

	/**
	 * Setup action hooks.
	 */
	protected function setup_actions() {
		add_action( 'init', [$this, 'add_endpoint'] );
		add_action( 'parse_request', [$this, 'handle_papi_ajax'] );
		add_action( 'admin_enqueue_scripts', [$this, 'ajax_url'], 10 );

		// Ajax actions.
		add_action( $this->action_prefix . 'get_entry_type', [$this, 'get_entry_type'] );
		add_action( $this->action_prefix . 'get_property', [$this, 'get_property'] );
		add_action( $this->action_prefix . 'get_properties', [$this, 'get_properties'] );
		add_action( $this->action_prefix . 'get_rules_result', [$this, 'get_rules_result'] );
		add_action( $this->action_prefix . 'get_posts', [$this, 'get_posts'] );
		add_action( $this->action_prefix . 'get_terms', [$this, 'get_terms'] );
		add_action( $this->action_prefix . 'get_shortcode', [$this, 'get_shortcode'] );
	}
}

new Papi_Admin_Ajax;


<?php

/**
 * Manage terms.
 */
class Papi_CLI_Term_Command extends Papi_CLI_Command {

	/**
	 * Get default fields for formatter.
	 *
	 * @return array
	 */
	protected function get_default_format_fields() {
		return ['slug', 'type', 'has value', 'box'];
	}

	/**
	 * Get fields that exists on a term.
	 *
	 * ## OPTIONS
	 *
	 * <term>
	 * : Term ID
	 *
	 * [--field=<field>]
	 * : Instead of returning the whole term fields, returns the value of a single fields.
	 *
	 * [--fields=<fields>]
	 * : Get a specific subset of the term's fields.
	 *
	 * [--format=<format>]
	 * : Accepted values: table, json, csv. Default: table.
	 *
	 * ## AVAILABLE FIELDS
	 *
	 * These fields are available for get command:
	 *
	 * * slug
	 * * type
	 * * exists
	 *
	 * ## EXAMPLES
	 *
	 *     wp papi term get 123 --format=json
	 *
	 *     wp papi term get 123 --field=slug
	 *
	 * @param  array $args
	 * @param  array $assoc_args
	 */
	public function get( $args, $assoc_args ) {
		try {
			// Set query string that we need.
			$_GET['meta_type'] = 'term';

			// Get the taxonomy type that the term has.
			$entry_type = papi_get_entry_type_by_meta_id( $args[0] );

			if ( empty( $entry_type ) || $entry_type instanceof Papi_Taxonomy_Type === false ) {
				WP_CLI::error( 'No taxonomy type exists on the term' );
			}

			$properties = [];

			foreach ( $entry_type->get_boxes() as $box ) {
				foreach ( $box->properties as $property ) {
					$properties[] = [
						'slug'      => $property->get_slug( true ),
						'type'      => $property->type,
						'has value' => $property->get_value() !== null ? 'true' : 'false',
						'box'       => $box->title
					];
				}
			}

			// Render types as a table.
			$formatter = $this->get_formatter( $assoc_args );
			$formatter->display_items( $properties );
		} catch ( WC_CLI_Exception $e ) {
			WP_CLI::error( $e->getMessage() );
		}
	}

	/**
	 * Rename meta key for taxonomy type.
	 *
	 * ## OPTIONS
	 *
	 * <taxonomy_type>
	 * : Taxonomy type id
	 *
	 * <old_key>
	 * : Old meta key
	 *
	 * <new_key>
	 * : New meta key
	 *
	 * ## EXAMPLES
	 *
	 *     wp papi term rename about-page-type name title
	 *
	 * @param  array $args
	 * @param  array $assoc_args
	 */
	public function rename( $args, $assoc_args ) {
		$type    = $args[0];
		$old_key = $args[1];
		$new_key = $args[2];

		$terms = ( new Papi_Query( [
			'entry_type' => $type,
			'fields'     => 'ids'
		] ) )->get_result();

		if ( empty( $terms ) ) {
			WP_CLI::error( 'No terms found' );
		}

		foreach ( $terms as $term ) {
			$meta = get_term_meta( $term, $old_key, true );

			if ( papi_is_empty( $meta ) ) {
				continue;
			}

			if ( delete_term_meta( $term, $old_key ) === false ) {
				WP_CLI::error( 'Could not delete term meta with key: ' . $old_key );

			}

			if ( update_term_meta( $term, $new_key, $meta ) === false ) {
				WP_CLI::error( 'Could not update term meta with key: ' . $new_key );
			}
		}

		WP_CLI::success( 'Done' );
	}
}


<?php

/**
 * Admin class that handle table columns.
 */
final class Papi_Admin_Columns {

	/**
	 * Current post type.
	 *
	 * @var string
	 */
	protected $post_type;

	/**
	 * Current taxonomy.
	 *
	 * @var string
	 */
	protected $taxonomy;

	/**
	 * The constructor.
	 */
	public function __construct() {
		$this->setup_globals();
		$this->setup_actions();
		$this->setup_filters();
	}

	/**
	 * Get meta type value.
	 *
	 * @return string
	 */
	protected function get_meta_type_value() {
		return empty( $this->taxonomy ) ? $this->post_type : $this->taxonomy;
	}

	/**
	 * Add custom table header to page or taxonomy type.
	 *
	 * @param  array $defaults
	 *
	 * @return array
	 */
	public function manage_page_type_posts_columns( array $defaults = [] ) {
		if ( ! in_array( $this->post_type, papi_get_post_types(), true ) && ! in_array( $this->taxonomy, papi_get_taxonomies(), true ) ) {
			return $defaults;
		}

		/**
		 * Hide column for post or taxonomy type. Default is false.
		 *
		 * @param string $post_type
		 */
		if ( apply_filters( 'papi/settings/column_hide_' . $this->get_meta_type_value(), false ) ) {
			return $defaults;
		}

		/**
		 * Change column title for entry type column.
		 *
		 * @param string $post_type
		 */
		$defaults['entry_type'] = apply_filters(
			'papi/settings/column_title_' . $this->get_meta_type_value(),
			esc_html__( 'Type', 'papi' )
		);

		return $defaults;
	}

	/**
	 * Add custom table column to page or taxonomy type.
	 *
	 * @param string $column_name
	 * @param int    $post_id
	 * @param int    $term_id
	 */
	public function manage_page_type_posts_custom_column( $column_name, $post_id, $term_id = null ) {
		if ( ! in_array( $this->post_type, papi_get_post_types(), true ) && ! in_array( $this->taxonomy, papi_get_taxonomies(), true ) ) {
			return;
		}

		/**
		 * Hide column for post type. Default is false.
		 *
		 * @param string $post_type
		 */
		if ( apply_filters( 'papi/settings/column_hide_' . $this->get_meta_type_value(), false ) ) {
			return;
		}

		// Column name most be `entry_type`. On taxomy the column name is `post_id` variable.
		if ( $column_name !== 'entry_type' && $post_id !== 'entry_type' ) {
			return;
		}

		// Get the entry type for the post or term.
		$entry_type = papi_get_entry_type_by_meta_id(
			is_numeric( $post_id ) ? $post_id : $term_id,
			papi_get_meta_type()
		);

		$post = ! empty( $this->post_type ) && empty( $this->taxonomy );
		$type = $post ? 'page' : 'taxonomy';
		$arg  = $post ? papi_get_post_type() : papi_get_taxonomy();
		$show = call_user_func_array( "papi_filter_settings_show_standard_{$type}_type", [$arg] );

		if ( ! is_null( $entry_type ) && ( $show === false || get_metadata( papi_get_meta_type( $type ), ( is_numeric( $post_id ) ? $post_id : $term_id ), papi_get_page_type_key(), true ) === $entry_type->get_id() ) ) {
			echo esc_html( $entry_type->name );
		} else {
			echo esc_html( call_user_func_array( "papi_filter_settings_standard_{$type}_type_name", [$arg] ) );
		}
	}

	/**
	 * Add entry type as a sortable column.
	 *
	 * @param  array $columns
	 *
	 * @return array
	 */
	public function manage_page_type_sortable_columns( $columns ) {
		if ( in_array( $this->post_type, papi_get_post_types(), true ) || in_array( $this->taxonomy, papi_get_taxonomies(), true ) ) {
			$columns['entry_type'] = 'entry_type';
		}

		return $columns;
	}

	/**
	 * Filter posts on load if `page_type` query string is set.
	 *
	 * @param  WP_Query $query
	 *
	 * @return WP_Query
	 */
	public function pre_get_posts( WP_Query $query ) {
		global $pagenow;

		if ( $pagenow === 'edit.php' && ! is_null( papi_get_qs( 'page_type' ) ) ) {
			if ( papi_get_qs( 'page_type' ) === 'papi-standard-page' ) {
				$query->set( 'meta_query', [
					[
						'key'     => papi_get_page_type_key(),
						'compare' => 'NOT EXISTS'
					]
				] );
			} else {
				$query->set( 'meta_key', papi_get_page_type_key() );
				$query->set( 'meta_value', papi_get_qs( 'page_type' ) );
			}
		}

		// Order query result by entry type.
		if ( $query->get( 'orderby' ) === 'entry_type' ) {
			$query->set( 'meta_key', papi_get_page_type_key() );
			$query->set( 'orderby', 'meta_value' );
		}

		return $query;
	}

	/**
	 * Filter page types in post type list.
	 */
	public function restrict_page_types() {
		$post_types = papi_get_post_types();

		if ( in_array( $this->post_type, $post_types, true ) ) {
			$page_types = papi_get_all_page_types( $this->post_type );
			$page_types = array_map( function ( $page_type ) {
				return [
					'name'  => $page_type->name,
					'value' => $page_type->get_id()
				];
			}, $page_types );

			// Add the standard page that isn't a real page type.
			if ( papi_filter_settings_show_standard_page_type_in_filter( $this->post_type ) ) {
				$page_types[] = [
					'name'  => papi_filter_settings_standard_page_type_name( $this->post_type ),
					'value' => 'papi-standard-page'
				];
			}

			usort( $page_types, function ( $a, $b ) {
				return strcmp(
					strtolower( $a['name'] ),
					strtolower( $b['name'] )
				);
			} );
			?>
			<select name="page_type" class="postform">
				<option value="0" selected><?php esc_html_e( 'All types', 'papi' ); ?></option>
				<?php
				foreach ( $page_types as $page_type ) {
					printf(
						'<option value="%s" %s>%s</option>',
						esc_attr( $page_type['value'] ),
						papi_get_qs( 'page_type' ) === $page_type['value'] ? ' selected' : '',
						esc_html( $page_type['name'] )
					);
				}
				?>
			</select>
			<?php
		}
	}

	/**
	 * Setup actions.
	 */
	protected function setup_actions() {
		// Setup post type actions.
		if ( ! empty( $this->post_type ) && empty( $this->taxonomy ) ) {
			add_filter( 'pre_get_posts', [$this, 'pre_get_posts'] );
			add_action( 'restrict_manage_posts', [$this, 'restrict_page_types'] );
			add_action( sprintf( 'manage_%s_posts_custom_column', $this->post_type ), [$this, 'manage_page_type_posts_custom_column'], 10, 2 );
		}

		// Setup taxonomy actions.
		if ( ! empty( $this->taxonomy ) ) {
			add_action( sprintf( 'manage_%s_custom_column', $this->taxonomy ), [$this, 'manage_page_type_posts_custom_column'], 10, 3 );
		}
	}

	/**
	 * Setup filters.
	 */
	protected function setup_filters() {
		// Setup post type actions.
		if ( ! empty( $this->post_type ) && empty( $this->taxonomy ) ) {
			add_filter( sprintf( 'manage_%s_posts_columns', $this->post_type ), [$this, 'manage_page_type_posts_columns'] );
			add_filter( sprintf( 'manage_edit-%s_sortable_columns', $this->post_type ), [$this, 'manage_page_type_sortable_columns'] );
		}

		// Setup taxonomy actions.
		if ( ! empty( $this->taxonomy ) ) {
			add_filter( sprintf( 'manage_edit-%s_columns', $this->taxonomy ), [$this, 'manage_page_type_posts_columns'] );
			add_filter( sprintf( 'manage_edit-%s_sortable_columns', $this->taxonomy ), [$this, 'manage_page_type_sortable_columns'] );
		}
	}

	/**
	 * Setup globals.
	 */
	protected function setup_globals() {
		$this->post_type = papi_get_post_type();
		$this->taxonomy  = papi_get_taxonomy();
	}
}

if ( papi_is_admin() ) {
	new Papi_Admin_Columns;
}


<?php

/**
 * Term property that populates a dropdown.
 */
class Papi_Property_Term extends Papi_Property {

	/**
	 * The convert type.
	 *
	 * @var string
	 */
	public $convert_type = 'object';

	/**
	 * Format the value of the property before it's returned to the application.
	 *
	 * @param  mixed  $value
	 * @param  string $slug
	 * @param  int    $term_id
	 *
	 * @return array
	 */
	public function format_value( $value, $slug, $term_id ) {
		$meta_key = $this->get_setting( 'meta_key' );

		if ( empty( $meta_key ) ) {
			if ( is_numeric( $value ) && intval( $value ) !== 0 ) {
				$term_id = $value;
			}
		} else {
			$args = [
				'fields'     => 'ids',
				'meta_key'   => $meta_key,
				'meta_value' => $value,
				'hide_empty' => false,
				'taxonomy'   => $this->get_setting( 'taxonomy' ),
				'number'     => 1
			];

			$terms = get_terms( $args );

			if ( ! empty( $terms ) && ! is_wp_error( $terms ) ) {
				$term_id = $terms[0];
			}
		}

		// Allow only id to be returned.
		if ( ! papi_is_admin() && $this->get_setting( 'fields' ) === 'ids' ) {
			$term = $this->get_term_value( $term_id );
		} elseif ( ! empty( $term_id ) ) {
			$term = get_term( $term_id );
		}

		if ( empty( $term ) || is_wp_error( $term ) ) {
			$term = $this->default_value;
		}

		return $term;
	}

	/**
	 * Get default settings.
	 *
	 * @return array
	 */
	public function get_default_settings() {
		return [
			'allow_clear' => true,
			'fields'      => '',
			'labels'      => [
				'select_taxonomy' => __( 'Select Taxonomy', 'papi' ),
				'select_item'     => __( 'Select %s term', 'papi' )
			],
			'layout'      => 'single', // Single or advanced
			'meta_key'    => '',
			'placeholder' => null,
			'taxonomy'    => '',
			'select2'     => true,
			'query'       => []
		];
	}

	/**
	 * Get labels from taxonomies.
	 *
	 * @return array
	 */
	protected function get_labels() {
		$results = [];

		foreach ( $this->get_taxonomies() as $taxonomy ) {
			if ( taxonomy_exists( $taxonomy ) ) {
				$taxonomy_object    = get_taxonomy( $taxonomy );
				$results[$taxonomy] = $taxonomy_object->labels->name;
			}
		}

		return $results;
	}

	/**
	 * Get taxonomies.
	 *
	 * @return array
	 */
	protected function get_taxonomies() {
		return papi_to_array( $this->get_setting( 'taxonomy' ) );
	}

	/**
	 * Get terms for specified taxonomy.
	 *
	 * @param  string $taxonomy
	 *
	 * @return array
	 */
	protected function get_terms( $taxonomy ) {
		// Prepare arguments for get_terms.
		$query = $this->get_setting( 'query' );
		$args  = array_merge( $query, [
			'fields' => 'id=>name'
		] );

		$terms = [];
		if ( taxonomy_exists( $taxonomy ) ) {
			$terms = get_terms( $taxonomy, $args );
		}

		return $terms;
	}

	/**
	 * Get matching value based on key from a term.
	 *
	 * @param  mixed $term
	 *
	 * @return mixed
	 */
	protected function get_term_value( $term ) {
		$meta_key = $this->get_setting( 'meta_key' );

		if ( is_numeric( $term ) ) {
			$term_id = $term;
		} else {
			$term = get_term( $term );

			if ( $term instanceof WP_Term === false ) {
				return 0;
			}

			$term_id = $term->term_id;
		}

		if ( ! empty( $meta_key ) ) {
			$value = get_term_meta( $term_id, $meta_key, true );
		} else {
			$value = $term_id;
		}

		return $value;
	}

	/**
	 * Render property html.
	 */
	public function html() {
		$settings          = $this->get_settings();
		$layout            = $settings->layout;
		$labels            = $this->get_labels();
		$taxonomies        = $this->get_taxonomies();
		$render_label      = count( $taxonomies ) > 1;
		$advanced          = $render_label && $layout === 'advanced';
		$single            = $render_label && $layout !== 'advanced';
		$classes           = count( $taxonomies ) > 1 ? '' : 'papi-fullwidth';
		$value             = $this->get_value();
		$selected_term     = get_term( $value );
		$selected_term     = is_wp_error( $selected_term ) || empty( $selected_term ) ? '' : $selected_term;
		$selected_taxonomy = empty( $selected_term ) ? reset( $taxonomies ) : $selected_term->taxonomy;
		$value             = $this->get_term_value( $value );
		$selected_label    = reset( $labels );

		if ( $settings->select2 ) {
			$classes = ' papi-component-select2';
		}

		?>

		<div class="papi-property-term <?php echo $advanced ? 'advanced' : ''; ?>">
			<?php if ( $advanced ): ?>

							<label for="<?php echo esc_attr( $this->html_id() ); ?>_taxonomy">
								<?php echo esc_html( $settings->labels['select_taxonomy'] ); ?>

							<select
								id="<?php echo esc_attr( $this->html_id() ); ?>_taxonomy"
								class="<?php echo esc_attr( $classes ); ?> papi-property-term-left"
								data-select-item="<?php echo esc_attr( $settings->labels['select_item'] ); ?>"
								data-term-query='<?php echo esc_attr( papi_maybe_json_encode( $settings->query ) ); ?>'
								data-width="100%"
								>
								<?php
								foreach ( $labels as $taxonomy => $label ) {
									papi_render_html_tag( 'option', [
										'value'    => $taxonomy,
										'selected' => $taxonomy === $selected_taxonomy,
										$label
									] );

									if ( $selected ) {
										$selected_label = $label;
									}
								}
								?>
							</select>

							<label for="<?php echo esc_attr( $this->html_id() ); ?>_terms">
								<?php echo esc_html( sprintf( $settings->labels['select_item'], $selected_label ) ); ?>

			<?php endif; ?>

			<?php
				$placeholder = ! is_null( $settings->placeholder ) ? $settings->placeholder : '';
				$placeholder = papi_is_empty( $placeholder ) ? ' ' : $placeholder;
			?>

			<select
				class="<?php echo esc_attr( $classes ); ?>  papi-property-term-right"
				id="<?php echo esc_attr( $this->html_id() ); ?>_terms"
				name="<?php echo esc_attr( $this->html_name() ); ?>"
				class="<?php echo esc_attr( $classes ); ?>"
				data-allow-clear="<?php echo is_null( $settings->placeholder ) ? 'false' : 'true'; ?>"
				data-placeholder="<?php echo esc_attr( $placeholder ); ?>"
				data-width="100%">

				<?php if ( ! is_null( $settings->placeholder ) ): ?>
					<option value="<?php echo esc_attr( $this->get_option( 'default', ' ' ) ); ?>"><?php echo esc_html( $placeholder ); ?></option>
				<?php endif; ?>

				<?php foreach ( $taxonomies as $taxonomy ) : ?>
					<?php
					if ( $advanced && $taxonomy !== $selected_taxonomy ) {
						continue;
					}

					$terms = $this->get_terms( $taxonomy );
					if ( empty( $terms ) ) {
						continue;
					}
					?>

					<?php if ( $single ): ?>
						<optgroup label="<?php echo esc_attr( $labels[$taxonomy] ); ?>">
					<?php endif; ?>

					<?php
					foreach ( $terms as $term_id => $term_name ) {
						if ( papi_is_empty( $term_name ) ) {
							continue;
						}

						papi_render_html_tag( 'option', [
							'data-allow-clear' => $settings->allow_clear,
							'value'            => $this->get_term_value( $term_id ),
							'selected'         => $value === $this->get_term_value( $term_id ),
							esc_html( $term_name )
						] );
					}
					?>

					<?php if ( $single ): ?>
						</optgroup>
					<?php endif; ?>

				<?php endforeach; ?>
			</select>

			<?php if ( $advanced ): ?>

			<?php endif; ?>

		<?php
	}
}


<?php

/**
 * Bool (true/false) property.
 */
class Papi_Property_Bool extends Papi_Property {

	/**
	 * The convert type.
	 *
	 * @var string
	 */
	public $convert_type = 'bool';

	/**
	 * The default value.
	 *
	 * @var bool
	 */
	public $default_value = false;

	/**
	 * Format the value of the property before it's returned
	 * to WordPress admin or the site.
	 *
	 * @param  mixed  $value
	 * @param  string $slug
	 * @param  int    $post_id
	 *
	 * @return bool
	 */
	public function format_value( $value, $slug, $post_id ) {
		if ( is_string( $value ) && $value === 'false' || $value === false ) {
			return false;
		}

		return  is_string( $value ) &&
			( $value === 'true' || $value === 'on' ) ||
			$value === true;
	}

	/**
	 * Render property html.
	 */
	public function html() {
		$value = $this->get_value();

		papi_render_html_tag( 'input', [
			'type'  => 'hidden',
			'name'  => esc_attr( $this->html_name() ),
			'value' => false
		] );

		papi_render_html_tag( 'input', [
			'checked' => ! empty( $value ),
			'id'      => esc_attr( $this->html_id() ),
			'name'    => esc_attr( $this->html_name() ),
			'type'    => 'checkbox'
		] );
	}

	/**
	 * Change value after it's loaded from the database.
	 *
	 * @param  mixed  $value
	 * @param  string $slug
	 * @param  int    $post_id
	 *
	 * @return mixed
	 */
	public function load_value( $value, $slug, $post_id ) {
		return is_string( $value ) && $value === '1' || $value;
	}

	/**
	 * Prepare property value.
	 *
	 * @param  mixed $value
	 *
	 * @return mixed
	 */
	protected function prepare_value( $value ) {
		if ( is_string( $value ) && ( $value === 'true' || $value === 'on' ) || $value === true ) {
			return true;
		}

		return null;
	}

	/**
	 * Fix the database value on update.
	 *
	 * @param  mixed  $value
	 * @param  string $slug
	 * @param  int    $post_id
	 *
	 * @return array
	 */
	public function update_value( $value, $slug, $post_id ) {
		return $this->prepare_value( $value );
	}
}


<?php

/**
 * Property that shows which relationships that exists
 * between the current post and other posts.
 */
class Papi_Property_Reference extends Papi_Property {

	/**
	 * Get default settings.
	 *
	 * @return array
	 */
	public function get_default_settings() {
		return [
			'slug'      => [],
			'page_type' => []
		];
	}

	/**
	 * Render property html.
	 */
	public function html() {
		$post_id  = papi_get_post_id();
		$settings = $this->get_settings();

		// Create query array for every page type.
		$page_types = array_map( function ( $page_type ) {
			return [
				'key'     => papi_get_page_type_key(),
				'value'   => $page_type,
				'compare' => 'LIKE'
			];
		}, papi_to_array( $settings->page_type ) );

		// Add relation.
		$page_types['relation'] = 'OR';

		// Prepare arguments for WP_Query.
		$args = [
			'post_type'              => 'any',
			'no_found_rows'          => true,
			'update_post_meta_cache' => false,
			'update_post_term_cache' => false,
			'meta_query'             => $page_types
		];

		$posts = ( new WP_Query( $args ) )->posts;

		$values = [];

		foreach ( papi_to_array( $settings->slug ) as $slug ) {
			foreach ( $posts as $post ) {
				$val = papi_get_field( $post->ID, $slug );

				$val = array_filter( papi_to_array( $val ), function ( $item ) use ( $post_id ) {
					return is_object( $item ) && $item->ID === $post_id;
				} );

				if ( empty( $val ) ) {
					continue;
				}

				$page_type = papi_get_entry_type_by_meta_id( $post->ID );

				if ( empty( $page_type ) ) {
					continue;
				}

				// Create the array
				if ( ! isset( $values[$post->post_type] ) ) {
					$values[$post->post_type] = [];
				}

				if ( ! isset( $values[$post->post_type][$page_type->name] ) ) {
					$values[$post->post_type][$page_type->name] = [];
				}

				// Add the post
				if ( ! isset( $values[$post->post_type][$page_type->name][$post->ID] ) && ! empty( $val ) ) {
					$values[$post->post_type][$page_type->name][$post->ID] = $post;
				}
			}
		}

		?>
		<ul class="papi-property-reference" data-papi-rule="<?php echo esc_attr( $this->html_name() ); ?>">
			<?php if ( empty( $values ) ): ?>

					<?php esc_html_e( 'No references exists', 'papi' ); ?>

			<?php
			endif;
			ksort( $values ); foreach ( $values as $title => $val ):
				$post_type = get_post_type_object( $title );
				?>

<?php echo esc_html( $post_type->labels->name ); ?>

							<?php ksort( $val ); foreach ( $val as $name => $posts ): ?>

<?php echo esc_html( $name ); ?>

										<?php $i = 0;
										foreach ( $posts as $post ): ?>
											<a href="<?php echo esc_attr( get_edit_post_link( $post->ID ) ); ?>"><?php echo esc_html( $post->post_title ); ?>
										<?php $i++;
										endforeach; ?>

							<?php endforeach; ?>

			<?php endforeach; ?>

		<?php
	}
}


<?php

namespace Scheb\YahooFinanceApi;

use GuzzleHttp\ClientInterface;
use GuzzleHttp\Cookie\CookieJar;
use Scheb\YahooFinanceApi\Exception\ApiException;
use Scheb\YahooFinanceApi\Results\HistoricalData;
use Scheb\YahooFinanceApi\Results\Quote;
use Scheb\YahooFinanceApi\Results\SearchResult;

class ApiClient
{
    const INTERVAL_1_DAY = '1d';
    const INTERVAL_1_WEEK = '1wk';
    const INTERVAL_1_MONTH = '1mo';
    const CURRENCY_SYMBOL_SUFFIX = '=X';

    /**
     * @var ClientInterface
     */
    private $client;

    /**
     * @var ResultDecoder
     */
    private $resultDecoder;

    public function __construct(ClientInterface $guzzleClient, ResultDecoder $resultDecoder)
    {
        $this->client = $guzzleClient;
        $this->resultDecoder = $resultDecoder;
    }

    /**
     * Search for stocks.
     *
     * @param string $searchTerm
     *
     * @return array|SearchResult[]
     *
     * @throws ApiException
     */
    public function search($searchTerm)
    {
        $url = 'https://finance.yahoo.com/_finance_doubledown/api/resource/searchassist;gossipConfig=%7B%22queryKey%22:%22query%22,%22resultAccessor%22:%22ResultSet.Result%22,%22suggestionTitleAccessor%22:%22symbol%22,%22suggestionMeta%22:[%22symbol%22],%22url%22:%7B%22query%22:%7B%22region%22:%22US%22,%22lang%22:%22en-US%22%7D%7D%7D;searchTerm='
            .urlencode($searchTerm)
            .'?bkt=[%22findd-ctrl%22,%22fin-strm-test1%22,%22fndmtest%22,%22finnossl%22]&device=desktop&feature=canvassOffnet,finGrayNav,newContentAttribution,relatedVideoFeature,videoNativePlaylist,livecoverage&intl=us&lang=en-US&partner=none&prid=eo2okrhcni00f&region=US&site=finance&tz=UTC&ver=0.102.432&returnMeta=true';
        $responseBody = (string) $this->client->request('GET', $url)->getBody();

        return $this->resultDecoder->transformSearchResult($responseBody);
    }

    /**
     * Get historical data for a symbol.
     *
     * @param string    $symbol
     * @param string    $interval
     * @param \DateTime $startDate
     * @param \DateTime $endDate
     *
     * @return array|HistoricalData[]
     *
     * @throws ApiException
     */
    public function getHistoricalData($symbol, $interval, \DateTime $startDate, \DateTime $endDate)
    {
        $allowedIntervals = [self::INTERVAL_1_DAY, self::INTERVAL_1_WEEK, self::INTERVAL_1_MONTH];
        if (!in_array($interval, $allowedIntervals)) {
            throw new \InvalidArgumentException('Interval must be one of: '.implode(', ', $allowedIntervals));
        }

        if ($startDate > $endDate) {
            throw new \InvalidArgumentException('Start date must be before end date');
        }

        $cookieJar = new CookieJar();

        $initialUrl = 'https://finance.yahoo.com/quote/'.urlencode($symbol).'/history?p='.urlencode($symbol);
        $responseBody = (string) $this->client->request('GET', $initialUrl, ['cookies' => $cookieJar])->getBody();
        $crumb = $this->resultDecoder->extractCrumb($responseBody);

        $dataUrl = 'https://query1.finance.yahoo.com/v7/finance/download/'.urlencode($symbol).'?period1='.$startDate->getTimestamp().'&period2='.$endDate->getTimestamp().'&interval='.$interval.'&events=history&crumb='.urlencode($crumb);
        $responseBody = (string) $this->client->request('GET', $dataUrl, ['cookies' => $cookieJar])->getBody();

        return $this->resultDecoder->transformHistoricalDataResult($responseBody);
    }

    /**
     * Get quote for a single symbol.
     *
     * @param string $symbol
     *
     * @return Quote|null
     */
    public function getQuote($symbol)
    {
        $list = $this->fetchQuotes([$symbol]);

        return isset($list[0]) ? $list[0] : null;
    }

    /**
     * Get quotes for one or multiple symbols.
     *
     * @param array $symbols
     *
     * @return array|Quote[]
     */
    public function getQuotes(array $symbols)
    {
        return $this->fetchQuotes($symbols);
    }

    /**
     * Get exchange rate for two currencies. Accepts concatenated ISO 4217 currency codes.
     *
     * @param string $currency1
     * @param string $currency2
     *
     * @return Quote|null
     */
    public function getExchangeRate($currency1, $currency2)
    {
        $list = $this->getExchangeRates([[$currency1, $currency2]]);

        return isset($list[0]) ? $list[0] : null;
    }

    /**
     * Retrieves currency exchange rates. Accepts concatenated ISO 4217 currency codes such as "GBPUSD".
     *
     * @param array $currencyPairs List of pairs of currencies
     *
     * @return array|Quote[]
     */
    public function getExchangeRates(array $currencyPairs)
    {
        $currencySymbols = array_map(function (array $currencies) {
            return implode($currencies).self::CURRENCY_SYMBOL_SUFFIX; // Currency pairs are suffixed with "=X"
        }, $currencyPairs);

        return $this->fetchQuotes($currencySymbols);
    }

    /**
     * Fetch quote data from API.
     *
     * @param array $symbols
     *
     * @return array|Quote[]
     */
    private function fetchQuotes(array $symbols)
    {
        $url = 'https://query1.finance.yahoo.com/v7/finance/quote?symbols='.urlencode(implode(',', $symbols));
        $responseBody = (string) $this->client->request('GET', $url)->getBody();

        return $this->resultDecoder->transformQuotes($responseBody);
    }
}


<?php

namespace Scheb\YahooFinanceApi;

use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;

class ApiClientFactory
{
    /**
     * @param ClientInterface|null $guzzleClient
     *
     * @return ApiClient
     */
    public static function createApiClient(ClientInterface $guzzleClient = null)
    {
        $guzzleClient = $guzzleClient ? $guzzleClient : new Client();
        $resultDecoder = new ResultDecoder();

        return new ApiClient($guzzleClient, $resultDecoder);
    }
}


<?php namespace SleepingOwl\Admin\Commands;

use Config;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
use SleepingOwl\Admin\Commands\Compilers\ModelCompiler;
use SleepingOwl\AdminAuth\Entities\Administrator;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

class AdministratorsCommand extends Command
{
	/**
	 * The console command name.
	 * @var string
	 */
	protected $name = 'admin:administrators';

	/**
	 * The console command description.
	 * @var string
	 */
	protected $description = 'Manage your administrators.';

	/**
	 * Execute the console command.
	 * @return void
	 */
	public function fire()
	{
		if ($this->option('new'))
		{
			$this->createNewAdministrator();
			return;
		}
		if ($this->option('delete'))
		{
			$this->deleteAdministrator();
			return;
		}
		if ($this->option('password'))
		{
			$this->changePassword();
			return;
		}
		$this->listAdministrators();
	}

	/**
	 * Get the console command options.
	 * @return array
	 */
	protected function getOptions()
	{
		return [
			[
				'new',
				null,
				InputOption::VALUE_NONE,
				'Create new administrator.'
			],
			[
				'delete',
				null,
				InputOption::VALUE_NONE,
				'Delete administrator.'
			],
			[
				'password',
				null,
				InputOption::VALUE_NONE,
				'Change administrator password.'
			]
		];
	}

	protected function createNewAdministrator()
	{
		$username = $this->ask('Username:');
		if (is_null($username)) return;

		$password = $this->secret('Password:');
		if (is_null($password)) return;

		$passwordConfirm = $this->secret('Password Confirm:');
		if (is_null($passwordConfirm)) return;

		if ($password !== $passwordConfirm)
		{
			$this->error('Password confirm failed.');
			return;
		}

		$name = $this->ask('Administrator Name:');

		try
		{
			Administrator::create([
				'username' => $username,
				'password' => $password,
				'name'     => $name,
			]);
		} catch (\Exception $e)
		{
			$this->error('Some error occurred. Administrator wasnt created.');
			return;
		}
		$this->info('Administrator ' . $username . ' was successfully created!');
	}

	protected function getAdministrators()
	{
		$administrators = Administrator::orderBy('id', 'asc')->get();
		$result = [];
		foreach ($administrators as $administrator)
		{
			$result[$administrator->id] = '<info>' . $administrator->username . '</info>: ' . $administrator->name;
		}
		return $result;
	}

	protected function listAdministrators()
	{
		$administrators = $this->getAdministrators();
		foreach ($administrators as $administrator)
		{
			$this->line($administrator);
		}
	}

	protected function deleteAdministrator()
	{
		$id = $this->selectAdministrator('Which administrator we deleting?');

		$confirm = $this->confirm('Are you sure want to delete administrator with id ' . $id . '?', false);
		if ( ! $confirm) return;

		Administrator::destroy($id);
		$this->info('Administrator with id ' . $id . ' was deleted.');
	}

	protected function changePassword()
	{
		$id = $this->selectAdministrator('Which administrator password we changing?');

		$password = $this->secret('New password:');
		if (is_null($password)) return;

		$passwordConfirm = $this->secret('Password Confirm:');
		if (is_null($passwordConfirm)) return;

		if ($password !== $passwordConfirm)
		{
			$this->error('Password confirm failed.');
			return;
		}

		Administrator::find($id)->fill(['password' => $password])->save();

		$this->info('Password was changed.');
	}

	/**
	 * @return mixed
	 */
	protected function selectAdministrator($message)
	{
		$administrators = $this->getAdministrators();
		$result = $this->choice($message, $administrators);
		$flipped = array_flip($administrators);
		$id = $flipped[$result];
		return $id;
	}

}


<?php namespace SleepingOwl\Admin\Columns\Column;

use Illuminate\Support\Str;
use SleepingOwl\Admin\Admin;
use SleepingOwl\Admin\Columns\Interfaces\ColumnInterface;
use SleepingOwl\Admin\Models\ModelItem;
use Illuminate\Support\Collection;

abstract class BaseColumn implements ColumnInterface
{
	/**
	 * Column field name
	 *
	 * @var string
	 */
	protected $name;
	/**
	 * Column label
	 *
	 * @var string
	 */
	protected $label;
	/**
	 * Is column sortable?
	 *     false|true|'default'
	 *
	 * @var string|boolean
	 */
	protected $sortable;
	protected $sortableDest = 'asc';
	/**
	 * Html builder
	 *
	 * @var \SleepingOwl\Html\HtmlBuilder
	 */
	protected $htmlBuilder;
	/**
	 * Model item, that ownes this column
	 *
	 * @var ModelItem
	 */
	protected $modelItem;
	/**
	 * Appends to this column cells
	 *
	 * @var ColumnInterface[]
	 */
	protected $appends = [];
	/**
	 * Is this column hidden?
	 *
	 * @var bool
	 */
	protected $hidden = false;

	/**
	 * @param string $name
	 * @param string $label
	 */
	function __construct($name, $label = null)
	{
		$this->name = $name;
		if (is_null($label))
		{
			$this->label = ucwords(str_replace('_', ' ', $name));
		} else
		{
			$this->label = $label;
		}
		$this->sortable(true);
		$this->htmlBuilder = Admin::instance()->htmlBuilder;
		$this->modelItem = ModelItem::$current;
	}

	/**
	 * Set sortable property
	 *
	 * @param bool $sortable
	 */
	public function sortable($sortable)
	{
		$this->sortable = $sortable;
	}

	/**
	 * Set this column as default sortable for this model item
	 * @param string $dest
	 */
	public function sortableDefault($dest = 'asc')
	{
		$this->sortable = 'default';
		$this->sortableDest = $dest;
	}

	/**
	 * Is this column sortable?
	 *
	 * @return bool
	 */
	public function isSortable()
	{
		return $this->sortable !== false;
	}

	/**
	 * Is this column default sortable?
	 *
	 * @return bool
	 */
	public function isSortableDefault()
	{
		return $this->sortable === 'default';
	}

	/**
	 * Append column to this column cells
	 *
	 * @param ColumnInterface $append
	 */
	public function append(ColumnInterface $append)
	{
		$this->appends[] = $append;
	}

	/**
	 * Render column header
	 *
	 * @return string
	 */
	public function renderHeader()
	{
		return $this->htmlBuilder->tag('th', $this->getAttributesForHeader(), $this->label);
	}

	/**
	 * Get attributes for column header tag
	 *
	 * @return array
	 */
	protected function getAttributesForHeader()
	{
		$attributes = [];
		if ( ! $this->isSortable())
		{
			$attributes['data-sortable'] = 'false';
		}
		if ($this->isSortableDefault())
		{
			$attributes['data-sortable-default'] = $this->sortableDest;
		}
		$attributes['style'] = 'width:10px;';
		return $attributes;
	}

	/**
	 * Render column cell
	 *
	 * @param $instance
	 * @param int $totalCount
	 * @param string $content
	 * @return string
	 */
	public function render($instance, $totalCount, $content = null)
	{
		if (is_null($content))
		{
			$content = $this->valueFromInstance($instance, $this->name);
		}
		$content = $this->renderAppends($instance, $totalCount, $content);
		return $this->htmlBuilder->tag('td', $this->getAttributesForCell($instance), $content);
	}

	/**
	 * Get attributes for column cell tag
	 *
	 * @return array
	 */
	protected function getAttributesForCell($instance)
	{
		return [];
	}

	/**
	 * Render column cells appends
	 *
	 * @param $instance
	 * @param $totalCount
	 * @param $content
	 * @return string
	 */
	protected function renderAppends($instance, $totalCount, $content)
	{
		$appends = [$content];
		foreach ($this->appends as $append)
		{
			$appends[] = $append->render($instance, $totalCount);
		}
		return implode(' ', $appends);
	}

	/**
	 * Get value from $instance by $name
	 * Use dot delimiter and search recursive
	 *
	 * @param $instance
	 * @param string $name
	 * @return mixed
	 */
	public function valueFromInstance($instance, $name)
	{
		$result = $instance;
		$parts = explode('.', $name);
		foreach ($parts as $part)
		{
			if ($result instanceof Collection)
			{
				$result = $result->lists($part);
			} elseif (is_null($result))
			{
				$result = null;
			} else
			{
				$result = $result->$part;
			}
		}
		if (is_string($result))
		{
			$result = e($result);
		}
		return $result;
	}

	/**
	 * @return bool
	 */
	public function isHidden()
	{
		return $this->hidden;
	}

	/**
	 * @return string
	 */
	public function getName()
	{
		return $this->name;
	}

}


<?php namespace SleepingOwl\Admin\Controllers;

use App;
use Config;
use Illuminate\Routing\Controller;
use SleepingOwl\RandomFilenamer\RandomFilenamer;
use Exception;
use Illuminate\Http\Response;
use Input;
use Lang;
use StdClass;
use Illuminate\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
use URL;

/**
 * Class ImagesController
 *
 * @package SleepingOwl\Admin\Controllers
 */
class ImagesController extends Controller
{
	/**
	 * @var Finder
	 */
	protected $finder;
	/**
	 * @var Filesystem
	 */
	protected $filesystem;

	/**
	 * @param Finder $finder
	 * @param Filesystem $filesystem
	 */
	function __construct(Finder $finder, Filesystem $filesystem)
	{
		$this->finder = $finder;
		$this->filesystem = $filesystem;
	}

	/**
	 * Get all images within "images" directory without "seeds" subdirectories
	 *
	 * @return array
	 */
	public function getAll()
	{
		$files = $this->getAllFiles();
		$result = [];
		foreach ($files as $file)
		{
			$result[] = $this->createImageObject($file);
		}
		return $result;
	}

	/**
	 * @return Finder
	 */
	protected function getAllFiles()
	{
		return $this->finder->create()->exclude('seeds')->files()->in(Config::get('admin.imagesDirectory'));
	}

	/**
	 * @param SplFileInfo $file
	 * @return StdClass
	 */
	protected function createImageObject(SplFileInfo $file)
	{
		$obj = new StdClass;
		$path = $file->getRelativePathname();
		$obj->url = route('imagecache', [
			'original',
			$path
		]);
		$obj->thumbnail = route('imagecache', [
			'small',
			$path
		]);
		return $obj;
	}

	/**
	 * Upload new image (uses "imgupload" ckeditor plugin, with little modifications in file naming)
	 */
	public function postUpload()
	{
		$imageDirectory = Config::get('admin.imagesUploadDirectory');
		$upload_dir = Config::get('admin.imagesDirectory') . '/' . $imageDirectory;

		$allowedExtensions = [
			'bmp',
			'gif',
			'jpg',
			'jpeg',
			'png'
		];

		$maxsize = 2000;
		$maxwidth = 9000;
		$maxheight = 8000;
		$minwidth = 10;
		$minheight = 10;

		$file = Input::file('upload');
		$errors = [];

		$extension = null;
		$width = 0;
		$height = 0;
		try
		{
			if (is_null($file))
			{
				$errors[] = Lang::get('admin::lang.ckeditor.upload.error.common');
				throw new Exception;
			}
			$extension = $file->guessClientExtension();
			if ( ! in_array($extension, $allowedExtensions))
			{
				$errors[] = Lang::get('admin::lang.ckeditor.upload.error.wrong_extension', ['file' => $file->getClientOriginalName()]);
				throw new Exception;
			}
			if ($file->getSize() > $maxsize * 1000)
			{
				$errors[] = Lang::get('admin::lang.ckeditor.upload.error.filesize_limit', ['size' => $maxsize]);
			}
			$image = App::make('image')->make($file);
			$width = $image->width();
			$height = $image->height();
			if ($width > $maxwidth || $height > $maxheight)
			{
				$errors[] = Lang::get('admin::lang.ckeditor.upload.error.imagesize_max_limit', [
					'width'     => $width,
					'height'    => $height,
					'maxwidth'  => $maxwidth,
					'maxheight' => $maxheight
				]);
			}
			if ($width < $minwidth || $height < $minheight)
			{
				$errors[] = Lang::get('admin::lang.ckeditor.upload.error.imagesize_min_limit', [
					'width'     => $width,
					'height'    => $height,
					'minwidth'  => $minwidth,
					'minheight' => $minheight
				]);
			}
		} catch (Exception $e)
		{
		}

		if ( ! empty($errors))
		{
			return '<script>alert("' . implode('\\n', $errors) . '");</script>';
		}

		$finalFilename = RandomFilenamer::get($upload_dir, $extension);
		$file = $file->move($upload_dir, $finalFilename);
		$CKEditorFuncNum = Input::get('CKEditorFuncNum');
		$url = URL::route('imagecache', [
			'original',
			$imageDirectory . '/' . $finalFilename
		]);
		$message = Lang::get('admin::lang.ckeditor.upload.success', [
			'size'   => number_format($file->getSize() / 1024, 3, '.', ''),
			'width'  => $width,
			'height' => $height
		]);
		$result = "window.parent.CKEDITOR.tools.callFunction($CKEditorFuncNum, '$url', '$message')";
		return '<script>' . $result . ';</script>';
	}

	public function getImage($template, $filename)
	{
		Config::set('session.driver', 'array');

		// find file
		$image_path = false;
		foreach (Config::get('imagecache.paths') as $path)
		{
			// don't allow '..' in filenames
			$image_path = $path . '/' . str_replace('..', '', $filename);
			if (file_exists($image_path) && is_file($image_path))
			{
				break;
			}
		}

		// abort if file not found
		if ($image_path === false)
		{
			App::abort(404);
		}

		// define template callback
		$callback = function ($image)
		{
			return $image->widen(80);
		};

		// image manipulation based on callback
		$content = app('image')->cache(function ($image) use ($image_path, $callback)
		{
			return $callback($image->make($image_path));
		}, Config::get('imagecache::lifetime'));

		// define mime type
		$mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $content);

		// return http response
		return new Response($content, 200, [
			'Content-Type'  => $mime,
			'Cache-Control' => 'max-age=' . (Config::get('imagecache::lifetime') * 60) . ', public',
			'Etag'          => md5($content)
		]);
	}
}


<?php namespace SleepingOwl\Admin;

use SleepingOwl\Admin\Exceptions\ValidationException;
use SleepingOwl\Html\FormBuilder;
use SleepingOwl\Admin\Validation\Validator;
use Illuminate\Support\ServiceProvider;
use Intervention\Image\ImageServiceProvider;

class AdminServiceProvider extends ServiceProvider
{
	/**
	 * @var array
	 */
	protected $commands = [
		'AdministratorsCommand',
		'InstallCommand',
		'ModelCommand'
	];

	/**
	 * Register the service provider.
	 * @return void
	 */
	public function register()
	{
		$this->registerCommands();

		$this->app->register('\Intervention\Image\ImageServiceProvider');
		$this->app->register('\SleepingOwl\AdminAuth\AdminAuthServiceProvider');

		$this->registerFormBuilder();

		$this->app->bind('SleepingOwl\Admin\Repositories\Interfaces\ModelRepositoryInterface', 'SleepingOwl\Admin\Repositories\ModelRepository');

		$this->app->bindShared('SleepingOwl\Admin\Admin', function ($app)
		{
			return Admin::instance();
		});
		$this->app->singleton('admin', 'SleepingOwl\Admin\Admin');
		$this->app->bind('admin.router', function ()
		{
			return Admin::instance()->router;
		});

		$this->registerValidateExceptionHandler();
	}

	/**
	 * Register the form builder instance.
	 * @return void
	 */
	protected function registerFormBuilder()
	{
		$this->app->bindShared('SleepingOwl\Html\FormBuilder', function ($app)
		{
			$htmlBuilder = $app->make('SleepingOwl\Html\HtmlBuilder');
			$form = new FormBuilder($htmlBuilder, $app['url'], $app['session.store']->getToken());

			return $form->setSessionStore($app['session.store']);
		});
	}

	/**
	 *
	 */
	public function boot()
	{
		$this->loadViewsFrom(__DIR__ . '/../../views', 'admin');
		$this->loadTranslationsFrom(base_path('resources/lang/'), 'admin');
		$this->mergeConfigFrom(__DIR__ . '/../../config/config.php', 'admin');

		$this->publishes([
			__DIR__ . '/../../config/config.php' => config_path('admin.php'),
		], 'config');

		$this->publishes([
			__DIR__ . '/../../migrations/' => base_path('/database/migrations'),
		], 'migrations');

		$this->publishes([
			__DIR__.'/../../../public/' => public_path('packages/sleeping-owl/admin/'),
		], 'assets');

		$this->publishes([
			__DIR__ . '/../../lang/' => base_path('resources/lang/'),
		], 'langs');

		app('SleepingOwl\Admin\Helpers\StartSession')->run();

		Admin::instance()->router->registerRoutes();
		$this->registerValidator();

		$this->registerFilters();
	}

	/**
	 *
	 */
	protected function registerValidator()
	{
		\Validator::resolver(function ($translator, $data, $rules, $messages, $customAttributes)
		{
			return new Validator($translator, $data, $rules, $messages, $customAttributes);
		});
	}

	protected function registerValidateExceptionHandler()
	{
	}

	/**
	 *
	 */
	protected function registerCommands()
	{
		foreach ($this->commands as $command)
		{
			$this->commands('SleepingOwl\Admin\Commands\\' . $command);
		}
	}

	protected function registerFilters()
	{
		require_once(__DIR__ . '/filters.php');
	}

	/**
	 * @return array
	 */
	public function provides()
	{
		return ['admin'];
	}

}


<?php namespace SleepingOwl\Admin\Commands;

use Config;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\App;
use Symfony\Component\Console\Input\InputOption;

class InstallCommand extends Command
{
	/**
	 * The console command name.
	 * @var string
	 */
	protected $name = 'admin:install';

	/**
	 * The console command description.
	 * @var string
	 */
	protected $description = 'Install the admin package';

	/**
	 * Execute the console command.
	 * @return void
	 */
	public function fire()
	{
		$title = $this->option('title');

		$this->call('vendor:publish', ['--provider' => 'Intervention\Image\ImageServiceProviderLaravel5']);
		$this->call('vendor:publish', ['--provider' => 'SleepingOwl\Admin\AdminServiceProvider']);

		$this->publishDB();

		$this->publishImagecacheConfig();
		$this->publishSelfConfig($title);

		$this->createBootstrapDirectory();
		$this->createMenuFile();
		$this->createBootstrapFile();
		$this->createDummyUserFile();

		$this->createPublicDefaultStructure();
	}

	/**
	 *
	 */
	protected function publishDB()
	{
		$this->call('migrate');

		$this->call('db:seed', [
			'--class' => 'SleepingOwl\\AdminAuth\\Database\\Seeders\\AdministratorsTableSeeder'
		]);
	}

	/**
	 *
	 */
	protected function createBootstrapDirectory()
	{
		$directory = Config::get('admin.bootstrapDirectory');

		if ( ! is_dir($directory))
		{
			$this->laravel['files']->makeDirectory($directory, 0755, true, true);
			$this->line('<info>Admin bootstrap directory was created:</info> ' . str_replace(base_path(), '', $directory));
		}
	}

	/**
	 *
	 */
	protected function createMenuFile()
	{
		$file = Config::get('admin.bootstrapDirectory') . '/menu.php';
		if ( ! file_exists($file))
		{
			$contents = $this->laravel['files']->get(__DIR__ . '/stubs/menu.stub');
			$this->laravel['files']->put($file, $contents);
			$this->line('<info>Menu file was created:</info> ' . str_replace(base_path(), '', $file));
		}
	}

	/**
	 *
	 */
	protected function createBootstrapFile()
	{
		$file = Config::get('admin.bootstrapDirectory') . '/bootstrap.php';
		if ( ! file_exists($file))
		{
			$contents = $this->laravel['files']->get(__DIR__ . '/stubs/bootstrap.stub');
			$this->laravel['files']->put($file, $contents);
			$this->line('<info>Bootstrap file was created:</info> ' . str_replace(base_path(), '', $file));
		}
	}

	/**
	 *
	 */
	protected function createDummyUserFile()
	{
		$file = Config::get('admin.bootstrapDirectory') . '/User.php';
		if ( ! file_exists($file))
		{
			$contents = $this->laravel['files']->get(__DIR__ . '/stubs/User.stub');
			$this->laravel['files']->put($file, $contents);
			$this->line('<info>Dummy user file was created:</info> ' . str_replace(base_path(), '', $file));
		}
	}

	/**
	 *
	 */
	protected function publishImagecacheConfig()
	{
		$file = config_path('imagecache.php');
		if ( ! file_exists($file))
		{
			$contents = $this->laravel['files']->get($file);
			$contents = str_replace('\'route\' => null,', '\'route\' => \'img/cache\',', $contents);
			$this->laravel['files']->put($file, $contents);
		}
	}

	/**
	 *
	 */
	protected function createPublicDefaultStructure()
	{
		$uploadsDirectory = public_path('images/uploads');
		if ( ! is_dir($uploadsDirectory))
		{
			$this->laravel['files']->makeDirectory($uploadsDirectory, 0755, true, true);
		}
		$filesDirectory = public_path('files');
		if ( ! is_dir($filesDirectory))
		{
			$this->laravel['files']->makeDirectory($filesDirectory, 0755, true, true);
		}
	}

	/**
	 * @param string|null $title
	 */
	protected function publishSelfConfig($title = null)
	{
		$file = config_path('admin.php');
		if ( ! is_null($title))
		{
			$contents = $this->laravel['files']->get($file);
			$contents = str_replace('Sleeping Owl administrator', $title, $contents);
			$this->laravel['files']->put($file, $contents);
		}
	}

	/**
	 * Get the console command options.
	 * @return array
	 */
	protected function getOptions()
	{
		return [
			[
				'title',
				null,
				InputOption::VALUE_REQUIRED,
				'Title for admin module.'
			],
		];
	}

}


<?php namespace SleepingOwl\Admin\Models;

use SleepingOwl\Admin\Admin;
use SleepingOwl\Admin\Columns\Column;
use SleepingOwl\Admin\Columns\Interfaces\ColumnInterface;
use SleepingOwl\Admin\Exceptions\MethodNotFoundException;
use SleepingOwl\Admin\Models\Filters\Filter;
use SleepingOwl\Admin\Models\Form\Form;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use SleepingOwl\Models\Interfaces\ModelWithOrderFieldInterface;

/**
 * Class ModelItem
 * @method $this as ()
 * @method $this denyCreating()
 * @method $this denyEditing(\Closure $closure)
 * @method $this denyDeleting(\Closure $closure)
 * @method $this denyEditingAndDeleting(\Closure $closure)
 */
class ModelItem
{
	/**
	 * @var ModelItem
	 */
	public static $current;

	/**
	 * @var string
	 */
	protected $modelClass;
	/**
	 * @var string
	 */
	protected $alias;
	/**
	 * @var ColumnInterface[]
	 */
	public $columns;
	/**
	 * @var string[]
	 */
	protected $with = [];
	/**
	 * @var Filter[]
	 */
	protected $filters;
	/**
	 * @var bool
	 */
	protected $orderable;
	/**
	 * @var bool|\Closure
	 */
	protected $creatable = true;
	/**
	 * @var bool|\Closure
	 */
	protected $editable = true;
	/**
	 * @var bool|\Closure
	 */
	protected $deletable = true;
	/**
	 * @var string
	 */
	protected $title;
	/**
	 * @var Form
	 */
	protected $form;
	/**
	 * @var \SleepingOwl\Html\HtmlBuilder
	 */
	protected $htmlBuilder;
	/**
	 * @var bool
	 */
	protected $async;
	/**
	 * @var bool
	 */
	protected $withjoinEnabled = true;
	/**
	 * @var bool
	 */
	protected $columnFilter = false;
	protected $stateSave = true;

	/**
	 * @param $modelClass
	 */
	function __construct($modelClass)
	{
		$this->htmlBuilder = Admin::instance()->htmlBuilder;
		$this->modelClass = $modelClass;
		$this->alias = $this->getAliasFromClass($modelClass);
		$this->columns = [];
		$this->filters = [];
		$this->form = null;

		Admin::instance()->models->addItem($this);

		$this->form = new Form;

		$this->orderable = true;
		if (class_exists($class = $this->getModelClass()))
		{
			$instance = new $class;
			$this->orderable = ! ($instance instanceof ModelWithOrderFieldInterface);
		}
	}

	/**
	 * @return string
	 */
	public function renderTableAttributes()
	{
		$attributes = [];
		if ( ! $this->orderable)
		{
			$attributes['data-ordering'] = 'false';
		}
		$attributes['data-statesave'] = $this->stateSave();
		if ($this->isAsync())
		{
			$url = Admin::instance()->router->routeToTable($this->getAlias(), \Input::all());
			$attributes['data-ajax'] = $url;
		}
		return $this->htmlBuilder->attributes($attributes);
	}

	/**
	 * @return boolean
	 */
	public function isOrderable()
	{
		return $this->orderable;
	}

	/**
	 * @return bool
	 */
	public function isCreatable()
	{
		if (is_bool($this->creatable)) return $this->creatable;
		return ! call_user_func($this->creatable);
	}

	/**
	 * @param $instance
	 * @return bool
	 */
	public function isEditable($instance)
	{
		if (is_bool($this->editable)) return $this->editable;
		return ! call_user_func($this->editable, $instance);
	}

	/**
	 * @param $instance
	 * @return bool
	 */
	public function isDeletable($instance)
	{
		if (is_bool($this->deletable)) return $this->deletable;
		return ! call_user_func($this->deletable, $instance);
	}

	/**
	 * @return mixed
	 */
	public function getAlias()
	{
		return $this->alias;
	}

	/**
	 * @param mixed $alias
	 * @return $this
	 */
	public function setAlias($alias)
	{
		$this->alias = $alias;
		return $this;
	}

	/**
	 * @return mixed
	 */
	public function getWith()
	{
		return $this->with;
	}

	/**
	 * Set eager load fields
	 *
	 * @param $params
	 * @return ModelItem
	 */
	public function with($params = null)
	{
		if ( ! is_array($params))
		{
			$params = func_get_args();
		}
		$this->with = $params;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getModelClass()
	{
		return $this->modelClass;
	}

	public function getModelTable()
	{
		$instance = new $this->modelClass;
		return $instance->getTable();
	}

	/**
	 * @return ColumnInterface[]
	 */
	public function getColumns()
	{
		return $this->columns;
	}

	public function getColumnByName($name)
	{
		foreach ($this->columns as $column)
		{
			if ($column->getName() === $name)
			{
				return $column;
			}
		}
		return null;
	}

	/**
	 * @param $callback
	 * @return $this
	 */
	public function columns($callback)
	{
		$old = static::$current;
		static::$current = $this;
		call_user_func($callback);
		Column::control();
		static::$current = $old;
		return $this;
	}

	/**
	 * @param ColumnInterface $column
	 */
	public function addColumn(ColumnInterface $column)
	{
		$this->columns[] = $column;
	}

	/**
	 * @param $title
	 * @return $this
	 */
	public function title($title)
	{
		$this->title = $title;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getTitle()
	{
		return $this->title;
	}

	/**
	 * @param $callback
	 * @return $this
	 */
	public function filters($callback)
	{
		$old = static::$current;
		static::$current = $this;
		call_user_func($callback);
		static::$current = $old;
		return $this;
	}

	/**
	 * @param $name
	 * @return Filter
	 */
	public static function filter($name)
	{
		return new Filter($name);
	}

	/**
	 * @param Filter $filter
	 */
	public function addFilter(Filter $filter)
	{
		$this->filters[] = $filter;
	}

	/**
	 * @param Builder $query
	 * @param $parameters
	 * @return array
	 */
	public function applyFilters(Builder $query, $parameters)
	{
		$titles = [];
		foreach ($this->filters as $filter)
		{
			$title = $filter->filter($query, $parameters);
			if ($title)
			{
				$titles[] = $title;
			}
		}
		return $titles;
	}

	/**
	 * @param $callback
	 * @return $this
	 */
	public function form($callback)
	{
		$old = static::$current;
		static::$current = $this;
		call_user_func($callback);
		static::$current = $old;
		return $this;
	}

	/**
	 * @return Form
	 */
	public function getForm()
	{
		return $this->form;
	}

	/**
	 * @param $modelClass
	 * @return string
	 */
	protected function getAliasFromClass($modelClass)
	{
		return Str::snake(Str::plural(class_basename($modelClass)));
	}

	/**
	 * @param $method
	 * @param $param
	 * @throws MethodNotFoundException
	 */
	public function __call($method, $param)
	{
		if ($method === 'as')
		{
			return call_user_func_array([
				$this,
				'setAlias'
			], $param);
		}
		if (preg_match('/^deny(?<types>.+)$/', $method, $attributes))
		{
			$closure = isset($param[0]) ? $param[0] : true;
			if (is_bool($closure)) $closure = ! $closure;

			$types = $attributes['types'];
			$types = strtolower(str_replace('ing', 'able', $types));
			$types = explode('and', $types);
			foreach ($types as $type)
			{
				$this->$type = $closure;
			}
			return $this;
		}
		throw new MethodNotFoundException(get_class($this), $method);
	}

	/**
	 * @return boolean
	 */
	public function isAsync()
	{
		return $this->async;
	}

	/**
	 * @param boolean $async
	 * @return $this
	 */
	public function async($async = true)
	{
		$this->async = $async;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function isWithJoinEnabled()
	{
		return $this->withjoinEnabled;
	}

	/**
	 * @return $this
	 */
	public function disableWithJoin()
	{
		$this->withjoinEnabled = false;
		return $this;
	}

	/**
	 * @param bool $state
	 * @return $this
	 */
	public function columnFilter($state = true)
	{
		$this->columnFilter = $state;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function isColumnFilter()
	{
		return $this->columnFilter;
	}

	public function stateSave($stateSave = null)
	{
		if (is_null($stateSave))
		{
			return $this->stateSave;
		}
		$this->stateSave = $stateSave;
		return $this;
	}

}


<?php namespace SleepingOwl\Admin\Models\Form;

use SleepingOwl\Admin\Admin;
use SleepingOwl\Admin\Models\Form\Interfaces\FormItemInterface;
use Illuminate\Support\Arr;
use Illuminate\Support\ViewErrorBag;

/**
 * Class Form
 * @package SleepingOwl\Admin\Models\Form
 */
class Form
{
	/**
	 * @var FormItemInterface[]
	 */
	protected $items;
	/**
	 * @var \SleepingOwl\Html\FormBuilder
	 */
	protected $formBuilder;
	/**
	 * @var
	 */
	public $instance;
	/**
	 * @var string
	 */
	protected $method;
	/**
	 * @var string
	 */
	protected $saveUrl;
	/**
	 * @var string
	 */
	protected $backUrl;
	/**
	 * @var ViewErrorBag
	 */
	protected $errors;
	/**
	 * @var array
	 */
	protected $values;

	function __construct()
	{
		$this->formBuilder = Admin::instance()->formBuilder;
		$this->items = [];
	}

	/**
	 * @return mixed
	 */
	public function getInstance()
	{
		return $this->instance;
	}

	/**
	 * @param mixed $instance
	 */
	public function setInstance($instance)
	{
		$this->instance = $instance;
		$this->setDefaults();
	}

	/**
	 * Set default values for instance
	 */
	public function setDefaults()
	{
		foreach ($this->items as $item)
		{
			$name = $item->getName();
			if ( ! is_null($name) && ! isset($this->instance->$name))
			{
				$this->instance->$name = $item->getDefault();
			}
		}
	}

	/**
	 * @param mixed $method
	 */
	public function setMethod($method)
	{
		$this->method = $method;
	}

	/**
	 * @param mixed $saveUrl
	 */
	public function setSaveUrl($saveUrl)
	{
		$this->saveUrl = $saveUrl;
	}

	/**
	 * @param mixed $errors
	 */
	public function setErrors($errors)
	{
		if (is_null($errors))
		{
			$errors = new ViewErrorBag;
		}
		$this->errors = $errors;
	}

	/**
	 * @param mixed $backUrl
	 */
	public function setBackUrl($backUrl)
	{
		$this->backUrl = $backUrl;
	}

	/**
	 * @return string
	 */
	public function render()
	{
		$content = [];
		$content[] = $this->formBuilder->model($this->instance, [
			'method' => $this->method,
			'url'    => $this->saveUrl,
			'errors' => $this->errors
		]);
		foreach ($this->items as $item)
		{
			$content[] = $item->render();
		}
		$content[] = $this->formBuilder->submitGroup($this->backUrl);
		$content[] = $this->formBuilder->close();

		return implode('', $content);
	}

	/**
	 * @return string
	 */
	public function __toString()
	{
		return $this->render();
	}

	/**
	 * @param $values
	 */
	public function setValues($values)
	{
		$this->values = $values;
	}

	public function getValueForName($name)
	{
		return Arr::get($this->values, $name, null);
	}

	public function addItem($item)
	{
		$this->items[] = $item;
	}

	public function getValidationRules()
	{
		$rules = [];
		foreach ($this->items as $item)
		{
			$rules[$item->getName()] = $item->getValidationRules();
		}
		return $rules;
	}

	public function updateRequestData(&$data)
	{
		foreach ($this->items as $item)
		{
			$item->updateRequestData($data);
		}
	}

}


<?php namespace SleepingOwl\RandomFilenamer;

use Illuminate\Support\Str;

class RandomFilenamer
{

	/**
	 * Get unique random filename with $extenstion within $path directory
	 * @param string $path
	 * @param string $extension
	 * @return string
	 */
	public static function get($path, $extension)
	{
		if ( ! Str::endsWith($path, '/'))
		{
			$path .= '/';
		}
		do
		{
			$name = Str::random(10) . '.' . $extension;
		} while (file_exists($path . $name));
		return $name;
	}

}


<?php namespace SleepingOwl\Admin\Models\Filters;

use SleepingOwl\Admin\Exceptions\MethodNotFoundException;
use SleepingOwl\Admin\Models\ModelItem;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;

/**
 * Class Filter
 * @method Filter as ()
 */
class Filter
{
	/**
	 * Filter field name
	 * @var string
	 */
	protected $name;
	/**
	 * Filter alias name in query
	 * @var string
	 */
	protected $alias;
	/**
	 * Filter title object to display as subtitle
	 * @var Title
	 */
	protected $title;
	/**
	 * Apply this filter scope
	 * @var string
	 */
	protected $scope;
	/**
	 * Value to filter by (ignoring query parameter)
	 * @var string
	 */
	protected $value;

	/**
	 * @param $name
	 */
	function __construct($name)
	{
		$this->name = $name;
		$this->alias = $name;
		$this->title = new Title;
		if ($modelItem = ModelItem::$current)
		{
			$modelItem->addFilter($this);
		}
	}

	/**
	 * Set static title and/or get title object
	 * @param null $title
	 * @return Title
	 */
	public function title($title = null)
	{
		return $this->title->title($title);
	}

	/**
	 * Set filter scope
	 * @param string $scope
	 * @return $this
	 */
	public function scope($scope)
	{
		$this->scope = $scope;
		return $this;
	}

	/**
	 * Run filter
	 * @param Builder $query
	 * @param array $parameters
	 * @return string
	 */
	public function filter(Builder $query, $parameters)
	{
		$parameter = $this->getParameter($parameters);
		if (is_null($parameter)) return null;

		if ( ! $this->applyScope($query, $parameter) && $this->name)
		{
			$query->where($this->name, '=', $parameter);
		}
		return $this->title->get($parameter);
	}

	/**
	 * Set static value for filter
	 * @param $value
	 * @return $this
	 */
	public function value($value)
	{
		$this->value = $value;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getAlias()
	{
		return $this->alias;
	}

	/**
	 * @param string $alias
	 */
	public function setAlias($alias)
	{
		$this->alias = $alias;
	}

	/**
	 * @param $name
	 * @param $arguments
	 * @throws MethodNotFoundException
	 */
	function __call($name, $arguments)
	{
		if ($name == 'as')
		{
			$this->setAlias(Arr::get($arguments, 0, null));
			return $this;
		}
		throw new MethodNotFoundException(get_class($this), $name);
	}

	/**
	 * Apply scope to $query
	 * @param Builder $query
	 * @param $parameter
	 * @return bool
	 */
	protected function applyScope(Builder $query, $parameter)
	{
		if ($this->scope)
		{
			$query->{$this->scope}($parameter);
			return true;
		}
		return false;
	}

	/**
	 * @param $parameters
	 * @return mixed
	 */
	protected function getParameter($parameters)
	{
		if (($parameter = Arr::get($parameters, $this->alias, null)) && ($this->value !== null))
		{
			$parameter = $this->value;
		}
		return $parameter;
	}

}


<?php namespace SleepingOwl\Admin\Commands;

use Config;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
use SleepingOwl\Admin\Commands\Compilers\ModelCompiler;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

class ModelCommand extends Command
{
	/**
	 * The console command name.
	 * @var string
	 */
	protected $name = 'admin:model';

	/**
	 * The console command description.
	 * @var string
	 */
	protected $description = 'Create new admin model file.';

	/**
	 * Execute the console command.
	 * @return void
	 */
	public function fire()
	{
		$compiler = new ModelCompiler($this, $this->getModelClass(), $this->option('title'));
		$compiler->parseColumns($this->option('columns'));
		$compiler->generateForm();

		$template = $this->getTemplate();
		$replacement = $compiler->getReplacements();
		$template = $this->makeReplacements($template, $replacement);

		$this->saveResult($compiler->getModelClass(), $template);
	}

	/**
	 * @return string
	 */
	protected function getModelClass()
	{
		$modelClass = str_replace('/', '\\', $this->argument('modelClass'));
		if ( ! class_exists($modelClass))
		{
			$this->error('Class "' . $modelClass . '" not found.');
			exit(1);
		}
		return $modelClass;
	}

	/**
	 * @return string
	 */
	protected function getTemplate()
	{
		return file_get_contents(__DIR__ . '/stubs/model.stub');
	}

	/**
	 * @param $template
	 * @param $replacement
	 * @return string
	 */
	protected function makeReplacements($template, $replacement)
	{
		return strtr($template, $replacement);
	}

	/**
	 * @param $modelClass
	 * @param $template
	 */
	protected function saveResult($modelClass, $template)
	{
		$filename = class_basename($modelClass) . '.php';
		$file = Config::get('admin.bootstrapDirectory') . '/' . $filename;
		if (file_exists($file))
		{
			$result = $this->confirm('File "' . $filename . '" already exist in your admin bootstrap directory. Overwrite?', false);
			if ( ! $result) return;
		}
		file_put_contents($file, $template);

		$this->info('File "' . $filename . '" was created.');
	}

	/**
	 * Get the console command arguments.
	 * @return array
	 */
	protected function getArguments()
	{
		return [
			[
				'modelClass',
				InputArgument::REQUIRED,
				'Model class name.'
			],
		];
	}

	/**
	 * Get the console command options.
	 * @return array
	 */
	protected function getOptions()
	{
		return [
			[
				'title',
				null,
				InputOption::VALUE_REQUIRED,
				'Model title to display in menu and headings.'
			],
			[
				'columns',
				null,
				InputOption::VALUE_REQUIRED,
				'Columns to display in table view.'
			],
		];
	}

}


<?php namespace SleepingOwl\Admin\Models\Filters;

use SleepingOwl\Admin\Exceptions\ModelAttributeNotFoundException;
use SleepingOwl\Admin\Exceptions\TitleNotFormattedException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException;

/**
 * Class Title
 */
class Title
{
	/**
	 * Default field name
	 */
	const DEFAULT_FIELD = 'title';
	/**
	 * Classname to load title from
	 *
	 * @var string
	 */
	protected $from;
	/**
	 * Field name to load title from
	 *
	 * @var string
	 */
	protected $field;
	/**
	 * Static title. If set - $from field is ignored
	 *
	 * @var string
	 */
	protected $staticTitle;

	/**
	 * Set static title
	 *
	 * @param string $title
	 * @return $this
	 */
	public function title($title)
	{
		$this->staticTitle = $title;
		return $this;
	}

	/**
	 * Set model classname and/or attribute to load title from
	 *
	 * @param string $modelClass
	 * @param string|null $field
	 * @return $this
	 */
	public function from($modelClass, $field = null)
	{
		$this->from = $modelClass;
		$this->field = $field ?: static::DEFAULT_FIELD;
		return $this;
	}

	/**
	 * Get title as string
	 *
	 * @param string $parameter
	 * @throws ModelAttributeNotFoundException
	 * @throws TitleNotFormattedException
	 * @throws ModelNotFoundException
	 * @return string
	 */
	public function get($parameter)
	{
		if ( ! is_null($this->staticTitle)) return $this->staticTitle;
		if (is_null($this->from))
		{
			throw new TitleNotFormattedException;
		}
		$from = $this->from;
		$property = $this->field;
		$model = new $this->from;
		$model = $this->getInstance($parameter, $model);
		if (isset($model->$property))
		{
			return $model->$property;
		}
		throw new ModelAttributeNotFoundException($from, $property);
	}

	/**
	 * Get instance by id
	 *
	 * @param string $id
	 * @param Model $model
	 * @throws ModelNotFoundException
	 * @return Model|null
	 */
	protected function getInstance($id, $model)
	{
		return $model->findOrFail($id);
	}
}


<?php namespace SleepingOwl\Html;

use Carbon\Carbon;
use Illuminate\Support\Arr;
use Illuminate\Html\FormBuilder as IlluminateFormBuilder;
use Illuminate\Support\ViewErrorBag;
use Lang;
use SleepingOwl\DateFormatter\DateFormatter;
use SleepingOwl\Models\Interfaces\ModelWithFileFieldsInterface;
use SleepingOwl\Models\Interfaces\ModelWithImageFieldsInterface;

/**
 * Class FormBuilder
 */
class FormBuilder extends IlluminateFormBuilder
{
	/**
	 * @var ViewErrorBag
	 */
	protected $errors;

	/**
	 * @param array $options
	 * @return string
	 */
	public function open(array $options = [])
	{
		$this->errors = array_get($options, 'errors');
		array_forget($options, 'errors');
		array_set($options, 'files', true);
		return parent::open($options);
	}

	/**
	 * Append element label and error label to form element and wrap it with div
	 *
	 * @param string $name
	 * @param string $label
	 * @param string $formElement
	 * @return string
	 */
	protected function makeGroup($name, $label, $formElement)
	{
		$content = '';
		$content .= $this->label($name, $label);
		$content .= $formElement;
		return $this->wrapContent($name, $content);
	}

	/**
	 * Append error label to element and wrap it with div
	 *
	 * @param string $name
	 * @param string $content
	 * @return string
	 */
	protected function wrapContent($name, $content)
	{
		$content .= $this->errors->first($name, $this->getErrorTemplate());
		$class = $this->getErrorClass($name);
		return $this->wrapGroup($content, compact('class'));
	}

	/**
	 * Wrap content with div
	 *
	 * @param string $content
	 * @param array $options
	 * @return string
	 */
	protected function wrapGroup($content, array $options = [])
	{
		$options = $this->addClassToOptions($this->getFormGroupClass(), $options);
		return $this->html->tag('div', $options, $content);
	}

	/**
	 * Add class to attributes array
	 *
	 * @param string $classToAdd
	 * @param array $options
	 * @return array
	 */
	protected function addClassToOptions($classToAdd, array $options = [])
	{
		$class = array_get($options, 'class', '');
		if (is_array($class))
		{
			$class[] = $classToAdd;
		} elseif ( ! empty($class))
		{
			$class .= ' ' . $classToAdd;
		} else
		{
			$class = $classToAdd;
		}
		array_set($options, 'class', $class);
		return $options;
	}

	/**
	 * @param $name
	 * @param null $value
	 * @param string $addon
	 * @param string $placement
	 * @param array $options
	 * @param array $wrapperOptions
	 * @return mixed
	 */
	public function textAddon($name, $value = null, $addon = '', $placement = 'before', $options = [],
							  $wrapperOptions = [])
	{
		$addonElement = $this->html->tag('span', ['class' => 'input-group-addon'], $addon);
		$inputElement = $this->text($name, $value, $options);
		if ($placement === 'before')
		{
			$content = $addonElement . $inputElement;
		} else
		{
			$content = $inputElement . $addonElement;
		}
		$wrapperOptions = $this->addClassToOptions('form-group input-group', $wrapperOptions);
		return $this->html->tag('div', $wrapperOptions, $content);
	}

	/**
	 * @param $name
	 * @param $label
	 * @param null $value
	 * @param array $options
	 * @param int $dateFormat
	 * @param int $timeFormat
	 * @return string
	 */
	public function datetime($name, $label, $value = null, array $options = [], $dateFormat = DateFormatter::SHORT,
							 $timeFormat = DateFormatter::NONE)
	{
		$value = $this->getValueAttribute($name, $value);
		if ( ! is_null($value))
		{
			$value = DateFormatter::format($value, $dateFormat, $timeFormat);
		}

		$options = $this->addClassToOptions('form-control', $options);
		$content = $this->textAddon($name, $value, '', 'after', $options, ['class' => 'datepicker']);
		return $this->makeGroup($name, $label, $content);
	}

	/**
	 * @param $name
	 * @param $label
	 * @param null $value
	 * @param array $options
	 * @return mixed
	 */
	public function textGroup($name, $label, $value = null, array $options = [])
	{
		$options = $this->updateOptions($options);
		return $this->makeGroup($name, $label, $this->text($name, $value, $options));
	}

	/**
	 * @param $name
	 * @param $label
	 * @param bool $showSeconds
	 * @param null $value
	 * @param array $options
	 * @return string
	 */
	public function timeGroup($name, $label, $showSeconds = false, $value = null, array $options = [])
	{
		$options = $this->updateOptions($options);

		$value = $this->getValueAttribute($name, $value);
		$value = DateFormatter::format($value, DateFormatter::NONE, $showSeconds ? DateFormatter::MEDIUM : DateFormatter::SHORT);

		$content = $this->text($name, $value, $options);
		$content .= $this->html->tag('span', ['class' => 'input-group-addon'], '');
		$content = $this->html->tag('div', ['class' => 'form-group input-group timepicker'], $content);
		return $this->makeGroup($name, $label, $content);
	}

	/**
	 * @param $name
	 * @param $label
	 * @param array $options
	 * @return mixed
	 */
	public function passwordGroup($name, $label, array $options = [])
	{
		$options = $this->updateOptions($options);
		return $this->makeGroup($name, $label, $this->password($name, $options));
	}

	/**
	 * @param $name
	 * @param $label
	 * @param string $addon
	 * @param string $placement
	 * @param null $value
	 * @param array $options
	 * @param array $wrapperAttributes
	 * @return mixed
	 */
	public function textAddonGroup($name, $label, $addon = '', $placement = 'before', $value = null,
								   array $options = [], array $wrapperAttributes = [])
	{
		$options = $this->updateOptions($options);
		return $this->makeGroup($name, $label, $this->textAddon($name, $value, $addon, $placement, $options, $wrapperAttributes));
	}

	/**
	 * @param $name
	 * @param $label
	 * @param null $value
	 * @param array $options
	 * @return mixed
	 */
	public function checkboxGroup($name, $label, $value = null, array $options = [])
	{
		$content = $this->hidden($name, 0, ['type' => 'hidden']);
		$content .= $this->labelWithoutEscaping($name, $this->checkbox($name, 1, $value, $options) . $label);
		$content = $this->html->tag('div', ['class' => 'checkbox'], $content);
		return $this->wrapContent($name, $content);
	}

	/**
	 * @param $name
	 * @param null $value
	 * @param array $options
	 * @return string
	 */
	public function labelWithoutEscaping($name, $value = null, $options = [])
	{
		$this->labels[] = $name;

		$options = $this->html->attributes($options);

		$value = $this->formatLabel($name, $value);

		return '' . $value . '';
	}

	/**
	 * @param $name
	 * @param $label
	 * @param array $list
	 * @param null $value
	 * @param array $options
	 * @return mixed
	 */
	public function selectGroup($name, $label, array $list = [], $value = null, array $options = [])
	{
		$options = $this->updateOptions($options);
		return $this->makeGroup($name, $label, $this->html->tag('div', [], $this->select($name, $list, $value, $options)));
	}

	/**
	 * @param $name
	 * @param $label
	 * @param null $value
	 * @param array $options
	 * @return mixed
	 */
	public function textareaGroup($name, $label, $value = null, array $options = [])
	{
		$options = $this->updateOptions($options);
		return $this->makeGroup($name, $label, $this->textarea($name, $value, $options));
	}

	/**
	 * @param $name
	 * @param $label
	 * @param $model
	 * @param array $options
	 * @return mixed
	 */
	public function imageGroup($name, $label, ModelWithImageFieldsInterface $model, array $options = [])
	{
		$options = $this->updateOptions($options);
		$content = '';
		if ($model->$name->exists())
		{
			$img = $this->html->tag('img', [
				'class'       => 'thumbnail',
				'src'         => $model->$name->thumbnail('admin_preview'),
				'width'       => '80px',
				'data-toggle' => 'tooltip',
				'title'       => $model->$name->info()
			]);
			$innerContent = $this->html->tag('a', [
				'href'        => $model->$name->thumbnail('original'),
				'data-toggle' => 'lightbox'
			], $img);
			$innerContent .= $this->html->tag('a', [
				'href'      => '#',
				'class'     => 'img-delete',
				'data-name' => $name,
			], ' ' . Lang::get('admin::lang.table.delete'));
			$innerContent .= '

';
			$content .= $this->html->tag('div', ['class' => 'img-container'], $innerContent);
		}
		$content .= $this->file($name, $options);
		return $this->makeGroup($name, $label, $content);
	}

	/**
	 * @param $name
	 * @param $label
	 * @param ModelWithFileFieldsInterface $model
	 * @param array $options
	 * @return mixed
	 */
	public function fileGroup($name, $label, ModelWithFileFieldsInterface $model, array $options = [])
	{
		$options = $this->updateOptions($options);
		$content = '';
		if ($model->$name->exists())
		{
			$link = $this->html->tag('a', [
				'href'        => $model->$name->link(),
				'title'       => Lang::get('admin::lang.table.download'),
				'data-toggle' => 'tooltip'
			], ' ' . $model->$name->info());
			$file = $this->html->tag('div', ['class' => 'thumbnail file-info'], $link);
			$content .= $this->html->tag('div', [], $file);
		}
		$content .= $this->file($name, $options);
		return $this->makeGroup($name, $label, $content);
	}

	/**
	 * @param $cancelUrl
	 * @return mixed
	 */
	public function submitGroup($cancelUrl)
	{
		$content = $this->submit(Lang::get('admin::lang.table.save'), ['class' => 'btn btn-primary']);
		$content .= ' ';
		$content .= $this->html->link($cancelUrl, Lang::get('admin::lang.table.cancel'), ['class' => 'btn btn-default']);
		return $this->wrapGroup($content);
	}

	/**
	 * @param string $name
	 * @param array $attributes
	 * @return null|string
	 */
	public function getIdAttribute($name, $attributes)
	{
		if ($id = parent::getIdAttribute($name, $attributes))
		{
			return $id;
		}
		if (Arr::get($attributes, 'type') !== 'hidden')
		{
			return $name;
		}
		return null;
	}

	/**
	 * @return mixed
	 */
	public function getModel()
	{
		return $this->model;
	}

	/**
	 * @return string
	 */
	protected function getFormGroupClass()
	{
		return 'form-group';
	}

	/**
	 * @param array $options
	 * @return array
	 */
	protected function updateOptions($options = [])
	{
		return $this->addClassToOptions('form-control', $options);
	}

	/**
	 * @return string
	 */
	protected function getErrorTemplate()
	{
		return '
:message
';
	}

	/**
	 * @param $name
	 * @return array
	 */
	protected function getErrorClass($name)
	{
		$class = [];
		if ($this->errors->has($name))
		{
			$class[] = 'has-error';
		}
		return $class;
	}

}


<?php namespace SleepingOwl\Admin;

use SleepingOwl\Admin\Exceptions\MethodNotFoundException;
use Illuminate\Config\Repository;
use Illuminate\Http\Response;
use Illuminate\Routing\Router as IlluminateRouter;
use Illuminate\Routing\UrlGenerator;
use Illuminate\Support\Str;

/**
 * Class Router
 *
 * @package SleepingOwl\Admin
 * @method string routeToTable($alias)
 * @method string routeToCreate()
 * @method string routeToStore()
 * @method string routeToEdit()
 * @method string routeToUpdate()
 * @method string routeToDestroy()
 * @method string routeToMoveup()
 * @method string routeToMovedown()
 */
class Router
{
	/**
	 * @var string
	 */
	public $prefix;
	/**
	 * @var string
	 */
	public $routePrefix;
	/**
	 * @var IlluminateRouter
	 */
	protected $laravelRouter;

	/**
	 * @var array
	 */
	public static $modelRoutes = [
		[
			'url'    => '{model}',
			'action' => 'table',
			'method' => 'get'
		],
		[
			'url'    => '{model}/create',
			'action' => 'create',
			'method' => 'get'
		],
		[
			'url'    => '{model}',
			'action' => 'store',
			'method' => 'post'
		],
		[
			'url'    => '{model}/{id}/edit',
			'action' => 'edit',
			'method' => 'get'
		],
		[
			'url'    => '{model}/{id}/update',
			'action' => 'update',
			'method' => 'put'
		],
		[
			'url'    => '{model}/{id}',
			'action' => 'destroy',
			'method' => 'delete'
		],
		[
			'url'    => '{model}/{id}/moveup',
			'action' => 'moveup',
			'method' => 'patch'
		],
		[
			'url'    => '{model}/{id}/movedown',
			'action' => 'movedown',
			'method' => 'patch'
		],
	];
	/**
	 * @var Repository
	 */
	private $config;
	/**
	 * @var UrlGenerator
	 */
	private $urlGenerator;

	/**
	 * @param IlluminateRouter $router
	 * @param Repository $config
	 * @param UrlGenerator $urlGenerator
	 * @param string $prefix
	 * @param string $routePrefix
	 */
	function __construct(IlluminateRouter $router, Repository $config, UrlGenerator $urlGenerator, $prefix,
						 $routePrefix = 'sleeping-owl-admin')
	{
		$this->laravelRouter = $router;
		$this->config = $config;
		$this->prefix = $prefix;
		$this->routePrefix = $routePrefix;
		$this->urlGenerator = $urlGenerator;
	}

	/**
	 * Register all admin routes
	 */
	public function registerRoutes()
	{
		$this->registerAssetsRoutes();
		$this->registerAuthRoutes();
		$this->registerImageCacheRoute();

		$models = Admin::instance()->models->getAllAliases();

		$this->laravelRouter->group([
			'prefix'    => $this->prefix,
			'before'    => $this->getBeforeFilters(),
			'namespace' => 'SleepingOwl\Admin\Controllers',
		], function () use ($models)
		{
			if (empty($models)) $models = ['__empty_models__'];
			$this->laravelRouter->group([
				'where' => ['model' => implode('|', $models)]
			], function ()
			{
				foreach (static::$modelRoutes as $route)
				{
					$url = $route['url'];
					$action = $route['action'];
					$method = $route['method'];
                    			$controller = isset($route['controller']) ? $route['controller'] : 'AdminController';
                    			$this->laravelRouter->$method($url, [
						'as'   => $this->routePrefix . '.table.' . $action,
						'uses' => $controller . '@' . $action
					]);
				}
			});

			$wildcardRoute = $this->laravelRouter->any('{wildcard?}', [
				'as'   => $this->routePrefix . '.wildcard',
				'uses' => 'AdminController@getWildcard'
			])->where('wildcard', '.*');
			$this->setRoutePriority($wildcardRoute, 0);
		});
	}

	/**
	 * Register routes to admin assets
	 */
	protected function registerAssetsRoutes()
	{
		# CKEditor file listing and upload
		$this->laravelRouter->group([
			'before'    => $this->getBeforeFilters(),
			'prefix'    => 'images',
			'namespace' => 'SleepingOwl\Admin\Controllers'
		], function ()
		{
			$this->laravelRouter->get('all', 'ImagesController@getAll');
			$this->laravelRouter->post('upload', 'ImagesController@postUpload');
		});
		$this->laravelRouter->get('js/{locale}/lang.js', [
			'prefix' => $this->prefix,
			'as'     => $this->routePrefix . '.lang',
			'uses'   => 'SleepingOwl\Admin\Controllers\LangController@getAll'
		]);
	}

	/**
	 * Register login and logout routes
	 */
	protected function registerAuthRoutes()
	{
		$this->laravelRouter->group([
			'prefix'    => $this->prefix,
			'namespace' => 'SleepingOwl\Admin\Controllers'
		], function ()
		{
			$this->laravelRouter->get('login', [
				'as'   => $this->routePrefix . '.login',
				'uses' => 'AuthController@getLogin'
			]);
			$this->laravelRouter->post('login', [
				'as'   => $this->routePrefix . '.login.post',
				'uses' => 'AuthController@postLogin'
			])->before('csrf');
			$this->laravelRouter->get('logout', [
				'as'   => $this->routePrefix . '.logout',
				'uses' => 'AuthController@getLogout'
			]);
		});
	}

	/**
	 *
	 */
	protected function registerImageCacheRoute()
	{
		$this->laravelRouter->get($this->prefix . '/img/cache/{template}/{filename}', [
			'as' => 'imagecache.admin',
			'uses' => 'SleepingOwl\Admin\Controllers\ImagesController@getImage',
		])->where(['filename' => '[ \w\\.\\/\\-]+']);
	}

	/**
	 * @return array
	 */
	protected function getBeforeFilters()
	{
		return $this->config->get('admin.beforeFilters');
	}

	/**
	 * @param string $asset
	 * @return string
	 */
	public function routeToAsset($asset)
	{
		return $this->urlGenerator->asset('packages/sleeping-owl/admin/' . $asset);
	}

	/**
	 * @param $locale
	 * @return string
	 */
	public function routeToLang($locale)
	{
		return $this->urlGenerator->route($this->routePrefix . '.lang', $locale);
	}

	/**
	 * @param string $model
	 * @param array $parameters
	 * @return string
	 */
	public function routeToModel($model, $parameters = [])
	{
		return $this->routeToTable($model, $parameters);
	}

	/**
	 * Get route to admin startpage
	 * @return string
	 */
	public function routeHome()
	{
		return $this->routeToWildcard('/');
	}

	/**
	 * @param $url
	 * @return string
	 */
	public function routeToWildcard($url)
	{
		return $this->urlGenerator->route($this->routePrefix . '.wildcard', ['wildcard' => $url]);
	}

	/**
	 * @param $action
	 * @return string
	 */
	public function routeToAuth($action)
	{
		return $this->urlGenerator->route($this->routePrefix . '.' . $action);
	}

	/**
	 * @param string $method
	 * @param array $parameters
	 * @throws MethodNotFoundException
	 */
	public function __call($method, $parameters)
	{
		if (preg_match('/^routeTo(?<routeName>[a-zA-Z]+)$/', $method, $matches))
		{
			$route = Str::camel($matches['routeName']);
			while (count($parameters) < 2)
			{
				$parameters[] = [];
			}
			if ( ! is_array($parameters[1]))
			{
				$parameters[1] = [$parameters[1]];
			}
			$routeParameters = $parameters[1];
			array_unshift($routeParameters, $parameters[0]);
			return $this->urlGenerator->route($this->routePrefix . '.table.' . $route, $routeParameters);
		}
		if (method_exists($this->laravelRouter, $method))
		{
			return $this->laravelRouter->group([
				'prefix' => $this->prefix,
				'before' => $this->getBeforeFilters(),
			], function () use ($method, $parameters)
			{
				call_user_func_array([
					$this->laravelRouter,
					$method
				], $parameters);
			});
		}
		throw new MethodNotFoundException(get_class($this), $method);
	}

	protected function setRoutePriority($wildcardRoute, $priority)
	{
		if (method_exists($wildcardRoute, 'setPriority'))
		{
			$wildcardRoute->setPriority($priority);
		}
	}

}


<?php declare(strict_types=1);

namespace Lmc\Steward\Process;

use Assert\Assertion;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Vertex;
use Graphp\Algorithms\Tree\OutTree;
use Lmc\Steward\Publisher\AbstractPublisher;

/**
 * Set of Test processes.
 */
class ProcessSet implements \Countable
{
    /**
     * Array of wrapped Processes, indexed by testcase fully qualified name
     *
     * @var ProcessWrapper[]
     */
    protected $processes = [];
    /** @var AbstractPublisher|null */
    protected $publisher;
    /** @var Graph */
    protected $graph;
    /** @var OutTree */
    protected $tree;

    /**
     * Instantiate processSet to manage processes in different states,
     * If publisher is passed, it is used to publish process statuses after status changes.
     */
    public function __construct(AbstractPublisher $publisher = null)
    {
        $this->publisher = $publisher;

        $this->graph = new Graph();
    }

    public function setPublisher(AbstractPublisher $publisher): void
    {
        $this->publisher = $publisher;
    }

    /**
     * Get count of all processes in the set
     */
    public function count(): int
    {
        return count($this->processes);
    }

    /**
     * Add new process to the set.
     */
    public function add(ProcessWrapper $processWrapper): void
    {
        $className = $processWrapper->getClassName();
        if (isset($this->processes[$className])) {
            throw new \InvalidArgumentException(
                sprintf(
                    'Testcase with name "%s" was already added, make sure you don\'t have duplicate class names.',
                    $className
                )
            );
        }

        $this->processes[$className] = $processWrapper;

        $this->graph->createVertex($className);

        if ($this->publisher) {
            $this->publisher->publishResults($className, ProcessWrapper::PROCESS_STATUS_QUEUED, null);
        }
    }

    /**
     * Get array of processes in the set having given status
     *
     * @return ProcessWrapper[]
     */
    public function get(string $status): array
    {
        Assertion::choice($status, ProcessWrapper::PROCESS_STATUSES);

        $return = [];
        foreach ($this->processes as $className => $processWrapper) {
            if ($processWrapper->getStatus() === $status) {
                $return[$className] = $processWrapper;
            }
        }

        return $return;
    }

    /**
     * Build out-tree graph from defined Processes and their relations.
     *
     * @internal Should be called directly only in unit-testing
     */
    public function buildTree(): OutTree
    {
        if ($this->tree === null) {
            $root = $this->graph->createVertex(0);

            // Create edges directed from the root node
            foreach ($this->processes as $className => $processWrapper) {
                $vertex = $this->graph->getVertex($className);

                if ($processWrapper->getDelayMinutes() === null) { // doesn't depend on anything => link it to the root
                    $root->createEdgeTo($vertex)->setWeight(0);
                } else { // is dependant => link it to its dependency
                    // Throw error if dependency is to not existing vertex
                    if (!$this->graph->hasVertex($processWrapper->getDelayAfter())) {
                        throw new \InvalidArgumentException(
                            sprintf(
                                'Testcase "%s" has @delayAfter dependency on "%s", but this testcase was not defined.',
                                $className,
                                $processWrapper->getDelayAfter()
                            )
                        );
                    }

                    $this->graph->getVertex($processWrapper->getDelayAfter())
                        ->createEdgeTo($vertex)
                        ->setWeight($processWrapper->getDelayMinutes());
                }
            }
        }

        $this->tree = new OutTree($this->graph);

        if (!$this->tree->isTree()) {
            throw new \InvalidArgumentException(
                sprintf('Cannot build tree graph from tests dependencies. Probably some cyclic dependency is present.')
            );
        }

        return $this->tree;
    }

    /**
     * Optimize order of processes using given strategy.
     */
    public function optimizeOrder(OptimizeOrderInterface $optimizeStrategy): void
    {
        $optimizedOrder = $optimizeStrategy->optimize($this->buildTree());

        // Sort the $optimizedOrder array to have the same order as corresponding array of processes
        // (so that the array could be passed to array_multisort())
        $sortingArray = [];
        foreach ($this->processes as $processClassName => $processObject) {
            $sortingArray[$processClassName] = $optimizedOrder[$processClassName];
        }

        // Sort processes descending according to corresponding values in $sortingArray
        array_multisort($sortingArray, SORT_DESC, SORT_NUMERIC, $this->processes);
    }

    /**
     * Get count of processes status
     */
    public function countStatuses(): array
    {
        $statusesCount = [];
        foreach (ProcessWrapper::PROCESS_STATUSES as $status) {
            $statusesCount[$status] = count($this->get($status));
        }

        return $statusesCount;
    }

    /**
     * Get result counts of done processes
     */
    public function countResults(): array
    {
        $done = $this->get(ProcessWrapper::PROCESS_STATUS_DONE);
        $doneClasses = [];
        $resultsCount = [
            ProcessWrapper::PROCESS_RESULT_PASSED => 0,
            ProcessWrapper::PROCESS_RESULT_FAILED => 0,
            ProcessWrapper::PROCESS_RESULT_FATAL => 0,
        ];

        // Retrieve names of done processes and count their results
        foreach ($done as $className => $processObject) {
            $doneClasses[] = $className;
            $resultsCount[$processObject->getResult()]++;
        }

        return $resultsCount;
    }

    /**
     * Mark all dependant processes of given process as failed
     *
     * @return ProcessWrapper[] Processes that has been failed
     */
    public function failDependants(string $className): array
    {
        $descendantProcesses = $this->getDependencyTree($className);

        $failedProcesses = [];
        foreach ($descendantProcesses as $processClassName => $processWrapper) {
            $failedProcesses[$processClassName] = $processWrapper;
            $processWrapper->setStatus(ProcessWrapper::PROCESS_STATUS_DONE);
        }

        return $failedProcesses;
    }

    /**
     * Get all wrapped processes that depends on process of given name.
     *
     * @return ProcessWrapper[]
     */
    protected function getDependencyTree(string $className): array
    {
        Assertion::notEmpty($this->tree, 'Cannot get dependency tree - the tree was not yet build using buildTree()');

        $descendants = $this->tree->getVerticesDescendant($this->graph->getVertex($className));

        /** @var ProcessWrapper[] $descendantProcesses */
        $descendantProcesses = [];

        /** @var Vertex $descendant */
        foreach ($descendants as $descendant) {
            $descendantProcesses[$descendant->getId()] = $this->processes[$descendant->getId()];
        }

        return $descendantProcesses;
    }
}