$label, 'value' => $value, 'change' => null === $change ? '' : self::signed( (float) $change ), 'tone' => null === $change || abs( (float) $change ) < 1 ? 'flat' : ( $change < 0 ? 'bad' : 'good' ), ); } /** * A before/now row for the details table. * * @param string $label Label. * @param float|null $before Before. * @param float|null $now Now. * @param string $kind number|ratio|position. * @return array */ public static function fact( $label, $before, $now, $kind = 'number' ) { $before = null === $before ? null : (float) $before; $now = null === $now ? null : (float) $now; if ( 'position' === $kind ) { $delta = null !== $before && null !== $now ? $now - $before : null; return array( 'label' => $label, 'before' => self::pos( $before ), 'now' => self::pos( $now ), 'change' => null === $delta ? '' : ( abs( $delta ) < 0.05 ? '0.0' : ( $delta > 0 ? '▼ ' : '▲ ' ) . number_format_i18n( abs( $delta ), 1 ) ), 'tone' => null === $delta || abs( $delta ) < 0.5 ? 'flat' : ( $delta > 0 ? 'bad' : 'good' ), ); } if ( 'ratio' === $kind ) { $pts = null !== $before && null !== $now ? ( $now - $before ) * 100 : null; return array( 'label' => $label, 'before' => self::pct( $before ), 'now' => self::pct( $now ), 'change' => null === $pts ? '' : self::pts( $pts ), 'tone' => null === $pts || abs( $pts ) < 0.1 ? 'flat' : ( $pts < 0 ? 'bad' : 'good' ), ); } $change = null !== $before && null !== $now ? Rules::change( $now, $before ) : null; return array( 'label' => $label, 'before' => self::num( $before ), 'now' => self::num( $now ), 'change' => null === $change ? '' : self::signed( $change ), 'tone' => null === $change || abs( $change ) < 1 ? 'flat' : ( $change < 0 ? 'bad' : 'good' ), ); } /** * The website's address from its Search Console property, or ''. * * @param Profile $site Site. * @return string */ public static function site_url( Profile $site ) { $prop = (string) $site->gsc_site; if ( 0 === strpos( $prop, 'sc-domain:' ) ) { return 'https://' . substr( $prop, 10 ) . '/'; } return 0 === strpos( $prop, 'http' ) ? $prop : ''; } /** * A readable name for a page, from its address: * /water-blasting-house-cleaning-cost/ → "Water blasting house cleaning cost page". * * @param string $url URL or path. * @return string */ public static function page_name( $url ) { $path = self::path( (string) $url ); $path = (string) strtok( $path, '?' ); $bits = array_values( array_filter( explode( '/', $path ), static function ( $part ) { return '' !== $part; } ) ); if ( ! $bits ) { return __( 'The home page', 'turtlereport' ); } $slug = rawurldecode( (string) end( $bits ) ); $slug = preg_replace( '/\.(html?|php|aspx?)$/i', '', $slug ); $name = trim( preg_replace( '/[-_]+/', ' ', $slug ) ); if ( '' === $name ) { return $path; } /* translators: %s: page name made from its address, e.g. "Pricing". */ return sprintf( __( 'The %s page', 'turtlereport' ), $name ); } /** * A page name for the middle of a sentence: "the pricing page". * * @param string $url URL or path. * @return string */ public static function page_ref( $url ) { return Text::lower_first( self::page_name( $url ) ); } /** * A nested value with a default. * * @param array $arr Array. * @param string[] $path Keys. * @param mixed $fallback Default. * @return mixed */ public static function val( $arr, array $path, $fallback = 0 ) { foreach ( $path as $key ) { if ( ! is_array( $arr ) || ! array_key_exists( $key, $arr ) ) { return $fallback; } $arr = $arr[ $key ]; } return null === $arr ? $fallback : $arr; } /** * A whole number for people. * * @param float|null $n Number. * @return string */ public static function num( $n ) { return null === $n ? '–' : number_format_i18n( round( (float) $n ) ); } /** * A ratio as a percentage, e.g. 0.034 → "3.4%". * * @param float|null $ratio Ratio. * @return string */ public static function pct( $ratio ) { return null === $ratio ? '–' : number_format_i18n( (float) $ratio * 100, 1 ) . '%'; } /** * Percentage points, e.g. -0.52 → "-0.5 pts". * * @param float $pts Points. * @return string */ public static function pts( $pts ) { $pts = (float) $pts; /* translators: %s: change in percentage points, e.g. "+0.4". */ return sprintf( __( '%s pts', 'turtlereport' ), ( $pts > 0.05 ? '+' : ( $pts < -0.05 ? '-' : '' ) ) . number_format_i18n( abs( $pts ), 1 ) ); } /** * A percentage change without its sign, e.g. -32.4 → "32%". * * @param float $change Change. * @return string */ public static function pct_abs( $change ) { return number_format_i18n( abs( round( (float) $change ) ) ) . '%'; } /** * A signed percentage change, e.g. "+12%" or "-32%". * * @param float $change Change. * @return string */ public static function signed( $change ) { $r = round( (float) $change ); return ( $r > 0 ? '+' : ( $r < 0 ? '-' : '' ) ) . number_format_i18n( abs( $r ) ) . '%'; } /** * Change from before to now as text, or "–". * * @param float $now Now. * @param float $before Before. * @return string */ public static function change_text( $now, $before ) { $change = Rules::change( (float) $now, (float) $before ); return null === $change ? '–' : self::signed( $change ); } /** * A position, one decimal. * * @param float|null $pos Position. * @return string */ public static function pos( $pos ) { return null === $pos || ! $pos ? '–' : number_format_i18n( (float) $pos, 1 ); } /** * A page URL shown as its path. * * @param string $url URL. * @return string */ public static function path( $url ) { $parts = wp_parse_url( (string) $url ); if ( ! is_array( $parts ) || empty( $parts['host'] ) ) { return (string) $url; } $path = isset( $parts['path'] ) && '' !== $parts['path'] ? $parts['path'] : '/'; return $path . ( isset( $parts['query'] ) ? '?' . $parts['query'] : '' ); } /** * A date range, e.g. "14 Sep – 11 Oct". * * @param string $s Start (Y-m-d). * @param string $e End (Y-m-d). * @return string */ public static function range( $s, $e ) { if ( '' === $s || '' === $e ) { return ''; } $fmt = static function ( $ymd ) { return wp_date( 'j M', strtotime( $ymd . ' 12:00:00 UTC' ), new \DateTimeZone( 'UTC' ) ); }; return $fmt( $s ) . ' – ' . $fmt( $e ); } }