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() );
		}
	}

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 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' );
	}

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'
)
;
}

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 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 header to page or taxonomy type.

@param  array $defaults

@return array

Add custom table header to page or taxonomy type .


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 custom table column to page or taxonomy type.

@param string $column_name
@param int    $post_id
@param int    $term_id

Add custom table column to page or taxonomy type .


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;
	}

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 ; }

Add entry type as a sortable column.

@param  array $columns

@return array


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;
	}

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 posts on load if `page_type` query string is set.

@param  WP_Query $query

@return WP_Query

Filter posts on load if page_type query string is set .


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
		}
	}


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;
	}

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
;
}

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

Format the value of the property before it s returned to the application .


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;
	}

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 labels from taxonomies.

@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;
	}

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 terms for specified taxonomy.

@param  string $taxonomy

@return array


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;
	}

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 ; }

Get matching value based on key from a term.

@param  mixed $term

@return mixed

Get matching value based on key from a term .


protected function prepare_value( $value ) {
		if ( is_string( $value ) && ( $value === 'true' || $value === 'on' ) || $value === true ) {
			return true;
		}

		return null;
	}

protected function prepare_value ( $ value ) { if ( is_string ( $ value ) && ( $ value === 'true' || $ value === 'on' ) || $ value === true ) { return true ; } return null ; }

Prepare property value.

@param  mixed $value

@return mixed


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);
    }

Search for stocks.

@param string $searchTerm

@return array|SearchResult[]

@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 historical data for a symbol.

@param string    $symbol
@param string    $interval
@param \DateTime $startDate
@param \DateTime $endDate

@return array|HistoricalData[]

@throws ApiException


public function getQuote($symbol)
    {
        $list = $this->fetchQuotes([$symbol]);

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

public function getQuote ( $ symbol ) { $ list = $ this -> fetchQuotes ( [ $ symbol ] ) ; return isset ( $ list [ 0 ] ) ? $ list [ 0 ] : null ; }

Get quote for a single symbol.

@param string $symbol

@return Quote|null


public function getExchangeRate($currency1, $currency2)
    {
        $list = $this->getExchangeRates([[$currency1, $currency2]]);

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

public function getExchangeRate ( $ currency1 , $ currency2 ) { $ list = $ this -> getExchangeRates ( [ [ $ currency1 , $ currency2 ] ] ) ; return isset ( $ list [ 0 ] ) ? $ list [ 0 ] : null ; }

Get exchange rate for two currencies. Accepts concatenated ISO 4217 currency codes.

@param string $currency1
@param string $currency2

@return Quote|null

Get exchange rate for two currencies . Accepts concatenated ISO 4217 currency codes .


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);
    }

Retrieves currency exchange rates. Accepts concatenated ISO 4217 currency codes such as "GBPUSD".

@param array $currencyPairs List of pairs of currencies

@return array|Quote[]

Retrieves currency exchange rates . Accepts concatenated ISO 4217 currency codes such as GBPUSD .


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);
    }

Fetch quote data from API.

@param array $symbols

@return array|Quote[]


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;
	}

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 ; }

Get attributes for column header tag

@return array


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);
	}

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 ) ; }

Render column cell

@param $instance
@param int $totalCount
@param string $content
@return string


protected function renderAppends($instance, $totalCount, $content)
	{
		$appends = [$content];
		foreach ($this->appends as $append)
		{
			$appends[] = $append->render($instance, $totalCount);
		}
		return implode(' ', $appends);
	}

protected
function
renderAppends
(
$
instance
,
$
totalCount
,
$
content
)
{
$
appends
=
[
$
content
]
;
foreach
(
$
this
->
appends
as
$
append
)
{
$
appends
[
]
=
$
append
->
render
(
$
instance
,
$
totalCount
)
;
}
return
implode
(
' '
,
$
appends
)
;
}

Render column cells appends

@param $instance
@param $totalCount
@param $content
@return string


public function getAll()
	{
		$files = $this->getAllFiles();
		$result = [];
		foreach ($files as $file)
		{
			$result[] = $this->createImageObject($file);
		}
		return $result;
	}

public function getAll ( ) { $ files = $ this -> getAllFiles ( ) ; $ result = [ ] ; foreach ( $ files as $ file ) { $ result [ ] = $ this -> createImageObject ( $ file ) ; } return $ result ; }

Get all images within "images" directory without "seeds" subdirectories

@return array

Get all images within images directory without seeds subdirectories


public function with($params = null)
	{
		if ( ! is_array($params))
		{
			$params = func_get_args();
		}
		$this->with = $params;
		return $this;
	}

public function with ( $ params = null ) { if ( ! is_array ( $ params ) ) { $ params = func_get_args ( ) ; } $ this -> with = $ params ; return $ this ; }

Set eager load fields

@param $params
@return ModelItem


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

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


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);
	}

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 title as string

@param string $parameter
@throws ModelAttributeNotFoundException
@throws TitleNotFormattedException
@throws ModelNotFoundException
@return string


protected function makeGroup($name, $label, $formElement)
	{
		$content = '';
		$content .= $this->label($name, $label);
		$content .= $formElement;
		return $this->wrapContent($name, $content);
	}

protected function makeGroup ( $ name , $ label , $ formElement ) { $ content = '' ; $ content .= $ this -> label ( $ name , $ label ) ; $ content .= $ formElement ; return $ this -> wrapContent ( $ name , $ content ) ; }

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 wrapContent($name, $content)
	{
		$content .= $this->errors->first($name, $this->getErrorTemplate());
		$class = $this->getErrorClass($name);
		return $this->wrapGroup($content, compact('class'));
	}

protected function wrapContent ( $ name , $ content ) { $ content .= $ this -> errors -> first ( $ name , $ this -> getErrorTemplate ( ) ) ; $ class = $ this -> getErrorClass ( $ name ) ; return $ this -> wrapGroup ( $ content , compact ( 'class' ) ) ; }

Append error label to element and wrap it with div

@param string $name
@param string $content
@return string


protected function wrapGroup($content, array $options = [])
	{
		$options = $this->addClassToOptions($this->getFormGroupClass(), $options);
		return $this->html->tag('div', $options, $content);
	}

protected function wrapGroup ( $ content , array $ options = [ ] ) { $ options = $ this -> addClassToOptions ( $ this -> getFormGroupClass ( ) , $ options ) ; return $ this -> html -> tag ( 'div' , $ options , $ content ) ; }

Wrap content with div

@param string $content
@param array $options
@return string


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;
	}

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
;
}

Add class to attributes array

@param string $classToAdd
@param array $options
@return array


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);
		});
	}

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 ) ; } ) ; }


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'
		]);
	}


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 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' ] ) ; } ) ; }


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);
        }
    }


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;
    }

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 ; }

Get array of processes in the set having given status

@return ProcessWrapper[]


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;
    }

Build out-tree graph from defined Processes and their relations.

@internal Should be called directly only in unit-testing

Build out - tree graph from defined Processes and their relations .


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);
    }

Optimize order of processes using given strategy.

Optimize order of processes using given strategy .


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

        return $statusesCount;
    }

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


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;
    }


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;
    }

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 ; }

Mark all dependant processes of given process as failed

@return ProcessWrapper[] Processes that has been failed


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;
    }

Get all wrapped processes that depends on process of given name.

@return ProcessWrapper[]

Get all wrapped processes that depends on process of given name .


public function output(string $output, string $identifier): void
    {
        if (empty($output)) {
            return;
        }

        $lines = explode("\n", $output);

        foreach ($lines as $line) {
            // color lines containing "[WARN]" or "[DEBUG]"
            if (mb_strpos($line, '[WARN]') !== false) {
                $line = '<fg=black;bg=yellow>' . $line . '</fg=black;bg=yellow>';
            } elseif (mb_strpos($line, '[DEBUG]') !== false) {
                $line = '<comment>' . $line . '</comment>';
            }

            $this->write($identifier . '> ');
            $this->writeln($line);
        }
    }


public function errorOutput(string $output, string $identifier): void
    {
        $output = rtrim($output);

        if (empty($output)) {
            return;
        }

        $lines = explode("\n", $output);

        foreach ($lines as $line) {
            $this->write('<error>' . $identifier . ' ERR> ');
            $this->writeln($line . '</>');
        }
    }

