Files
2026-04-28 15:13:50 +02:00

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