| Server IP : 121.121.20.254 / Your IP : 216.73.216.146 Web Server : Microsoft-IIS/10.0 System : Windows NT WEB-SERVER 10.0 build 20348 (Windows Server 2022) AMD64 User : IUSR ( 0) PHP Version : 8.3.28 Disable Function : NONE MySQL : ON | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : C:/inetpub/wwwroot/KnowledgeBase/wp-content/plugins/wpmu-dev-seo/includes/core/ |
Upload File : |
<?php
/**
* Checks hub
*
* @package wpmu-dev-seo
*/
namespace SmartCrawl;
use SmartCrawl\Cache\Post_Cache;
use SmartCrawl\Checks\Check;
/**
* Checks dispatcher class
*/
class Checks extends Work_Unit {
use Singleton;
const ENDPOINT = 'endpoint';
const POST = 'post';
/**
* Remote handler for frontend post fetching
*
* @var Core_Request
*/
private $endpoint_remote_handler;
/**
* Holds reference to checks.
*
* @var array
*/
private $checks = array(
// Holds reference to checks that deal with final rendered content.
'endpoint' => array(
'imgalts_keywords',
'content_length',
'keyword_density',
'links_count',
'nofollow_links',
'para_keywords',
'subheadings_keywords',
),
// Holds reference to checks that deal with raw post data.
'post' => array(
'focus',
'focus_stopwords',
'title_keywords',
'title_length',
'metadesc_keywords',
'metadesc_length',
'slug_keywords',
'slug_underscores',
'keywords_used',
'metadesc_handcraft',
),
);
/**
* Extra keyword checks.
*
* @since 3.4.0
*
* @var array
*/
private $extra_keyword_checks = array(
'endpoint' => array(
'subheadings_keywords',
'keyword_density',
'bolded_keyword',
),
'post' => array(
'title_secondary_keywords',
),
);
/**
* Holds a reference to all checks that have been
* applied in this checks run
*
* @var array
*/
private $applied_checks = array();
/**
* Post ID
*
* @var int
*/
private $post_id;
/**
* Is current check for primary keyword.
*
* @since 3.4.0
*
* @var bool
*/
private $is_primary = true;
/**
* Holds current keyword.
*
* @since 3.4.0
*
* @var string
*/
private $keywords = true;
/**
* Static entry point, can be used instead of constructor
*
* Applies all queued checks to the subject post using only primary keyword.
*
* @since 3.4.0 Added params $keyword, $primary
*
* @param int $post_id ID of the post to check.
* @param Core_Request $request Optional Core_Request instance to use - used in testing.
* @param string $keyword Keyword to analyse.
* @param bool $primary Is primary keyword check.
*
* @return object Checks instance
*/
public static function apply( $post_id, $request = false, $keyword = '', $primary = true ) {
$me = new self();
$me->set_post_id( $post_id );
$me->set_keywords( $keyword );
// Set if primary.
$me->is_primary = (bool) $primary;
if ( ! empty( $request ) ) {
$me->set_remote_handler( $request );
}
// Primary keyword checks.
$me->apply_post_checks();
$me->apply_endpoint_checks();
return $me;
}
/**
* Sets internal post ID
*
* @param int $post_id Post ID to check.
*
* @return bool Status
*/
public function set_post_id( $post_id ) {
$this->post_id = (int) $post_id;
return ! ! $this->post_id;
}
/**
* Set current keywords.
*
* @since 3.4.0
*
* @param string|array $keywords Keywords.
*
* @return void
*/
public function set_keywords( $keywords ) {
$this->keywords = $this->get_focus( $keywords );
}
/**
* Set remote request handler.
*
* @param Core_Request $request Request.
*
* @return void
*/
public function set_remote_handler( $request ) {
$this->endpoint_remote_handler = $request;
}
/**
* Applies check tests to post-specific queue.
*
* @return bool Overall status
*/
public function apply_post_checks() {
$checks = $this->is_primary ? $this->checks : $this->extra_keyword_checks;
$subject = $this->apply_filters( 'subject-post', false, $this->keywords, $this->is_primary );
if ( empty( $subject ) ) {
$subject = get_post( $this->post_id );
}
return $this->apply_checks( $checks['post'], $subject );
}
/**
* Applies check tests to endpoint-specific queue.
*
* @return bool Overall status
*/
public function apply_endpoint_checks() {
$checks = $this->is_primary ? $this->checks : $this->extra_keyword_checks;
if ( empty( $checks['endpoint'] ) ) {
return true;
}
$subject = $this->apply_filters( 'subject-endpoint', false, $this->keywords, $this->is_primary );
if ( empty( $subject ) ) {
$subject = $this->get_endpoint_content();
}
if ( false === $subject ) {
$this->add_error( 'checks', __( 'We encountered an error fetching your content', 'wds' ) );
return false;
}
return $this->apply_checks( $checks['endpoint'], $subject );
}
/**
* Applies the checks in queue.
*
* @param array $checks A list of checks to apply.
* @param mixed $subject Subject to apply the checks to.
*
* @return bool Overall status
*/
public function apply_checks( $checks, $subject ) {
$overall_result = true;
$keywords = $this->get_focus( $this->keywords );
$language = $this->get_seo_analysis_language();
foreach ( $checks as $check_id ) {
/**
* Check.
*
* @var $check Check
*/
$check = $this->get_check( $check_id );
if ( empty( $check ) ) {
continue;
}
$check->set_subject( $subject );
$check->set_focus( $keywords );
$check->set_language( $language );
$check->set_post_id( $this->post_id );
$check->set_primary( $this->is_primary );
$is_ignored = $this->is_ignored_check( $check_id );
$result = true;
if ( ! $is_ignored ) {
$result = $check->apply();
if ( ! $result ) {
$overall_result = false;
$this->add_error( $check_id, $check->get_status_msg() );
}
}
if ( ! $check->is_hidden() ) {
$this->applied_checks[ $check_id ] = array(
'status' => $result,
'ignored' => $is_ignored,
'result' => $check->get_result(),
);
}
}
return $overall_result;
}
/**
* Gets a list of keywords.
*
* @since 3.4.0 Param $primary_keyword added.
*
* @param string $keyword Is check for primary keyword.
*
* @return array A list of expected keywords
*/
public function get_focus( $keyword = '' ) {
if ( empty( $keyword ) ) {
$post = Post_Cache::get()->get_post( $this->post_id );
if ( empty( $post ) ) {
return array();
}
// For backward compatibility use primary keyword as backup.
$keyword = array( $post->get_primary_keyword() );
}
// Make sure it's array.
$keywords = (array) $keyword;
return (array) $this->apply_filters( 'focus', $keywords, $this->post_id );
}
/**
* Instantiates check according to check ID
*
* @param string $check_id Check to be instantiated.
*
* @return bool|object Smartcrawl_Check_abstract object instance on success,
* (bool)false on failure
*/
public function get_check( $check_id ) {
$cname = $this->get_check_class_name( $check_id );
if ( ! class_exists( $cname ) ) {
return false;
}
return new $cname();
}
/**
* Get class name for the check.
*
* @param string $check_id Check ID.
*
* @return string
*/
private function get_check_class_name( $check_id ) {
$cname = str_replace( '_', ' ', $check_id );
$cname = str_replace( ' ', '_', ucwords( $cname ) );
return sprintf( '\SmartCrawl\Checks\%s', $cname );
}
/**
* Checks whether a check is to be ignored for the current post
*
* @param string $check_id ID of the check.
*
* @return bool
*/
public function is_ignored_check( $check_id ) {
$ignored = self::get_ignored_checks( $this->post_id );
return in_array( $check_id, $ignored, true );
}
/**
* Gets a list of post-specific ignored checks
*
* Ignored checks are skipped in analysis
*
* @param int $post_id Post ID.
*
* @return array A list of check IDs
*/
public static function get_ignored_checks( $post_id ) {
// Make sure meta is fetched from the post, not a revision. The same thing is done in WP function update_post_meta.
$revision_parent = wp_is_post_revision( $post_id );
if ( $revision_parent ) {
$post_id = $revision_parent;
}
$ignored = get_post_meta( $post_id, '_wds_ignored_checks', true );
return is_array( $ignored ) && ! empty( $ignored )
? $ignored
: array();
}
/**
* Fetches local endpoint via HTTP API
*
* @return bool|string Endpoint content as string, or
* (bool)false if something went wrong
*/
public function get_endpoint_content() {
if ( empty( $this->endpoint_remote_handler ) || ! ( $this->endpoint_remote_handler instanceof Core_Request ) ) {
$this->set_remote_handler( new Core_Request() );
}
$content = $this->endpoint_remote_handler->get_rendered_post( $this->post_id );
if ( is_wp_error( $content ) ) {
return false;
}
return (string) $content;
}
/**
* Whether the readability check for this post is ignored
*
* @param int $post_id Post ID.
*
* @return bool
*/
public static function is_readability_ignored( $post_id ) {
$ignored = self::get_ignored_checks( $post_id );
return in_array( 'readability', $ignored, true );
}
/**
* Adds a single check to the ignored checks stack
*
* @param int $post_id ID of the post.
* @param string $check_id ID of the check.
*
* @return bool
*/
public static function add_ignored_check( $post_id, $check_id ) {
$ignored = self::get_ignored_checks( $post_id );
$ignored[] = $check_id;
return self::set_ignored_checks( $post_id, $ignored );
}
/**
* Updates a list of ignored checks
*
* @param int $post_id ID of the post.
* @param array $checks Full list of checks.
*
* @return bool
*/
public static function set_ignored_checks( $post_id, $checks ) {
if ( ! is_array( $checks ) ) {
return false;
}
$checks = array_filter( array_map( 'trim', array_unique( $checks ) ) );
return update_post_meta( $post_id, '_wds_ignored_checks', $checks );
}
/**
* Removes a single check from the ignored checks stack
*
* @param int $post_id ID of the post.
* @param string $check_id ID of the check.
*
* @return bool
*/
public static function remove_ignored_check( $post_id, $check_id ) {
$ignored = self::get_ignored_checks( $post_id );
$key = array_search( $check_id, $ignored, true );
if ( false === $key ) {
return false;
}
unset( $ignored[ $key ] );
return self::set_ignored_checks( $post_id, $ignored );
}
/**
* Gets checks that have been applied in this run
*
* @return array
*/
public function get_applied_checks() {
return $this->applied_checks;
}
/**
* Calculates approximate checks success percentage
*
* Approximate because the result is rounded to integer
*
* @return int Success percentage
*/
public function get_percentage() {
if ( $this->get_status() ) {
return 100;
}
$cnum = count( $this->get_checks() );
$enum = count( $this->get_errors() );
$err = (int) ( ( $enum / $cnum ) * 100 );
return 100 - $err;
}
/**
* Checks whether we're all good and without issues
*
* @return bool
*/
public function get_status() {
$errors = $this->get_errors();
return empty( $errors );
}
/**
* Gets the list of checks to be performed for primary keyword.
*
* @param string $which Which checks to perform.
*
* @return array List of check IDs
*/
public function get_checks( $which = false ) {
if ( self::ENDPOINT === $which ) {
return $this->checks['endpoint'];
}
if ( self::POST === $which ) {
return $this->checks['post'];
}
return array_merge( $this->checks['endpoint'], $this->checks['post'] );
}
/**
* Gets the list of checks to be performed on extra keywords.
*
* @since 3.4.0
*
* @param string $which Which checks to perform.
*
* @return array List of check IDs
*/
public function get_extra_checks( $which = false ) {
if ( self::ENDPOINT === $which ) {
return $this->extra_keyword_checks['endpoint'];
}
if ( self::POST === $which ) {
return $this->extra_keyword_checks['post'];
}
return array_merge( $this->extra_keyword_checks['endpoint'], $this->extra_keyword_checks['post'] );
}
/**
* Gets filtering prefix
*
* @return string
*/
public function get_filter_prefix() {
return 'wds-checks';
}
/**
* Get language for analysing.
*
* @return string
*/
private function get_seo_analysis_language() {
$locale = str_replace( array( '-', '_' ), '-', get_locale() );
$parts = explode( '-', $locale );
return apply_filters( 'wds_post_seo_analysis_language', $parts[0], $this->post_id );
}
}