public
function
errorOutput
(
string
$
output
,
string
$
identifier
)
:
void
{
$
output
=
rtrim
(
$
output
)
;
if
(
empty
(
$
output
)
)
{
return
;
}
$
lines
=
explode
(
"\n"
,
$
output
)
;
foreach
(
$
lines
as
$
line
)
{
$
this
->
write
(
'<error>'
.
$
identifier
.
' ERR> '
)
;
$
this
->
writeln
(
$
line
.
'</>'
)
;
}
}


protected function initialize(InputInterface $input, OutputInterface $output): void
    {
        parent::initialize($input, $output);

        $output->writeln(
            sprintf(
                '<info>Steward</info> <comment>%s</comment> is running the tests...%s',
                $this->getApplication()->getVersion(),
                (!(new CiDetector())->isCiDetected() ? ' Just for you <fg=red><3</fg=red>!' : '')
            )
        );

        // If browser name or env is empty, ends initialization and let the Console/Command fail on input validation
        if (empty($input->getArgument(self::ARGUMENT_BROWSER))
            || empty($input->getArgument(self::ARGUMENT_ENVIRONMENT))
        ) {
            return;
        }

        // Browser name is case insensitive, normalize it to lower case
        $browserNormalized = mb_strtolower($input->getArgument(self::ARGUMENT_BROWSER));

        // Check if browser is supported
        if (!isset($this->supportedBrowsers[$browserNormalized])) {
            throw new \RuntimeException(
                sprintf(
                    'Browser "%s" is not supported (use one of: %s)',
                    $browserNormalized,
                    implode(', ', array_keys($this->supportedBrowsers))
                )
            );
        }

        // Set WebDriver browser identifier back to the argument value
        $input->setArgument(self::ARGUMENT_BROWSER, $this->supportedBrowsers[$browserNormalized]);

        if ($output->isVerbose()) {
            $output->writeln(sprintf('Browser: %s', $input->getArgument(self::ARGUMENT_BROWSER)));
            $output->writeln(sprintf('Environment: %s', $input->getArgument(self::ARGUMENT_ENVIRONMENT)));
        }

        // Initialize Selenium server adapter and normalize server URL
        $seleniumAdapter = $this->getSeleniumAdapter($input->getOption(self::OPTION_SERVER_URL));
        $input->setOption(self::OPTION_SERVER_URL, $seleniumAdapter->getServerUrl());

        // Make sure parallel-limit is greater than 0
        $parallelLimit = (int) $input->getOption(self::OPTION_PARALLEL_LIMIT);
        if ($parallelLimit === 0) {
            throw new \RuntimeException('Parallel limit must be a whole number greater than 0');
        }
        $input->setOption(self::OPTION_PARALLEL_LIMIT, $parallelLimit);

        $this->getDispatcher()->dispatch(
            CommandEvents::RUN_TESTS_INIT,
            new ExtendedConsoleEvent($this, $input, $output)
        );

        if ($output->isVeryVerbose()) {
            $output->writeln(
                sprintf('Path to logs: %s', $this->config[ConfigOptions::LOGS_DIR])
            );
            $output->writeln(
                sprintf('Ignore delays: %s', ($input->getOption(self::OPTION_IGNORE_DELAYS)) ? 'yes' : 'no')
            );
            $output->writeln(
                sprintf('Parallel limit: %d', $input->getOption(self::OPTION_PARALLEL_LIMIT))
            );
        }
    }

Initialize, check arguments and options values etc.

Initialize check arguments and options values etc .


public function getVersion(): ?string
    {
        if (!$this->version) {
            $this->version = self::getLatestVersion();
        }

        return $this->version;
    }

public function getVersion ( ) : ? string { if ( ! $ this -> version ) { $ this -> version = self :: getLatestVersion ( ) ; } return $ this -> version ; }

Get version that should be downloaded; if not set, attempt to retrieve latest released version

Get version that should be downloaded ; if not set attempt to retrieve latest released version


public function getFileUrl(): string
    {
        $version = $this->getVersion();
        Assert::that($version, 'Invalid version (expected format is X.Y.Z)')
            ->notEmpty()
            ->regex('/^\d+\.\d+\.[\da-z\-]+$/i');

        $versionParts = explode('.', $version);

        $devVersion = '';
        if (preg_match('/(\d+)-([[:alnum:]]+)/', $versionParts[2], $matches)) {
            $devVersion = $matches[2];
        }

        $fileUrl = self::$storageUrl . '/' . $versionParts[0] . '.' . $versionParts[1]
            . (!empty($devVersion) ? '-' . $devVersion : '')
            . '/' . $this->getFileName();

        return $fileUrl;
    }

Get complete URL of the file to download


public function download(): int
    {
        $targetPath = $this->getFilePath();

        if (!is_dir(dirname($targetPath))) {
            mkdir(dirname($targetPath), 0777, true);
        }

        $fileUrl = $this->getFileUrl();

        $fp = @fopen($fileUrl, 'rb');
        $responseHeaders = get_headers($fileUrl);
        if (mb_strpos($responseHeaders[0], '200 OK') === false) {
            throw new \RuntimeException(sprintf('Error downloading file "%s" (%s)', $fileUrl, $responseHeaders[0]));
        }

        $downloadedSize = file_put_contents($targetPath, $fp);
        // @codeCoverageIgnoreStart
        if (!$downloadedSize) {
            throw new \RuntimeException(sprintf('Error saving file to path "%s"', $targetPath));
        }
        // @codeCoverageIgnoreEnd

        return $downloadedSize;
    }

public
function
download
(
)
:
int
{
$
targetPath
=
$
this
->
getFilePath
(
)
;
if
(
!
is_dir
(
dirname
(
$
targetPath
)
)
)
{
mkdir
(
dirname
(
$
targetPath
)
,
0777
,
true
)
;
}
$
fileUrl
=
$
this
->
getFileUrl
(
)
;
$
fp
=
@
fopen
(
$
fileUrl
,
'rb'
)
;
$
responseHeaders
=
get_headers
(
$
fileUrl
)
;
if
(
mb_strpos
(
$
responseHeaders
[
0
]
,
'200 OK'
)
===
false
)
{
throw
new
\
RuntimeException
(
sprintf
(
'Error downloading file "%s" (%s)'
,
$
fileUrl
,
$
responseHeaders
[
0
]
)
)
;
}
$
downloadedSize
=
file_put_contents
(
$
targetPath
,
$
fp
)
;
// @codeCoverageIgnoreStart
if
(
!
$
downloadedSize
)
{
throw
new
\
RuntimeException
(
sprintf
(
'Error saving file to path "%s"'
,
$
targetPath
)
)
;
}
// @codeCoverageIgnoreEnd
return
$
downloadedSize
;
}

Execute the download

@throws \RuntimeException Thrown when file cannot be downloaded
@return int Downloaded size in bytes.


public function selectByVisiblePartialText($text): void
    {
        $this->openDropdownOptions();

        $this->log('Sending keys to select2: %s', $text);

        $inputSelector = WebDriverBy::cssSelector(
            $this->isMultiple() ? $this->select2Selector . ' input' : '#select2-drop input'
        );

        // Insert searched term into Select2 generated input
        $this->tc->wd
            ->findElement($inputSelector)
            ->sendKeys($text);

        // Wait until result are rendered (or maybe loaded with ajax)
        $firstResult = $this->tc->wd->wait()->until(
            WebDriverExpectedCondition::presenceOfElementLocated(
                WebDriverBy::cssSelector('.select2-drop .select2-result.select2-highlighted')
            )
        );

        $this->log('Dropdown detected, selecting the first result: %s', $firstResult->getText());

        // Select first item in results
        $firstResult->click();
    }

Select first option which text partially matches the argument.

@param string $text

Select first option which text partially matches the argument .


public function saveWithName($data, string $legacyName): void
    {
        $filename = $this->getLegacyFullPath($legacyName);
        $this->log('Saving data as Legacy "%s" to file "%s"', $legacyName, $filename);
        $this->debug('Legacy data: %s', $this->getPrintableValue($data));

        if (@file_put_contents($filename, serialize($data)) === false) {
            throw new LegacyException('Cannot save Legacy to file ' . $filename);
        }
    }

Store legacy of test under a custom name

@param string $legacyName filename to store the data if null getLegacyFilename is called to generate filename
from the test class name
@throws LegacyException


public function save($data, string $type = self::LEGACY_TYPE_CASE): void
    {
        $this->saveWithName($data, $this->getLegacyName($type));
    }

public function save ( $ data , string $ type = self :: LEGACY_TYPE_CASE ) : void { $ this -> saveWithName ( $ data , $ this -> getLegacyName ( $ type ) ) ; }

