346 lines
12 KiB
PHP
346 lines
12 KiB
PHP
<?php
|
|
namespace AIOSEO\Plugin\Pro\SearchStatistics;
|
|
|
|
// Exit if accessed directly.
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
use AIOSEO\Plugin\Common\Models;
|
|
use AIOSEO\Plugin\Addon\LinkAssistant\Models as LinkAssistantModels;
|
|
use AIOSEO\Plugin\Pro\Redirects\Utils as RedirectsUtils;
|
|
|
|
/**
|
|
* Contains helper functions specific to Search Statistics.
|
|
*
|
|
* @since 4.3.0
|
|
*/
|
|
class Helpers {
|
|
/**
|
|
* Retrieves the page expression, which Search Statistics uses to identify a page.
|
|
*
|
|
* @since 4.7.8
|
|
*
|
|
* @param int|string $postId The post ID.
|
|
* @return string The page expression for the given post ID. An empty string if a permalink for the post ID is not found.
|
|
*/
|
|
public function buildPageExpression( $postId ) {
|
|
$output = '';
|
|
$postId = (int) $postId;
|
|
$permalink = get_permalink( $postId ?: 0 );
|
|
if ( $permalink ) {
|
|
$slug = $this->getPageSlug( $permalink );
|
|
$baseUrl = untrailingslashit( aioseo()->searchStatistics->api->auth->getAuthedSite() );
|
|
$output = $baseUrl . $slug;
|
|
}
|
|
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* Maps all rows to use to the given property.
|
|
*
|
|
* @since 4.3.0
|
|
*
|
|
* @param array $rows The rows.
|
|
* @param string $property The property,
|
|
* @return array The mapped rows.
|
|
*/
|
|
public function setRowKey( $rows, $property = 'keyword' ) {
|
|
$mappedRows = [];
|
|
foreach ( $rows as $row ) {
|
|
$value = is_object( $row ) ? $row->$property : $row[ $property ];
|
|
$key = 'page' === $property ? aioseo()->searchStatistics->helpers->getPageSlug( $value ) : strtolower( $value );
|
|
$key = is_numeric( $key ) ? '_' . $key : $key; // To prevent sorting issues, the key can never be numeric.
|
|
$mappedRows[ $key ] = $row;
|
|
}
|
|
|
|
return $mappedRows;
|
|
}
|
|
|
|
/**
|
|
* Returns the page slug from a URL.
|
|
*
|
|
* @since 4.3.0
|
|
*
|
|
* @param string $url The URL.
|
|
* @return string The page slug.
|
|
*/
|
|
public function getPageSlug( $url ) {
|
|
$siteUrl = [
|
|
aioseo()->searchStatistics->api->auth->getAuthedSite(), // Replaces the authed site url.
|
|
home_url() // Also replaces the home url.
|
|
];
|
|
|
|
$url = strtolower( trim( $url ) );
|
|
$url = str_replace( $siteUrl, '', $url );
|
|
$url = urldecode( $url );
|
|
$url = wp_make_link_relative( $url );
|
|
$url = trim( $url, '/' );
|
|
|
|
if ( empty( $url ) ) {
|
|
return '/';
|
|
}
|
|
|
|
return aioseo()->helpers->maybeRemoveTrailingSlash( '/' . $url . '/' );
|
|
}
|
|
|
|
/**
|
|
* Returns the included Search Statistics post types.
|
|
*
|
|
* @since 4.3.0
|
|
*
|
|
* @return array The included post types.
|
|
*/
|
|
public function getIncludedPostTypes() {
|
|
if ( aioseo()->options->searchStatistics->postTypes->all ) {
|
|
return aioseo()->helpers->getPublicPostTypes( true );
|
|
}
|
|
|
|
return aioseo()->options->searchStatistics->postTypes->included;
|
|
}
|
|
|
|
/**
|
|
* Returns the Link Assistant data for the given post ID if the addon is active.
|
|
*
|
|
* @since 4.3.0
|
|
*
|
|
* @param int $postId The post ID.
|
|
* @return array The Link Assistant data.
|
|
*/
|
|
public function getLinkAssistantData( $postId ) {
|
|
if ( ! $postId ) {
|
|
return [];
|
|
}
|
|
|
|
$isLinkAssistantLoaded = function_exists( 'aioseoLinkAssistant' );
|
|
$linkAssistantAddon = aioseo()->addons->getAddon( 'aioseo-link-assistant' );
|
|
|
|
if ( ! $isLinkAssistantLoaded || ! aioseo()->license->isActive() || ! $linkAssistantAddon->isActive ) {
|
|
return [];
|
|
}
|
|
|
|
$totalOutboundSuggestions = LinkAssistantModels\Suggestion::getTotalOutboundSuggestions( $postId );
|
|
$totalInboundSuggestions = LinkAssistantModels\Suggestion::getTotalInboundSuggestions( $postId );
|
|
|
|
$totalLinks = LinkAssistantModels\Link::getLinkTotals( $postId );
|
|
|
|
return [
|
|
'inboundInternal' => ! empty( $totalLinks->inboundInternal ) ? (int) $totalLinks->inboundInternal : 0,
|
|
'outboundInternal' => ! empty( $totalLinks->outboundInternal ) ? (int) $totalLinks->outboundInternal : 0,
|
|
'affiliate' => ! empty( $totalLinks->affiliate ) ? (int) $totalLinks->affiliate : 0,
|
|
'external' => ! empty( $totalLinks->external ) ? (int) $totalLinks->external : 0,
|
|
'linkSuggestions' => $totalOutboundSuggestions + $totalInboundSuggestions
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Returns the Redirects data for the given post ID if the addon is active.
|
|
*
|
|
* @since 4.3.0
|
|
*
|
|
* @param int $postId The post ID.
|
|
* @return array The Redirects data.
|
|
*/
|
|
public function getRedirectsData( $postId ) {
|
|
if ( ! $postId ) {
|
|
return [];
|
|
}
|
|
|
|
$isRedirectsLoaded = ! empty( aioseo()->redirects );
|
|
|
|
if (
|
|
! $isRedirectsLoaded ||
|
|
! aioseo()->license->isActive() ||
|
|
! method_exists( aioseo()->redirects->redirect, 'getRedirects' ) ||
|
|
! method_exists( aioseo()->redirects->redirect, 'getRedirectsByTarget' )
|
|
) {
|
|
return [];
|
|
}
|
|
|
|
$from = [];
|
|
$to = '';
|
|
$toCode = 0;
|
|
$permalink = get_permalink( $postId );
|
|
$targetUrl = RedirectsUtils\WpUri::excludeHomeUrl( $permalink );
|
|
|
|
foreach ( aioseo()->redirects->redirect->getRedirects( $permalink ) as $redirect ) {
|
|
$to = RedirectsUtils\Request::formatTargetUrl( trim( $redirect->target_url ) );
|
|
$toCode = $redirect->type;
|
|
break;
|
|
}
|
|
|
|
foreach ( aioseo()->redirects->redirect->getRedirectsByTarget( $targetUrl ) as $redirect ) {
|
|
$from[] = RedirectsUtils\Request::formatTargetUrl( trim( $redirect->target_url ) );
|
|
}
|
|
|
|
return [
|
|
'from' => $from,
|
|
'to' => $to,
|
|
'toCode' => $toCode
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get the suggested changes for the current post.
|
|
*
|
|
* @since 4.3.0
|
|
* @version 4.7.2 Moved function.
|
|
*
|
|
* @param Models\Post $post The post to get the schema graphs.
|
|
* @return array List of the suggested changes.
|
|
*/
|
|
public function getSuggestedChanges( $post ) {
|
|
$analysis = Models\Post::getPageAnalysisDefaults( $post->page_analysis );
|
|
$suggestedChanges = [];
|
|
|
|
foreach ( $analysis->analysis as $analysis ) {
|
|
foreach ( $analysis as $change => $score ) {
|
|
if ( is_object( $score ) && 1 === $score->error ) {
|
|
$suggestedChanges[] = self::getSuggestedChangeDescription( $change, $score->score );
|
|
}
|
|
}
|
|
}
|
|
|
|
return array_values( array_filter( $suggestedChanges ) );
|
|
}
|
|
|
|
/**
|
|
* Get the suggested changes description and tooltip.
|
|
*
|
|
* @since 4.3.0
|
|
* @version 4.7.2 Moved function.
|
|
*
|
|
* @param string $change The change name.
|
|
* @param int $score The score for the current change.
|
|
* @return array An array with the description and whether or not to show a tooltip.
|
|
*/
|
|
private function getSuggestedChangeDescription( $change, $score ) {
|
|
// phpcs:disable Universal.Arrays.MixedArrayKeyTypes
|
|
$keyphraseString = __( 'Focus Keyword', 'aioseo-pro' );
|
|
$strings = [
|
|
'keyphraseInContent' => [
|
|
'3' => __( 'Your Focus Keyword was not found in your content.', 'aioseo-pro' )
|
|
],
|
|
'keyphraseInIntroduction' => [
|
|
'0' => __( 'No content added yet.', 'aioseo-pro' ),
|
|
'3' => sprintf(
|
|
// Translators: 1 - Focus Keyword or Keyword.
|
|
__( 'Your %1$s does not appear in the first paragraph. Make sure the topic is clear immediately.', 'aioseo-pro' ),
|
|
$keyphraseString
|
|
)
|
|
],
|
|
'keyphraseInDescription' => [
|
|
'3' => sprintf(
|
|
// Translators: 1 - Focus Keyword or Keyword.
|
|
__( 'Your %1$s was not found in the meta description.', 'aioseo-pro' ),
|
|
$keyphraseString
|
|
)
|
|
],
|
|
'keyphraseInURL' => [
|
|
'1' => __( 'Focus Keyword not found in the URL.', 'aioseo-pro' )
|
|
],
|
|
'keyphraseLength' => [
|
|
'-999' => sprintf(
|
|
// Translators: 1 - Focus Keyword or Keyword.
|
|
__( 'No %1$s was set. Set a %1$s in order to calculate your SEO score.', 'aioseo-pro' ),
|
|
$keyphraseString
|
|
),
|
|
'3' => sprintf(
|
|
// Translators: 1 - Focus Keyword or Keyword.
|
|
__( 'The %1$s is too long. Try to make it shorter.', 'aioseo-pro' ),
|
|
$keyphraseString
|
|
),
|
|
'6' => sprintf(
|
|
// Translators: 1 - Focus Keyword or Keyword.
|
|
__( 'The %1$s is slightly long. Try to make it shorter.', 'aioseo-pro' ),
|
|
$keyphraseString
|
|
)
|
|
],
|
|
'metadescriptionLength' => [
|
|
'tooltip' => true,
|
|
'1' => __( 'No meta description has been specified. Search engines will display copy from the page instead. Make sure to write one!', 'aioseo-pro' ),
|
|
'6' => __( 'Your meta description may not display correctly in search results.', 'aioseo-pro' )
|
|
],
|
|
'lengthContent' => [
|
|
'6' => __( 'The content is below the minimum of words. Add more content.', 'aioseo-pro' ),
|
|
'3' => __( 'The content is below the minimum of words. Add more content.', 'aioseo-pro' ),
|
|
'-10' => __( 'This is far below the recommended minimum of words.', 'aioseo-pro' ),
|
|
'-20' => __( 'This is far below the recommended minimum of words.', 'aioseo-pro' )
|
|
],
|
|
'isInternalLink' => [
|
|
'3' => __( 'There are not enough internal links in your content, try adding some more.', 'aioseo-pro' )
|
|
],
|
|
'isExternalLink' => [
|
|
'3' => __( 'No outbound links were found. Link out to external resources.', 'aioseo-pro' )
|
|
],
|
|
'keyphraseInTitle' => [
|
|
'3' => __( 'Your Focus Keyword was not found in the SEO title.', 'aioseo-pro' )
|
|
],
|
|
'keyphraseInBeginningTitle' => [
|
|
'3' => __( 'The Focus Keyword doesn\'t appear at the beginning of the SEO title.', 'aioseo-pro' )
|
|
],
|
|
'titleLength' => [
|
|
'tooltip' => true,
|
|
'1' => __( 'No title has been specified. Make sure to write one!', 'aioseo-pro' ),
|
|
'6' => __( 'Your title may not display correctly in search results.', 'aioseo-pro' )
|
|
],
|
|
'contentHasAssets' => [
|
|
'1' => __( 'You are not using rich media like images or videos.', 'aioseo-pro' )
|
|
],
|
|
'paragraphLength' => [
|
|
'1' => __( 'At least one paragraph is long. Consider using short paragraphs.', 'aioseo-pro' )
|
|
],
|
|
'sentenceLength' => [
|
|
'tooltip' => true,
|
|
'6' => __( 'Some of your sentences are too long, try shortening them to improve readability.', 'aioseo-pro' )
|
|
],
|
|
'passiveVoice' => [
|
|
'tooltip' => true,
|
|
'3' => __( 'Try to use active counterparts on the sentences.', 'aioseo-pro' )
|
|
],
|
|
'transitionWords' => [
|
|
'tooltip' => true,
|
|
'3' => __( 'Use more transition words in your content.', 'aioseo-pro' ),
|
|
'6' => __( 'Use more transition words in your content.', 'aioseo-pro' )
|
|
],
|
|
'consecutiveSentences' => [
|
|
'tooltip' => true,
|
|
'3' => __( 'The text contains a high number of consecutive sentences starting with the same word. Try to mix things up!', 'aioseo-pro' )
|
|
],
|
|
'subheadingsDistribution' => [
|
|
'tooltip' => true,
|
|
'6' => __( 'Add subheadings to improve readability.', 'aioseo-pro' ),
|
|
'3' => __( 'Add subheadings to improve readability.', 'aioseo-pro' ),
|
|
'2' => __( 'You are not using any subheadings, although your text is rather long. Try and add some subheadings.', 'aioseo-pro' )
|
|
],
|
|
'calculateFleschReading' => [
|
|
'tooltip' => true,
|
|
'6' => __( 'Use less difficult words to improve readability', 'aioseo-pro' ),
|
|
'3' => __( 'Use less difficult words to improve readability', 'aioseo-pro' )
|
|
],
|
|
];
|
|
// phpcs:enable Universal.Arrays.MixedArrayKeyTypes
|
|
|
|
if ( ! isset( $strings[ $change ] ) || ! isset( $strings[ $change ][ $score ] ) ) {
|
|
return [];
|
|
}
|
|
|
|
return [
|
|
'text' => $strings[ $change ][ $score ],
|
|
'tooltip' => in_array( $change, array_keys( wp_list_filter( $strings, [ 'tooltip' => true ] ) ), true )
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Gets the timestamp for the next 8 AM (UTC-0).
|
|
* Google quota resets daily at 00:00 Pacific Time (UTC-8 or UTC-7 depending on daylight saving time).
|
|
*
|
|
* @since 4.8.2
|
|
*
|
|
* @return int The timestamp for the next 8 AM.
|
|
*/
|
|
public function getNext8Am() {
|
|
return strtotime( '8:00 AM ' . ( date( 'H' ) >= 8 ? '+1 day' : '' ) );
|
|
}
|
|
} |