*/ public static function goals() { return array( self::GOAL_ENQUIRIES => __( 'Get more enquiries / quote requests', 'turtlereport' ), self::GOAL_CALLS => __( 'Get more calls', 'turtlereport' ), self::GOAL_BOOKINGS => __( 'Get more bookings', 'turtlereport' ), self::GOAL_SALES => __( 'Increase online sales', 'turtlereport' ), self::GOAL_AWARENESS => __( 'Brand awareness', 'turtlereport' ), self::GOAL_CUSTOM => __( 'Custom goal', 'turtlereport' ), ); } /** * Nothing chosen. * * @return array{goal:string,custom:string,pages:string[],events:string[]} */ public static function defaults() { return array( 'goal' => '', 'custom' => '', 'pages' => array(), 'events' => array(), ); } /** * Cleans stored or posted priorities. * * @param mixed $raw Raw value. * @return array{goal:string,custom:string,pages:string[],events:string[]} */ public static function normalize( $raw ) { $out = self::defaults(); if ( ! is_array( $raw ) ) { return $out; } $goal = isset( $raw['goal'] ) && is_scalar( $raw['goal'] ) ? (string) $raw['goal'] : ''; $out['goal'] = isset( self::goals()[ $goal ] ) ? $goal : ''; $custom = isset( $raw['custom'] ) && is_scalar( $raw['custom'] ) ? trim( wp_strip_all_tags( (string) $raw['custom'] ) ) : ''; $out['custom'] = self::GOAL_CUSTOM === $out['goal'] ? Text::substr( $custom, 0, self::MAX_CUSTOM ) : ''; if ( self::GOAL_CUSTOM === $out['goal'] && '' === $out['custom'] ) { $out['goal'] = ''; } $pages = isset( $raw['pages'] ) ? $raw['pages'] : array(); if ( is_string( $pages ) ) { $pages = preg_split( '/[\r\n,]+/', $pages ); } foreach ( (array) $pages as $page ) { $path = is_scalar( $page ) ? self::path( (string) $page ) : ''; if ( '' !== $path && ! in_array( $path, $out['pages'], true ) && count( $out['pages'] ) < self::MAX_PAGES ) { $out['pages'][] = $path; } } foreach ( isset( $raw['events'] ) ? (array) $raw['events'] : array() as $event ) { $event = is_scalar( $event ) ? trim( (string) $event ) : ''; if ( '' !== $event && strlen( $event ) <= 100 && ! in_array( $event, $out['events'], true ) ) { $out['events'][] = $event; } } return $out; } /** * A page address as the path the reports compare: lowercase, no host, no * query or trailing slash ("/" for the home page). * * @param string $url Full URL or path. * @return string Path, or '' when it isn't one. */ public static function path( $url ) { $url = trim( (string) $url ); if ( '' === $url || strlen( $url ) > 500 ) { return ''; } if ( preg_match( '#^https?://#i', $url ) ) { $path = (string) wp_parse_url( $url, PHP_URL_PATH ); } elseif ( '/' === $url[0] ) { $path = (string) strtok( $url, '?#' ); } else { return ''; } $path = strtolower( rawurldecode( '' === $path ? '/' : $path ) ); if ( preg_match( '/[\s<>"]/', $path ) ) { return ''; } return strlen( $path ) > 1 ? rtrim( $path, '/' ) : '/'; } /** * A short name for the kind of page, only when the address says it clearly. * * @param string $path Path. * @return string */ public static function page_kind( $path ) { if ( '/' === $path ) { return __( 'Homepage', 'turtlereport' ); } if ( preg_match( '#(^|/)(contact|contact-us|get-in-touch)(/|$)#', $path ) ) { return __( 'Contact', 'turtlereport' ); } if ( preg_match( '#(quote|estimate|pricing|prices)#', $path ) ) { return __( 'Quote', 'turtlereport' ); } if ( preg_match( '#(book|booking|appointment|schedule)#', $path ) ) { return __( 'Booking', 'turtlereport' ); } return ''; } /** * Whether anything is chosen. * * @param array $p Normalised priorities. * @return bool */ public static function any( array $p ) { return '' !== $p['goal'] || $p['pages'] || $p['events']; } /** * The goal as a short phrase for tags, e.g. "Goal: more calls". * * @param array $p Normalised priorities. * @return string */ public static function goal_tag( array $p ) { switch ( $p['goal'] ) { case self::GOAL_ENQUIRIES: return __( 'Goal: more enquiries', 'turtlereport' ); case self::GOAL_CALLS: return __( 'Goal: more calls', 'turtlereport' ); case self::GOAL_BOOKINGS: return __( 'Goal: more bookings', 'turtlereport' ); case self::GOAL_SALES: return __( 'Goal: more sales', 'turtlereport' ); case self::GOAL_AWARENESS: return __( 'Goal: brand awareness', 'turtlereport' ); case self::GOAL_CUSTOM: /* translators: %s: the agency's own goal for the client. */ return sprintf( __( 'Goal: %s', 'turtlereport' ), $p['custom'] ); } return ''; } /** * Whether the goal is about conversions (enquiries, calls, bookings, sales). * * A custom goal isn't guessed at: it only labels the client. * * @param array $p Normalised priorities. * @return bool */ public static function wants_conversions( array $p ) { return in_array( $p['goal'], array( self::GOAL_ENQUIRIES, self::GOAL_CALLS, self::GOAL_BOOKINGS, self::GOAL_SALES ), true ); } /** * How much a Client Monitor alert touches this client's priorities. * * Level 2: it is about an important page or a priority event. Level 1: it * is the kind of change the goal depends on (conversions for enquiries, * calls, bookings and sales; search visibility for brand awareness). * Level 0 otherwise, which keeps the usual order. * * @param array $alert Stored alert (type, data). * @param Profile $site Site. * @return array{level:int,label:string} */ public static function for_alert( array $alert, Profile $site ) { $p = $site->priorities(); $none = array( 'level' => self::LEVEL_NONE, 'label' => '', ); if ( ! self::any( $p ) ) { return $none; } $type = isset( $alert['type'] ) ? (string) $alert['type'] : ''; $d = isset( $alert['data'] ) && is_array( $alert['data'] ) ? $alert['data'] : array(); $pages = array_flip( $p['pages'] ); $events = array_flip( $site->priority_events() ); $goal = self::goal_tag( $p ); $key = static function ( $label ) { return array( 'level' => self::LEVEL_KEY, 'label' => $label, ); }; $by_goal = static function () use ( $goal ) { return array( 'level' => self::LEVEL_GOAL, 'label' => $goal, ); }; switch ( $type ) { case 'page_drop': if ( isset( $d['page'] ) && isset( $pages[ self::path( (string) $d['page'] ) ] ) ) { return $key( __( 'Important page', 'turtlereport' ) ); } break; case 'clicks_drop': foreach ( isset( $d['pages'] ) ? (array) $d['pages'] : array() as $row ) { $url = is_array( $row ) && isset( $row['key'] ) ? (string) $row['key'] : ''; if ( '' !== $url && isset( $pages[ self::path( $url ) ] ) ) { return $key( __( 'Affects an important page', 'turtlereport' ) ); } } break; case 'tracking_stopped': if ( isset( $d['event'] ) && isset( $events[ (string) $d['event'] ] ) ) { return $key( __( 'Priority event', 'turtlereport' ) ); } if ( ! $events && self::wants_conversions( $p ) ) { return $by_goal(); } return $none; case 'leads_drop': if ( ! empty( $d['priority'] ) ) { return $key( __( 'Priority event', 'turtlereport' ) ); } foreach ( isset( $d['events'] ) ? (array) $d['events'] : array() as $row ) { if ( is_array( $row ) && isset( $row['name'], $events[ (string) $row['name'] ] ) && (int) $row['now'] < (int) $row['prev'] ) { return $key( __( 'Priority event', 'turtlereport' ) ); } } if ( self::wants_conversions( $p ) ) { return $by_goal(); } return $none; } if ( self::GOAL_AWARENESS === $p['goal'] && in_array( $type, array( 'no_clicks', 'clicks_drop', 'rankings_drop', 'sessions_drop' ), true ) ) { return $by_goal(); } return $none; } /** * What the report screen needs. * * @param Profile $site Site. * @return array */ public static function for_report( Profile $site ) { $p = $site->priorities(); return array( 'goal' => $p['goal'], 'goalTag' => self::goal_tag( $p ), 'pages' => $p['pages'], 'events' => $site->priority_events(), ); } }