Store legacy of test getLegacyFilename is called to generate filename from the test class name

@param string $type LEGACY_TYPE_CASE (shared by all tests in test case)
or LEGACY_TYPE_TEST (shared only by the same test function)
@throws LegacyException


public function load(string $type = self::LEGACY_TYPE_CASE)
    {
        return $this->loadWithName($this->getLegacyName($type));
    }

public function load ( string $ type = self :: LEGACY_TYPE_CASE ) { return $ this -> loadWithName ( $ this -> getLegacyName ( $ type ) ) ; }

Reads legacy of test getLegacyFilename is called to generate filename from the test class name.
Raises exception if it is not found.

@param string $type LEGACY_TYPE_CASE (shared by all tests in test case)
or LEGACY_TYPE_TEST (shared only by the same test function)
@throws LegacyException

Reads legacy of test getLegacyFilename is called to generate filename from the test class name . Raises exception if it is not found .


public function loadWithName(string $legacyName)
    {
        $filename = $this->getLegacyFullPath($legacyName);

        $this->log('Reading Legacy "%s" from file "%s"', $legacyName, $filename);

        $data = @file_get_contents($filename);
        if ($data === false) {
            throw new LegacyException('Cannot read Legacy file ' . $filename);
        }

        $legacy = unserialize($data);
        if ($legacy === false) {
            throw new LegacyException('Cannot parse Legacy from file ' . $filename);
        }

        $this->debug('Legacy data: %s', $this->getPrintableValue($legacy));

        return $legacy;
    }

public
function
loadWithName
(
string
$
legacyName
)
{
$
filename
=
$
this
->
getLegacyFullPath
(
$
legacyName
)
;
$
this
->
log
(
'Reading Legacy "%s" from file "%s"'
,
$
legacyName
,
$
filename
)
;
$
data
=
@
file_get_contents
(
$
filename
)
;
if
(
$
data
===
false
)
{
throw
new
LegacyException
(
'Cannot read Legacy file '
.
$
filename
)
;
}
$
legacy
=
unserialize
(
$
data
)
;
if
(
$
legacy
===
false
)
{
throw
new
LegacyException
(
'Cannot parse Legacy from file '
.
$
filename
)
;
}
$
this
->
debug
(
'Legacy data: %s'
,
$
this
->
getPrintableValue
(
$
legacy
)
)
;
return
$
legacy
;
}

Reads legacy specified by custom name.
Raises exception if it is not found.

@param string $legacyName filename to store the data from the test class name
@throws LegacyException

Reads legacy specified by custom name . Raises exception if it is not found .


public function isAccessible(): bool
    {
        // Check connection to server is possible
        $seleniumConnection = @fsockopen(
            $this->serverUrlParts['host'],
            $this->serverUrlParts['port'],
            $connectionErrorNo,
            $connectionError,
            5
        );
        if (!is_resource($seleniumConnection)) {
            $this->lastError = $connectionError ?? 'unknown connection error';

            return false;
        }
        fclose($seleniumConnection);

        return true;
    }

Test if server URL is accessible and we can connected there


public function isSeleniumServer(): bool
    {
        // Check server properly responds to http requests
        $context = stream_context_create(['http' => ['ignore_errors' => true, 'timeout' => 5]]);
        $responseData = @file_get_contents($this->getServerUrl() . self::STATUS_ENDPOINT, false, $context);

        if (!$responseData) {
            $this->lastError = 'error reading server response';

            return false;
        }

        $decodedData = json_decode($responseData);

        if (!$decodedData) {
            $this->lastError = 'error parsing server JSON response (' . json_last_error_msg() . ')';

            return false;
        }

        $this->cloudService = $this->detectCloudServiceByStatus($decodedData);

        return true;
    }

Test if remote server is really a Selenium server and is ready to accept connection


public function getCloudService(): string
    {
        // If cloud service value is not yet initialized, attempt to connect to the server first
        if ($this->cloudService === null) {
            if (!$this->isSeleniumServer()) {
                throw new \RuntimeException(sprintf('Unable to connect to remote server: %s', $this->getLastError()));
            }
        }

        return $this->cloudService;
    }

Get name of the cloud service we are connected to.

@return string Cloud service identifier; empty string if no cloud service detected

Get name of the cloud service we are connected to .


protected function guessPort(string $host, string $scheme): int
    {
        if ($scheme === 'https') {
            return self::DEFAULT_PORT_CLOUD_SERVICE_HTTPS;
        }

        foreach (['saucelabs.com', 'browserstack.com', 'testingbot.com'] as $knownCloudHost) {
            if (mb_strpos($host, $knownCloudHost) !== false) {
                return self::DEFAULT_PORT_CLOUD_SERVICE;
            }
        }

        return self::DEFAULT_PORT;
    }

protected function guessPort ( string $ host , string $ scheme ) : int { if ( $ scheme === 'https' ) { return self :: DEFAULT_PORT_CLOUD_SERVICE_HTTPS ; } foreach ( [ 'saucelabs.com' , 'browserstack.com' , 'testingbot.com' ] as $ knownCloudHost ) { if ( mb_strpos ( $ host , $ knownCloudHost ) !== false ) { return self :: DEFAULT_PORT_CLOUD_SERVICE ; } } return self :: DEFAULT_PORT ; }


private function detectCloudServiceByStatus($responseData): string
    {
        if (isset($responseData->value, $responseData->value->build, $responseData->value->build->version)) {
            if ($responseData->value->build->version === 'Sauce Labs') {
                return self::CLOUD_SERVICE_SAUCELABS;
            } elseif ($responseData->value->build->version === 'TestingBot') {
                return self::CLOUD_SERVICE_TESTINGBOT;
            } elseif (!isset($responseData->class) && !isset($responseData->value->ready)) {
                return self::CLOUD_SERVICE_BROWSERSTACK;
            }
        }

        return '';
    }

private
function
detectCloudServiceByStatus
(
$
responseData
)
:
string
{
if
(
isset
(
$
responseData
->
value
,
$
responseData
->
value
->
build
,
$
responseData
->
value
->
build
->
version
)
)
{
if
(
$
responseData
->
value
->
build
->
version
===
'Sauce Labs'
)
{
return
self
::
CLOUD_SERVICE_SAUCELABS
;
}
elseif
(
$
responseData
->
value
->
build
->
version
===
'TestingBot'
)
{
return
self
::
CLOUD_SERVICE_TESTINGBOT
;
}
elseif
(
!
isset
(
$
responseData
->
class
)
&&
!
isset
(
$
responseData
->
value
->
ready
)
)
{
return
self
::
CLOUD_SERVICE_BROWSERSTACK
;
}
}
return
''
;
}

Detect cloud service using server status response

@param object $responseData


protected function buildProcess(string $fileName, array $phpunitArgs = []): Process
    {
        $capabilities = (new KeyValueCapabilityOptionsParser())
            ->parse($this->input->getOption(RunCommand::OPTION_CAPABILITY));

        $env = [
            'BROWSER_NAME' => $this->input->getArgument(RunCommand::ARGUMENT_BROWSER),
            'ENV' => mb_strtolower($this->input->getArgument(RunCommand::ARGUMENT_ENVIRONMENT)),
            'CAPABILITY' => json_encode($capabilities),
            'CAPABILITIES_RESOLVER' => $this->config[ConfigOptions::CAPABILITIES_RESOLVER],
            'SERVER_URL' => $this->input->getOption(RunCommand::OPTION_SERVER_URL),
            'LOGS_DIR' => $this->config[ConfigOptions::LOGS_DIR],
            'DEBUG' => $this->output->isDebug() ? '1' : '0',
        ];

        $dispatcher = $this->command->getDispatcher();
        $dispatcher->dispatch(
            CommandEvents::RUN_TESTS_PROCESS,
            $processEvent = new RunTestsProcessEvent(
                $this->command,
                $this->input,
                $this->output,
                $env,
                $phpunitArgs
            )
        );

        $phpunitExecutable = realpath(__DIR__ . '/../../bin/phpunit-steward');

        $commandLine = array_merge([PHP_BINARY, $phpunitExecutable], $processEvent->getArgs(), [$fileName]);

        return new Process($commandLine, STEWARD_BASE_DIR, $processEvent->getEnvironmentVars(), null, 3600);
    }

protected function buildProcess ( string $ fileName , array $ phpunitArgs = [ ] ) : Process { $ capabilities = ( new KeyValueCapabilityOptionsParser ( ) ) -> parse ( $ this -> input -> getOption ( RunCommand :: OPTION_CAPABILITY ) ) ; $ env = [ 'BROWSER_NAME' => $ this -> input -> getArgument ( RunCommand :: ARGUMENT_BROWSER ) , 'ENV' => mb_strtolower ( $ this -> input -> getArgument ( RunCommand :: ARGUMENT_ENVIRONMENT ) ) , 'CAPABILITY' => json_encode ( $ capabilities ) , 'CAPABILITIES_RESOLVER' => $ this -> config [ ConfigOptions :: CAPABILITIES_RESOLVER ] , 'SERVER_URL' => $ this -> input -> getOption ( RunCommand :: OPTION_SERVER_URL ) , 'LOGS_DIR' => $ this -> config [ ConfigOptions :: LOGS_DIR ] , 'DEBUG' => $ this -> output -> isDebug ( ) ? '1' : '0' , ] ; $ dispatcher = $ this -> command -> getDispatcher ( ) ; $ dispatcher -> dispatch ( CommandEvents :: RUN_TESTS_PROCESS , $ processEvent = new RunTestsProcessEvent ( $ this -> command , $ this -> input , $ this -> output , $ env , $ phpunitArgs ) ) ; $ phpunitExecutable = realpath ( __DIR__ . '/../../bin/phpunit-steward' ) ; $ commandLine = array_merge ( [ PHP_BINARY , $ phpunitExecutable ] , $ processEvent -> getArgs ( ) , [ $ fileName ] ) ; return new Process ( $ commandLine , STEWARD_BASE_DIR , $ processEvent -> getEnvironmentVars ( ) , null , 3600 ) ; }

Build Process instance for each testcase file


private function getExcludingGroups(array $excludeGroups, array $annotations): array
    {
        $excludingGroups = [];

        if (!empty($excludeGroups) && array_key_exists('group', $annotations)) {
            if (!empty(array_intersect($excludeGroups, $annotations['group']))) {
                $excludingGroups = array_intersect($excludeGroups, $annotations['group']);
            }
        }

        return $excludingGroups;
    }

private function getExcludingGroups ( array $ excludeGroups , array $ annotations ) : array { $ excludingGroups = [ ] ; if ( ! empty ( $ excludeGroups ) && array_key_exists ( 'group' , $ annotations ) ) { if ( ! empty ( array_intersect ( $ excludeGroups , $ annotations [ 'group' ] ) ) ) { $ excludingGroups = array_intersect ( $ excludeGroups , $ annotations [ 'group' ] ) ; } } return $ excludingGroups ; }

Get array of groups that cause this class to be excluded.

@return array Empty array is returned if class should not be excluded.

Get array of groups that cause this class to be excluded .


protected function takeSnapshot(AbstractTestCase $test): void
    {
        if (!$test->wd instanceof RemoteWebDriver) {
            $test->appendTestLog('[WARN] WebDriver instance not found, cannot take snapshot.');

            return;
        }

        $savePath = ConfigProvider::getInstance()->logsDir . DIRECTORY_SEPARATOR;
        $testIdentifier = $this->assembleTestIdentifier($test);

        ob_start();
        $outputBufferClosed = false;

        try {
            $currentUrl = $test->wd->getCurrentURL();
            // Save PNG screenshot
            $screenshotPath = $savePath . $testIdentifier . '.png';
            $test->wd->takeScreenshot($screenshotPath);
            // Save HTML snapshot of page
            $htmlPath = $savePath . $testIdentifier . '.html';
            file_put_contents($htmlPath, $test->wd->getPageSource());

            $bufferedOutput = ob_get_clean();
            $outputBufferClosed = true;
            $test->appendFormattedTestLog($bufferedOutput);

            $test->appendTestLog('');
            $test->appendTestLog('[WARN] Test failed on page "%s", taking page snapshots:', $currentUrl);
            $test->appendTestLog('Screenshot: "%s"', $this->getSnapshotUrl($screenshotPath));
            $test->appendTestLog('HTML snapshot: "%s"', $this->getSnapshotUrl($htmlPath));
            $test->appendTestLog('');
        } catch (WebDriverException $e) {
            $test->appendTestLog('[WARN] Error taking page snapshot, perhaps browser is not accessible?');

            return;
        } finally {
            if (!$outputBufferClosed) {
                $test->appendFormattedTestLog(ob_get_clean());
            }
        }
    }

Take screenshot and HTML snapshot of the page and save it.

Take screenshot and HTML snapshot of the page and save it .


protected function getSnapshotUrl(string $path): string
    {
        if (getenv('JENKINS_URL') && getenv('BUILD_URL') && getenv('WORKSPACE')) {
            $realPath = realpath($path);
            if ($realPath) {
                // from absolute path, remove workspace
                $path = str_replace(getenv('WORKSPACE'), '', $realPath);
                // prepend url to artifact
                $path = getenv('BUILD_URL') . 'artifact' . DIRECTORY_SEPARATOR . $path;
            }
        }

        return $path;
    }

protected
function
getSnapshotUrl
(
string
$
path
)
:
string
{
if
(
getenv
(
'JENKINS_URL'
)
&&
getenv
(
'BUILD_URL'
)
&&
getenv
(
'WORKSPACE'
)
)
{
$
realPath
=
realpath
(
$
path
)
;
if
(
$
realPath
)
{
// from absolute path, remove workspace
$
path
=
str_replace
(
getenv
(
'WORKSPACE'
)
,
''
,
$
realPath
)
;
// prepend url to artifact
$
path
=
getenv
(
'BUILD_URL'
)
.
'artifact'
.
DIRECTORY_SEPARATOR
.
$
path
;
}
}
return
$
path
;
}

Get url based on relative path of specific snapshot.
In our implementation we prepend artifact's URL to given relative path to make it clickable in Jenkins output.

Get url based on relative path of specific snapshot . In our implementation we prepend artifact s URL to given relative path to make it clickable in Jenkins output .


public function instantiate(EventDispatcher $dispatcher, string $dirToSearchForListeners): void
    {
        $listeners = $this->searchListeners($dirToSearchForListeners);

        foreach ($listeners as $listener) {
            $r = new \ReflectionClass($listener);
            if ($r->implementsInterface('Symfony\\Component\\EventDispatcher\\EventSubscriberInterface')
                && !$r->isAbstract()
            ) {
                /** @var EventSubscriberInterface $listenerInstance */
                $listenerInstance = $r->newInstanceWithoutConstructor();
                $dispatcher->addSubscriber($listenerInstance);
            }
        }
    }

Instantiate listeners in given directory and register them to given dispatcher


public function getFilePath(): string
    {
        if (!$this->fileDir) {
            $this->fileDir = ConfigProvider::getInstance()->logsDir;
        }

        return $this->fileDir . '/' . $this->fileName;
    }

public function getFilePath ( ) : string { if ( ! $ this -> fileDir ) { $ this -> fileDir = ConfigProvider :: getInstance ( ) -> logsDir ; } return $ this -> fileDir . '/' . $ this -> fileName ; }


protected function quoteXpathAttribute(string $input): string
    {
        if (mb_strpos($input, '\'') === false) { // Selector does not contain single quotes
            return "'$input'"; // Encapsulate with double quotes
        }

        if (mb_strpos($input, '"') === false) { // Selector contain single quotes but not double quotes
            return "\"$input\""; // Encapsulate with single quotes
        }

        // When both single and double quotes are contained, escape each part individually and concat() all parts
        return "concat('" . strtr($input, ['\'' => '\', "\'", \'']) . "')";
    }

Encapsulate given attribute value into valid xpath expression.

@param string $input Value of an xpath attribute selector

Encapsulate given attribute value into valid xpath expression .


public function optimize(OutTree $tree): array
    {
        // get root node of the tree
        $root = $tree->getVertexRoot();

        // get all root descendants vertices (without the root vertex itself)
        $children = $tree->getVerticesDescendant($root);

        // for each vertex (process) get maximum total weight of its subtree (longest distance)
        $subTreeMaxDistances = [];
        /** @var Vertex $childVertex */
        foreach ($children as $childVertex) {
            $alg = new Dijkstra($childVertex);
            // get map with distances to all linked reachable vertexes
            $distanceMap = $alg->getDistanceMap();
            // save the longest distance
            $subTreeMaxDistances[$childVertex->getId()] = $distanceMap ? max($distanceMap) : 0;
        }

        return $subTreeMaxDistances;
    }

Optimize order of tests in given tree based on their defined delay.
The aim is to run as first processes having the longest delay of their sub-dependencies.

@return array Array of [string key (= testclass fully qualified name) => int value (= test order)]

Optimize order of tests in given tree based on their defined delay . The aim is to run as first processes having the longest delay of their sub - dependencies .


private function castToGuessedDataType(string $value)
    {
        $stringValueWithoutQuotes = $this->removeEncapsulatingQuotes($value);
        if ($stringValueWithoutQuotes !== null) {
            return $stringValueWithoutQuotes;
        }

        $intValue = filter_var($value, FILTER_VALIDATE_INT, []);
        if ($intValue !== false) {
            return $intValue;
        }

        $floatValue = filter_var($value, FILTER_VALIDATE_FLOAT, []);
        if ($floatValue !== false) {
            return $floatValue;
        }

        $boolValue = filter_var($value, FILTER_VALIDATE_BOOLEAN, ['flags' => FILTER_NULL_ON_FAILURE]);
        if ($boolValue !== null) {
            return $boolValue;
        }

        return $value;
    }

private function castToGuessedDataType ( string $ value ) { $ stringValueWithoutQuotes = $ this -> removeEncapsulatingQuotes ( $ value ) ; if ( $ stringValueWithoutQuotes !== null ) { return $ stringValueWithoutQuotes ; } $ intValue = filter_var ( $ value , FILTER_VALIDATE_INT , [ ] ) ; if ( $ intValue !== false ) { return $ intValue ; } $ floatValue = filter_var ( $ value , FILTER_VALIDATE_FLOAT , [ ] ) ; if ( $ floatValue !== false ) { return $ floatValue ; } $ boolValue = filter_var ( $ value , FILTER_VALIDATE_BOOLEAN , [ 'flags' => FILTER_NULL_ON_FAILURE ] ) ; if ( $ boolValue !== null ) { return $ boolValue ; } return $ value ; }

Guess most appropriate data type acceptable by JSON


public function checkProcessTimeout(): ?string
    {
        try {
            $this->getProcess()->checkTimeout();
        } catch (ProcessTimedOutException $e) {
            $this->setStatus(self::PROCESS_STATUS_DONE);

            return sprintf(
                'Process for class "%s" exceeded the timeout of %d seconds and was killed.',
                $this->getClassName(),
                $e->getExceededTimeout()
            );
        }

        return '';
    }

Check if process is not running longer then specified timeout, return error message if so.

@return string Non-empty error message if process timeout exceeded

Check if process is not running longer then specified timeout return error message if so .


private function resolveResult(): string
    {
        $exitCode = $this->getProcess()->getExitCode();

        // If the process was not even started, mark its result as failed
        if ($exitCode === null) {
            return self::PROCESS_RESULT_FAILED;
        }

        switch ($exitCode) {
            case TestRunner::SUCCESS_EXIT: // all tests passed
                $result = self::PROCESS_RESULT_PASSED;
                // for passed process save just the status and result; end time was saved by TestStatusListener
                break;
            case 15: // Process killed because of timeout, or
            case 9: // Process terminated because of timeout
                $result = self::PROCESS_RESULT_FATAL;
                break;
            case 255: // PHP fatal error
                $result = self::PROCESS_RESULT_FATAL;
                break;
            case TestRunner::EXCEPTION_EXIT: // exception thrown from phpunit
            case TestRunner::FAILURE_EXIT: // some test failed
            default:
                $result = self::PROCESS_RESULT_FAILED;
                break;
        }

        return $result;
    }


private function getExecutors(): array
    {
        $testElements = $this->xml->xpath('//testcase/test[@status="done"]');
        $hasTestWithoutExecutor = false;
        $executors = [];

        foreach ($testElements as $testElement) {
            $executorValue = (string) $testElement['executor'];

            if (!empty($executorValue)) {
                $executors[] = $executorValue;
            } else {
                $hasTestWithoutExecutor = true;
            }
        }

        $executors = array_unique($executors);
        // sort and reindex
        sort($executors);
        // use executors index as a value and executor name as a key
        $executors = array_flip($executors);

        if ($hasTestWithoutExecutor) {
            $executors['unknown'] = 'unknown';
        }

        return $executors;
    }

Get array of unique executors

@return array Array of executors, key = executor URL, value = executor ID


protected function setupCiCapabilities(
        DesiredCapabilities $capabilities,
        AbstractTestCase $test
    ): DesiredCapabilities {
        $ci = $this->ciDetector->detect();
        $capabilities->setCapability(
            'build',
            $this->config->env . '-' . $ci->getBuildNumber()
        );
        $capabilities->setCapability(
            'tags',
            [$this->config->env, $ci->getCiName(), get_class($test)]
        );

        return $capabilities;
    }

protected function setupCiCapabilities ( DesiredCapabilities $ capabilities , AbstractTestCase $ test ) : DesiredCapabilities { $ ci = $ this -> ciDetector -> detect ( ) ; $ capabilities -> setCapability ( 'build' , $ this -> config -> env . '-' . $ ci -> getBuildNumber ( ) ) ; $ capabilities -> setCapability ( 'tags' , [ $ this -> config -> env , $ ci -> getCiName ( ) , get_class ( $ test ) ] ) ; return $ capabilities ; }

Setup capabilities specific for continuous integration server


public function setCustomConfigurationOptions(array $customConfigurationOptions): void
    {
        if ($this->config !== null) {
            throw new \RuntimeException(
                'Custom configuration options can be set only before initialization of configuration'
            );
        }

        $this->customConfigurationOptions = $customConfigurationOptions;
    }

Add custom configuration options that should be added to the default ones.
Can be set only before initialization, because the configuration is then immutable.

Note you cannot override the default configuration options.

@param array $customConfigurationOptions Array with values = configuration options (environment variables)

Add custom configuration options that should be added to the default ones . Can be set only before initialization because the configuration is then immutable .


private function retrieveConfigurationValues(array $options): array
    {
        $outputValues = [];

        foreach ($options as $option) {
            $value = getenv($option);

            // value was not retrieved => fail
            if ($value === false) {
                throw new \RuntimeException(sprintf('%s environment variable must be defined', $option));
            }

            $outputValues[Inflector::camelize(mb_strtolower($option))] = $value;
        }

        return $outputValues;
    }

Retrieve given configuration options values from environment. If value is not found, throw and exception.

@throws \RuntimeException
@return array Option => value option name is converted from CAPS_WITH_UNDERSCORES to camelCase

Retrieve given configuration options values from environment . If value is not found throw and exception .


protected function dequeueProcessesWithoutDelay(): void
    {
        $queuedProcesses = $this->processSet->get(ProcessWrapper::PROCESS_STATUS_QUEUED);

        foreach ($queuedProcesses as $className => $processWrapper) {
            if ($processWrapper->isDelayed()) {
                $this->io->writeln(
                    sprintf(
                        'Testcase "%s" is queued to be run %01.1f minutes after testcase "%s" is finished',
                        $className,
                        $processWrapper->getDelayMinutes(),
                        $processWrapper->getDelayAfter()
                    ),
                    OutputInterface::VERBOSITY_DEBUG
                );
            } elseif ($this->parallelLimitReached()) {
                $this->io->writeln(
                    sprintf('Max parallel limit reached, testcase "%s" is queued', $className),
                    OutputInterface::VERBOSITY_QUIET
                );
            } else {
                $this->io->writeln(
                    sprintf('Testcase "%s" is prepared to be run', $className),
                    OutputInterface::VERBOSITY_DEBUG
                );
                $processWrapper->setStatus(ProcessWrapper::PROCESS_STATUS_PREPARED);
            }
        }
    }

Set queued processes without delay as prepared


protected function flushProcessOutput(ProcessWrapper $processWrapper): void
    {
        $this->io->output(
            $processWrapper->getProcess()->getIncrementalOutput(),
            $processWrapper->getClassName()
        );
        $this->io->errorOutput(
            $processWrapper->getProcess()->getIncrementalErrorOutput(),
            $processWrapper->getClassName()
        );
    }

protected function flushProcessOutput ( ProcessWrapper $ processWrapper ) : void { $ this -> io -> output ( $ processWrapper -> getProcess ( ) -> getIncrementalOutput ( ) , $ processWrapper -> getClassName ( ) ) ; $ this -> io -> errorOutput ( $ processWrapper -> getProcess ( ) -> getIncrementalErrorOutput ( ) , $ processWrapper -> getClassName ( ) ) ; }


public function favorite($class)
    {
        return $this->favorites()->where('favoriteable_type', $class)->with('favoriteable')->get()->mapWithKeys(function ($item) {
            return [$item['favoriteable']->id=>$item['favoriteable']];
        });
    }

public function favorite ( $ class ) { return $ this -> favorites ( ) -> where ( 'favoriteable_type' , $ class ) -> with ( 'favoriteable' ) -> get ( ) -> mapWithKeys ( function ( $ item ) { return [ $ item [ 'favoriteable' ] -> id => $ item [ 'favoriteable' ] ] ; } ) ; }

Return a collection with the User favorited Model.
The Model needs to have the Favoriteable trait

@param  $class *** Accepts for example: Post::class or 'App\Post' ****
@return Collection

Return a collection with the User favorited Model . The Model needs to have the Favoriteable trait


private function countSummary($dstFile){
        $tests = (new \DOMXPath($dstFile))->query("//table/tr[contains(@class,'scenarioRow')]");
        foreach($tests as $test){
            $class = str_replace('scenarioRow ', '', $test->getAttribute('class'));
            switch($class){
                case 'scenarioSuccess':
                    $this->countSuccess += 0.5;
                    break;
                case 'scenarioFailed':
                    $this->countFailed += 0.5;
                    break;
                case 'scenarioSkipped':
                    $this->countSkipped += 0.5;
                    break;
                case 'scenarioIncomplete':
                    $this->countIncomplete += 0.5;
                    break;
            }
        }
    }

This function counts all types of tests' scenarios and writes in class members
@param $dstFile \DOMDocument - destination file

This function counts all types of tests scenarios and writes in class members


private function updateSummaryTable($dstFile){
        $dstFile = new \DOMXPath($dstFile);
        $pathFor = function ($type) { return "//div[@id='stepContainerSummary']//td[@class='$type']";};
        $dstFile->query($pathFor('scenarioSuccessValue'))->item(0)->nodeValue = $this->countSuccess;
        $dstFile->query($pathFor('scenarioFailedValue'))->item(0)->nodeValue = $this->countFailed;
        $dstFile->query($pathFor('scenarioSkippedValue'))->item(0)->nodeValue = $this->countSkipped;
        $dstFile->query($pathFor('scenarioIncompleteValue'))->item(0)->nodeValue = $this->countIncomplete;
    }

This function updates values in Summary block for each type of scenarios
@param $dstFile \DOMDocument - destination file


private function moveSummaryTable($dstFile,$node){
        $summaryTable = (new \DOMXPath($dstFile))->query("//div[@id='stepContainerSummary']")
            ->item(0)->parentNode->parentNode;
        $node->appendChild($dstFile->importNode($summaryTable,true));
    }

private function moveSummaryTable ( $ dstFile , $ node ) { $ summaryTable = ( new \ DOMXPath ( $ dstFile ) ) -> query ( "//div[@id='stepContainerSummary']" ) -> item ( 0 ) -> parentNode -> parentNode ; $ node -> appendChild ( $ dstFile -> importNode ( $ summaryTable , true ) ) ; }

This function moves Summary block in the bottom of result report
@param $dstFile \DOMDocument - destination file
@param $node \DOMNode - parent node of Summary table


private function updateButtons($dstFile){
        $nodes = (new \DOMXPath($dstFile))->query("//div[@class='layout']/table/tr[contains(@class, 'scenarioRow')]");
        for($i=2;$i<$nodes->length;$i+=2){
            $n = $i/2 + 1;
            $p = $nodes->item($i)->childNodes->item(1)->childNodes->item(1);
            $table = $nodes->item($i+1)->childNodes->item(1)->childNodes->item(1);
            $p->setAttribute('onclick',"showHide('$n', this)");
            $table->setAttribute('id',"stepContainer".$n);
        }
    }

This function updates "+" and "-" button for viewing test steps in final report
@param $dstFile \DOMDocument - destination file

This function updates + and - button for viewing test steps in final report


public function addFavorite($user_id = null)
	{
		$favorite = new Favorite(['user_id' => ($user_id) ? $user_id : Auth::id()]);
		$this->favorites()->save($favorite);
	}

public function addFavorite ( $ user_id = null ) { $ favorite = new Favorite ( [ 'user_id' => ( $ user_id ) ? $ user_id : Auth :: id ( ) ] ) ; $ this -> favorites ( ) -> save ( $ favorite ) ; }

Add this Object to the user favorites

@param  int $user_id  [if  null its added to the auth user]


public function removeFavorite($user_id = null)
	{
		$this->favorites()->where('user_id', ($user_id) ? $user_id : Auth::id())->delete();
	}

public function removeFavorite ( $ user_id = null ) { $ this -> favorites ( ) -> where ( 'user_id' , ( $ user_id ) ? $ user_id : Auth :: id ( ) ) -> delete ( ) ; }

Remove this Object from the user favorites

@param  int $user_id  [if  null its added to the auth user]


public function toggleFavorite($user_id = null)
	{
		$this->isFavorited($user_id) ? $this->removeFavorite($user_id) : $this->addFavorite($user_id) ;
	}

public function toggleFavorite ( $ user_id = null ) { $ this -> isFavorited ( $ user_id ) ? $ this -> removeFavorite ( $ user_id ) : $ this -> addFavorite ( $ user_id ) ; }

Toggle the favorite status from this Object

@param  int $user_id  [if  null its added to the auth user]


public function isFavorited($user_id = null)
	{
		return $this->favorites()->where('user_id', ($user_id) ? $user_id : Auth::id())->exists();
	}

public function isFavorited ( $ user_id = null ) { return $ this -> favorites ( ) -> where ( 'user_id' , ( $ user_id ) ? $ user_id : Auth :: id ( ) ) -> exists ( ) ; }

Check if the user has favorited this Object

@param  int $user_id  [if  null its added to the auth user]
@return boolean


public function favoritedBy()
	{
		return $this->favorites()->with('user')->get()->mapWithKeys(function ($item) {
            return [$item['user']->id => $item['user']];
        });
	}

public function favoritedBy ( ) { return $ this -> favorites ( ) -> with ( 'user' ) -> get ( ) -> mapWithKeys ( function ( $ item ) { return [ $ item [ 'user' ] -> id => $ item [ 'user' ] ] ; } ) ; }

Return a collection with the Users who marked as favorite this Object.

@return Collection

Return a collection with the Users who marked as favorite this Object .


public function getPermissions()
    {
        $permissions = [
            $this->manage_permission
        ];

        if ($this->create) {
            $permissions[] = $this->create_permission;
            $permissions[] = $this->store_permission;
        }
        if ($this->edit) {
            $permissions[] = $this->edit_permission;
            $permissions[] = $this->update_permission;
        }
        if ($this->delete) {
            $permissions[] = $this->delete_permission;
        }

        return $permissions;
    }

public function getPermissions ( ) { $ permissions = [ $ this -> manage_permission ] ; if ( $ this -> create ) { $ permissions [ ] = $ this -> create_permission ; $ permissions [ ] = $ this -> store_permission ; } if ( $ this -> edit ) { $ permissions [ ] = $ this -> edit_permission ; $ permissions [ ] = $ this -> update_permission ; } if ( $ this -> delete ) { $ permissions [ ] = $ this -> delete_permission ; } return $ permissions ; }

Return the permissions used in the module.

@return array

Return the permissions used in the module .


public function insertToLanguageFiles()
    {
        //Model singular version
        $model_singular = Str::singular($this->originalName);
        //Model Plural version
        $model_plural = Str::plural($this->originalName);
        //Model Plural key
        $model_plural_key = strtolower(Str::plural($this->model));
        //Path to that language files
        $path = resource_path('lang'.DIRECTORY_SEPARATOR.'en');
        //config folder path
        $config_path = config_path('module.php');
        //Creating directory if it isn't
        $this->createDirectory($path);
        //Labels file
        $labels = [
            'create'     => "Create $model_singular",
            'edit'       => "Edit $model_singular",
            'management' => "$model_singular Management",
            'title'      => "$model_plural",

            'table' => [
                'id'        => 'Id',
                'createdat' => 'Created At',
            ],
        ];
        //Pushing values to labels
        add_key_value_in_file($path.'/labels.php', [$model_plural_key => $labels], 'backend');
        //Menus file
        $menus = [
            'all'        => "All $model_plural",
            'create'     => "Create $model_singular",
            'edit'       => "Edit $model_singular",
            'management' => "$model_singular Management",
            'main'       => "$model_plural",
        ];
        //Pushing to menus file
        add_key_value_in_file($path.'/menus.php', [$model_plural_key => $menus], 'backend');
        //Exceptions file
        $exceptions = [
            'already_exists' => "That $model_singular already exists. Please choose a different name.",
            'create_error'   => "There was a problem creating this $model_singular. Please try again.",
            'delete_error'   => "There was a problem deleting this $model_singular. Please try again.",
            'not_found'      => "That $model_singular does not exist.",
            'update_error'   => "There was a problem updating this $model_singular. Please try again.",
        ];
        //Alerts File
        $alerts = [
            'created' => "The $model_singular was successfully created.",
            'deleted' => "The $model_singular was successfully deleted.",
            'updated' => "The $model_singular was successfully updated.",
        ];
        //Pushing to menus file
        add_key_value_in_file($path.'/alerts.php', [$model_plural_key => $alerts], 'backend');
        //Pushing to exceptions file
        add_key_value_in_file($path.'/exceptions.php', [$model_plural_key => $exceptions], 'backend');
        //config file "module.php"
        $config = [
            $model_plural_key => [
                'table' => $this->table,
            ],
        ];
        //Pushing to config file
        add_key_value_in_file($config_path, $config);
    }

This would enter the necessary language file contents to respective language files.

@param [array] $input

This would enter the necessary language file contents to respective language files .


public function createViewFiles()
    {
        //Getiing model
        $model = $this->model;
        //lowercase version of model
        $model_lower = strtolower($model);
        //lowercase and plural version of model
        $model_lower_plural = Str::plural($model_lower);
        //View folder name
        $view_folder_name = $model_lower_plural;
        //View path
        $path = escapeSlashes(strtolower(Str::plural($this->view_path)));
        //Header buttons folder
        $header_button_path = $path.DIRECTORY_SEPARATOR.'partials';
        //This would create both the directory name as well as partials inside of that directory
        $this->createDirectory(base_path($header_button_path));
        //Header button full path
        $header_button_file_path = $header_button_path.DIRECTORY_SEPARATOR."$model_lower_plural-header-buttons.blade";
        //Getting stub file content
        $header_button_contents = $this->files->get($this->getStubPath().'header-buttons.stub');
        if (!$this->create) {
            $header_button_contents = $this->delete_all_between('@create', '@endCreate', $header_button_contents);
        } else {
            $header_button_contents = $this->delete_all_between('@create', '@create', $header_button_contents);
            $header_button_contents = $this->delete_all_between('@endCreate', '@endCreate', $header_button_contents);
        }
        //Generate Header buttons file
        $this->generateFile(false, ['dummy_small_plural_model' => $model_lower_plural, 'dummy_small_model' => $model_lower], $header_button_file_path, $header_button_contents);
        //Index blade
        $index_path = $path.DIRECTORY_SEPARATOR.'index.blade';
        //Generate the Index blade file
        $this->generateFile('index_view', ['dummy_small_plural_model' => $model_lower_plural], $index_path);
        //Create Blade
        if ($this->create) {
            //Create Blade
            $create_path = $path.DIRECTORY_SEPARATOR.'create.blade';
            //Generate Create Blade
            $this->generateFile('create_view', ['dummy_small_plural_model' => $model_lower_plural, 'dummy_small_model' => $model_lower], $create_path);
        }
        //Edit Blade
        if ($this->edit) {
            //Edit Blade
            $edit_path = $path.DIRECTORY_SEPARATOR.'edit.blade';
            //Generate Edit Blade
            $this->generateFile('edit_view', ['dummy_small_plural_model' => $model_lower_plural, 'dummy_small_model' => $model_lower], $edit_path);
        }
        //Form Blade
        if ($this->create || $this->edit) {
            //Form Blade
            $form_path = $path.DIRECTORY_SEPARATOR.'form.blade';
            //Generate Form Blade
            $this->generateFile('form_view', [], $form_path);
        }
        //BreadCrumbs Folder Path
        $breadcrumbs_path = escapeSlashes('app\\Http\\Breadcrumbs\\Backend');
        //Breadcrumb File path
        $breadcrumb_file_path = $breadcrumbs_path.DIRECTORY_SEPARATOR.$this->model;
        //Generate BreadCrumb File
        $this->generateFile('Breadcrumbs', ['dummy_small_plural_model' => $model_lower_plural], $breadcrumb_file_path);
        //Backend File of Breadcrumb
        $breadcrumb_backend_file = $breadcrumbs_path.DIRECTORY_SEPARATOR.'Backend.php';
        //file_contents of Backend.php
        $file_contents = file_get_contents(base_path($breadcrumb_backend_file));
        //If this is already not there, then only append
        if (!strpos($file_contents, "require __DIR__.'/$this->model.php';")) {
            //Appending into BreadCrumb backend file
            file_put_contents(base_path($breadcrumb_backend_file), "\nrequire __DIR__.'/$this->model.php';", FILE_APPEND);
        }
    }

Creating View Files.

@param array $input


public function createMigration()
    {
        $table = $this->table;

        if (Schema::hasTable($table)) {
            return 'Table Already Exists!';
        } else {
            //Calling Artisan command to create table
            Artisan::call('make:migration', [
                'name'     => 'create_'.$table.'_table',
                '--create' => $table,
            ]);

            return Artisan::output();
        }
    }

Creating Table File.

@param array $input


public function createEvents()
    {
        if (!empty($this->events)) {
            $base_path = $this->event_namespace;

            foreach ($this->events as $event) {
                $path = escapeSlashes($base_path.DIRECTORY_SEPARATOR.$event);
                $model = str_replace(DIRECTORY_SEPARATOR, '\\', $path);

                Artisan::call('make:event', [
                    'name' => $model,
                ]);

                Artisan::call('make:listener', [
                    'name'    => $model.'Listener',
                    '--event' => $model,
                ]);
            }
        }
    }

public function createEvents ( ) { if ( ! empty ( $ this -> events ) ) { $ base_path = $ this -> event_namespace ; foreach ( $ this -> events as $ event ) { $ path = escapeSlashes ( $ base_path . DIRECTORY_SEPARATOR . $ event ) ; $ model = str_replace ( DIRECTORY_SEPARATOR , '\\' , $ path ) ; Artisan :: call ( 'make:event' , [ 'name' => $ model , ] ) ; Artisan :: call ( 'make:listener' , [ 'name' => $ model . 'Listener' , '--event' => $ model , ] ) ; } } }

Creating Event Files.

@param array $input


public function generateFile($stub_name, $replacements, $file, $contents = null)
    {
        if ($stub_name) {
            //Getting the Stub Files Content
            $file_contents = $this->files->get($this->getStubPath().$stub_name.'.stub');
        } else {
            //Getting the Stub Files Content
            $file_contents = $contents;
        }
        //Replacing the stub
        $file_contents = str_replace(
            array_keys($replacements),
            array_values($replacements),
            $file_contents
        );
        $this->files->put(base_path(escapeSlashes($file)).'.php', $file_contents);
    }

public
function
generateFile
(
$
stub_name
,
$
replacements
,
$
file
,
$
contents
=
)
{
if
(
$
stub_name
)
{
//Getting the Stub Files Content
$
file_contents
=
$
this
->
files
->
get
(
$
this
->
getStubPath
(
)
.
$
stub_name
.
'.stub'
)
;
}
else
{
//Getting the Stub Files Content
$
file_contents
=
$
contents
;
}
//Replacing the stub
$
file_contents
=
str_replace
(
array_keys
(
$
replacements
)
,
array_values
(
$
replacements
)
,
$
file_contents
)
;
$
this
->
files
->
put
(
base_path
(
escapeSlashes
(
$
file
)
)
.
'.php'
,
$
file_contents
)
;
}

Generating the file by
replacing placeholders in stub file.

@param $stub_name Name of the Stub File
@param $replacements [array] Array of the replacement to be done in stub file
@param $file [string] full path of the file
@param $contents [string][optional] file contents

Generating the file by replacing placeholders in stub file .


public function getStubPath()
    {
        $path = resource_path('views'.DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'generator'.DIRECTORY_SEPARATOR.'Stubs'.DIRECTORY_SEPARATOR);
        $package_stubs_path = base_path('vendor'.DIRECTORY_SEPARATOR.'bvipul'.DIRECTORY_SEPARATOR.'generator'.DIRECTORY_SEPARATOR.'src'.DIRECTORY_SEPARATOR.'views'.DIRECTORY_SEPARATOR.'Stubs'.DIRECTORY_SEPARATOR);
        if($this->files->exists($path))
            return $path;
        else
            return $package_stubs_path;
    }

public function getStubPath ( ) { $ path = resource_path ( 'views' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'generator' . DIRECTORY_SEPARATOR . 'Stubs' . DIRECTORY_SEPARATOR ) ; $ package_stubs_path = base_path ( 'vendor' . DIRECTORY_SEPARATOR . 'bvipul' . DIRECTORY_SEPARATOR . 'generator' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . 'Stubs' . DIRECTORY_SEPARATOR ) ; if ( $ this -> files -> exists ( $ path ) ) return $ path ; else return $ package_stubs_path ; }

getting the stub folder path.

@return string


public function checkNamespace(Request $request)
    {
        if (isset($request->path)) {
            $path = $this->parseModel($request->path);
            $path = base_path(trim(str_replace('\\', '/', $request->path)), '\\');
            $path = str_replace('App', 'app', $path);
            if (file_exists($path.'.php')) {
                return response()->json((object) [
                    'type'    => 'error',
                    'message' => 'File exists Already',
                ]);
            } else {
                return response()->json((object) [
                    'type'    => 'success',
                    'message' => 'File can be generated at this location',
                ]);
            }
        } else {
            return response()->json((object) [
                'type'    => 'error',
                'message' => 'Please provide some value',
            ]);
        }
    }

Checking the path for a file if exists.

@param Request $request

Checking the path for a file if exists .


public function checkTable(Request $request)
    {
        if ($request->table) {
            if (Schema::hasTable($request->table)) {
                return response()->json((object) [
                    'type'    => 'error',
                    'message' => 'Table exists Already',
                ]);
            } else {
                return response()->json((object) [
                    'type'    => 'success',
                    'message' => 'Table Name Available',
                ]);
            }
        } else {
            return response()->json((object) [
                'type'    => 'error',
                'message' => 'Please provide some value',
            ]);
        }
    }

public
function
checkTable
(
Request
$
request
)
{
if
(
$
request
->
table
)
{
if
(
Schema
::
hasTable
(
$
request
->
table
)
)
{
return
response
(
)
->
json
(
(
object
)
[
'type'
=>
'error'
,
'message'
=>
'Table exists Already'
,
]
)
;
}
else
{
return
response
(
)
->
json
(
(
object
)
[
'type'
=>
'success'
,
'message'
=>
'Table Name Available'
,
]
)
;
}
}
else
{
return
response
(
)
->
json
(
(
object
)
[
'type'
=>
'error'
,
'message'
=>
'Please provide some value'
,
]
)
;
}
}

Checking if the table exists.

@param Request $request


public function onOneRandomServer()
    {
        $keys = array_keys($this->getServerConfig());

        do {
            $randServerRank = array_rand($keys);
            if ($redis = $this->getRedisFromServerConfig($keys[$randServerRank])) {
                $this->selectedRedis = array($keys[$randServerRank] => $redis);
            } else {
                unset($keys[$randServerRank]);
            }
        } while (!empty($keys) && empty($this->selectedRedis));

        if (empty($this->selectedRedis)) {
            throw new Exception("Can't connect to a random redis server");
        }

        $this->multiRedis = false;

        return $this;
    }

select one random server

@throws Exception
@return $this


public function onAllServer($strict = true)
    {
        foreach ($this->getServerConfig() as $idServer => $config) {
            if ($redis = $this->getRedisFromServerConfig($idServer)) {
                $this->selectedRedis[$idServer] = $redis;
            } else {
                if ($strict) {
                    throw new Exception('cant connect to redis '.$idServer);
                }
            }
        }

        $this->multiRedis = true;

        return $this;
    }

public
function
onAllServer
(
$
strict
=
true
)
{
foreach
(
$
this
->
getServerConfig
(
)
as
$
idServer
=>
$
config
)
{
if
(
$
redis
=
$
this
->
getRedisFromServerConfig
(
$
idServer
)
)
{
$
this
->
selectedRedis
[
$
idServer
]
=
$
redis
;
}
else
{
if
(
$
strict
)
{
throw
new
Exception
(
'cant connect to redis '
.
$
idServer
)
;
}
}
}
$
this
->
multiRedis
=
true
;
return
$
this
;
}

select all the servers

@param bool $strict

@throws Exception
@return $this


public function onOneServer($idServer, $strict = true)
    {
        $redisList = $this->getServerConfig();

        // The server must exist..
        if (array_key_exists($idServer, $redisList)) {
            // and must be available
            if ($redis = $this->getRedisFromServerConfig($idServer)) {
                $this->selectedRedis[$idServer] = $redis;
            } else {
                if ($strict) {
                    throw new Exception('cant connect to redis ' . $idServer);
                }
            }
        } else {
            throw new Exception('unknown redis '.$idServer);
        }

        $this->multiRedis = false;

        return $this;
    }

public
function
onOneServer
(
$
idServer
,
$
strict
=
true
)
{
$
redisList
=
$
this
->
getServerConfig
(
)
;
// The server must exist..
if
(
array_key_exists
(
$
idServer
,
$
redisList
)
)
{
// and must be available
if
(
$
redis
=
$
this
->
getRedisFromServerConfig
(
$
idServer
)
)
{
$
this
->
selectedRedis
[
$
idServer
]
=
$
redis
;
}
else
{
if
(
$
strict
)
{
throw
new
Exception
(
'cant connect to redis '
.
$
idServer
)
;
}
}
}
else
{
throw
new
Exception
(
'unknown redis '
.
$
idServer
)
;
}
$
this
->
multiRedis
=
false
;
return
$
this
;
}

select one server

@param string  $idServer
@param boolean $strict

@throws Exception
@return $this


public function onOneKeyServer($key)
    {
        $this->selectedRedis[] = $this->getRedis($key);
        $this->multiRedis = false;

        return $this;
    }

public function onOneKeyServer ( $ key ) { $ this -> selectedRedis [ ] = $ this -> getRedis ( $ key ) ; $ this -> multiRedis = false ; return $ this ; }

select one server according to the key

@param string $key cache key

@return $this
@throws Exception when no redis server available


protected function callRedisCommand(PredisProxy $redis, $name, $arguments)
    {
        $start = microtime(true);
        try {
            $return = call_user_func_array(array($redis, $name), $arguments);
            $this->notifyEvent($name, $arguments, microtime(true) - $start);
        } catch (Predis\PredisException $e) {
            throw new Exception("Error calling the method ".$name." : ".$e->getMessage());
        }

        return $return;
    }

protected
function
callRedisCommand
(
PredisProxy
$
redis
,
$
name
,
$
arguments
)
{
$
start
=
microtime
(
true
)
;
try
{
$
return
=
call_user_func_array
(
array
(
$
redis
,
$
name
)
,
$
arguments
)
;
$
this
->
notifyEvent
(
$
name
,
$
arguments
,
microtime
(
true
)
$
start
)
;
}
catch
(
Predis
\
PredisException
$
e
)
{
throw
new
Exception
(
"Error calling the method "
.
$
name
.
" : "
.
$
e
->
getMessage
(
)
)
;
}
return
$
return
;
}

call a redis command

@param PredisProxy    $redis
@param string         $name
@param array          $arguments

@throws Exception
@return mixed


public function setCurrentDb($v)
    {
        if (!is_int($v)) {
            throw new Exception("please describe the db as an integer ^^");
        }
        if ($v == Cache::CACHE) {
            throw new Exception("cant use ".Cache::CACHE." in class ".__CLASS__);
        }
        $this->currentDb = $v;

        return $this;
    }

set the current db

@param integer $v

@throws Exception
@return object DB


public function del($keys)
    {
        if (!is_array($keys)) {
            $keys = array($keys);
        }
        $funcs = array();

        // séparation en serveur
        foreach ($keys as $key) {
            $keyP = $this->getPatternKey().$key;
            $funcs[$key] = function ($redis) use ($keyP) {
                $start = microtime(true);
                $count = $redis->del($keyP);
                if ((is_int($count)) and ($count > 0)) {
                    $this->notifyEvent('del', array($keyP), microtime(true) - $start);
                }

                return $count;
            };
        }

        if (true === $this->multi) {
            //throw new Exception("not implemented yet");
            foreach ($funcs as $k => $func) {
                $this->addToExecList($k, $func);
            }

            return $this;
        } else {
            $ret = 0;
            foreach ($funcs as $k => $func) {
                $ret += $func($this->getRedis($k)); // execution
            }

            return $ret;
        }
    }

delete a key

@param array $keys array of keys

@return integer