link = $link;
}
/**
* Returns the Link object.
*
* @return Link
*/
public function get_link_object() {
return $this->link;
}
/**
* Replaces url in post's content, meta and comments.
* Returns false if link not replaced anywhere, else true.
*
* @param int $post_id
*
* @return bool
*/
public function execute_url_in_post( int $post_id = 0 ) {
if ( empty( $post_id ) ) {
return false;
}
$result = $this->init_processor( $post_id );
if ( is_wp_error( $result ) ) {
Utilities::log( 'BLC_LINK_ACTION_ERROR: ' . $result->get_error_message() );
return false;
}
//Execute link in post content.
$completed_in_posts = $this->execute_in_post_content( $post_id );
//Execute link in post metas.
$completed_in_meta = $this->execute_in_post_meta_values( $post_id );
//Execute link in post comments.
$completed_in_comments = $this->execute_in_post_comments( $post_id );
// Execute link reusable blocks.
$completed_in_reusable_blocks = $this->execute_in_post_reusable_blocks( $post_id );
return $completed_in_posts || $completed_in_meta || $completed_in_comments || $completed_in_reusable_blocks;
}
/**
* Initializes the processor object based on the link action type and sets the post ID if applicable.
*
* @param int $post_id The post ID to set in the processor object if it has a post_id property.
* @return true|\WP_Error True on success or
* a WP_Error if there was an issue determining the processor.
*/
public function init_processor( int $post_id ) {
$this->processor = $this->determine_processor();
if ( is_wp_error( $this->processor ) ) {
return $this->processor;
}
if (
$this->processor &&
property_exists( $this->processor, 'post_id' )
) {
$this->processor->post_id = $post_id;
}
return true;
}
/**
* Runs only on post's content.
*
* @param int $post_id
* @param string $content
*
* @return bool
*/
public function execute_in_post_content( int $post_id = 0, string $content = '' ) {
if ( empty( $post_id ) ) {
return false;
}
$processor = $this->get_processor();
if ( is_wp_error( $processor ) ) {
return false;
}
$new_content = '';
if ( empty( $content ) ) {
if ( empty( $this->temp_store['posts'][ $post_id ]['content'] ) ) {
$this->temp_store['posts'][ $post_id ]['content'] = get_post_field( 'post_content', $post_id );
}
$content = $this->temp_store['posts'][ $post_id ]['content'];
}
$link = $this->get_link_object();
if ( function_exists( 'has_blocks' ) && has_blocks( $content ) ) {
$new_content = $this->execute_post_blocks(
$content,
$link->get_link(),
$link->get_new_link()
);
} else {
$new_content = $processor->execute(
$content,
$link->get_link(),
$link->get_new_link()
);
}
$new_content = apply_filters( 'wpmudev_blc_link_action_post_content', $new_content, $post_id, $this->link );
if ( $content !== $new_content ) {
// At first, we are creating a new post revision.
wp_save_post_revision( $post_id );
// Then update post with new content.
kses_remove_filters();
$result = wp_update_post(
array(
'ID' => $post_id,
'post_content' => $new_content,
)
);
kses_init_filters();
return $result;
}
return false;
}
/**
* Runs the links action in post blocks.
*
* @param string $content
* @param string $link
* @param string $new_link
* @return string
*/
public function execute_post_blocks( string $content = '', string $link = '', string $new_link = '' ) {
// No point running if content is empty or if there is no link to modify.
if ( empty( $content ) || empty( $link ) ) {
return $content;
}
$processor = $this->get_processor();
if ( is_wp_error( $processor ) ) {
return $content;
}
if ( function_exists( 'has_blocks' ) && has_blocks( $content ) ) {
$blocks = parse_blocks( $content );
$parsed_blocks = array();
if ( ! empty( $blocks ) ) {
foreach ( $blocks as $block ) {
$parsed_blocks[] = $processor->parse_block( $block, $link, $new_link );
}
}
}
return ! empty( $parsed_blocks ) ? serialize_blocks( $parsed_blocks ) : $content;
}
/**
* Executes in all post meta values by post id.
*
* @param int $post_id
*
* @return false|void
*/
public function execute_in_post_meta_values( int $post_id = 0 ) {
if ( empty( $post_id ) ) {
return false;
}
$processor = $this->get_processor();
if ( is_wp_error( $processor ) ) {
return false;
}
$link = $this->get_link_object();
// The `wpmudev_blc_process_extensive` filter is returned in `Utilities::process_extensive()`.
if ( Utilities::process_extensive() ) {
global $wpdb;
$query = $wpdb->prepare(
"SELECT meta_key, meta_value as content FROM {$wpdb->postmeta} WHERE post_id=%d AND meta_value LIKE %s",
$post_id,
'%' . $wpdb->esc_like( $link->get_link() ) . '%'
);
$metas = $wpdb->get_results( $query );
if ( ! empty( $metas ) ) {
$old_link = $link->get_link();
$new_link = $link->get_new_link();
foreach ( $metas as $meta ) {
$new_content = $processor->execute( $meta->content, $old_link, $new_link );
$new_content = apply_filters( 'wpmudev_blc_link_action_post_meta_content', $new_content, $post_id, $meta->meta_key, $this->link );
if ( $new_content !== $meta->content ) {
return update_post_meta( $post_id, sanitize_key( $meta->meta_key ), $new_content );
}
}
}
} else {
$supported_meta_keys = apply_filters(
'wpmudev_blc_edit_link_post_meta_keys',
array()
);
if ( empty( $supported_meta_keys ) ) {
return false;
}
$old_link = $link->get_link();
$new_link = $link->get_new_link();
foreach ( $supported_meta_keys as $meta_key ) {
$content = get_post_meta( $post_id, sanitize_key( $meta_key ), true );
if ( is_string( $content ) ) {
$new_content = $this->get_processor()->execute( $content, $this->link->get_link(), $this->link->get_new_link() );
} else {
// If content is not a string, we cannot process it, so we return it as is.
$new_content = $content;
}
$new_content = apply_filters( 'wpmudev_blc_link_action_post_meta_content', $new_content, $post_id, $meta_key, $this->link );
if ( $new_content !== $content ) {
return update_post_meta( $post_id, sanitize_key( $meta_key ), $new_content );
}
}
}
return false;
}
/**
* Executes link action in post's comments.
* TODO: Revisit comments links because WP wraps urls with `` tags when comment is displayed. The means that because of that links will be found as broken but not replaced.
*
* @param int $post_id
*
* @return bool
*/
public function execute_in_post_comments( int $post_id = 0 ) {
$post_comments = get_comments( array( 'post_id' => $post_id ) );
$processor = $this->get_processor();
if ( is_wp_error( $processor ) ) {
return false;
}
if ( ! empty( $post_comments ) ) {
$link = $this->get_link_object();
$old_link = $link->get_link();
$new_link = $link->get_new_link();
foreach ( $post_comments as $post_comment ) {
// Make WordPress auto-link urls in comment content so the processor can find the broken link wrapped in tag and replace it. Otherwise, the processor will not find the link because of the HTML tags.
$content = make_clickable( $post_comment->comment_content );
// When unlinking in comments, we need to make sure that the replacement does not contain URL schema because of the auto-linking of urls in comments.
add_filter( 'wpmudev_blc_unlink_replacements', array( $this, 'encode_comment_urls' ), 10, 1 );
$new_content = $processor->execute( $content, $old_link, $new_link );
remove_filter( 'wpmudev_blc_unlink_replacements', array( $this, 'encode_comment_urls' ), 10, 1 );
if ( $new_content !== $content ) {
$update = wp_update_comment(
array(
'comment_ID' => intval( $post_comment->comment_ID ),
'comment_content' => $new_content,
)
);
return $update && ! is_wp_error( $update );
}
}
}
return false;
}
/**
* Runs the link action on the Reusable Blocks of the given $post_id.
*
* @param int $post_id
* @return bool
*/
public function execute_in_post_reusable_blocks( int $post_id = 0 ) {
if ( ! function_exists( 'register_block_type' ) || apply_filters( 'wpmudev_blc_link_actions_exclude_reusable_blocks', false ) ) {
return false;
}
if ( empty( $post_id ) ) {
return false;
}
$result = false;
if ( empty( $this->temp_store['posts'][ $post_id ]['content'] ) ) {
$this->temp_store['posts'][ $post_id ]['content'] = get_post_field( 'post_content', $post_id );
}
$content = $this->temp_store['posts'][ $post_id ]['content'];
$reusable_blocks = $this->get_reusable_blocks_from_content( $content );
if ( ! empty( $reusable_blocks ) ) {
$processor = $this->get_processor();
if ( is_wp_error( $processor ) ) {
return false;
}
$processor->load( [ 'is_recurring_block' => true ] );
foreach ( $reusable_blocks as $block_post_id ) {
if ( $this->execute_in_post_content( $block_post_id ) ) {
$result = true;
}
}
$processor->clear();
}
return $result;
}
public function get_reusable_blocks_from_content( string $content = '' ) {
if ( empty( $content ) ) {
return array();
}
static $reusable_blocks = null;
if ( is_null( $reusable_blocks ) ) {
$reusable_blocks = get_posts(
array(
'posts_per_page' => -1,
'fields' => 'ids',
'post_type' => 'wp_block',
)
);
}
if ( ! empty( $reusable_blocks ) ) {
foreach ( $reusable_blocks as $block_key => $block_id ) {
$block_name = '';
if ( strpos( $content, $block_name ) === false ) {
unset( $reusable_blocks[ $block_key ] );
}
}
}
return $reusable_blocks;
}
public function get_site_blocks() {
}
/**
* Executes in users meta table by user id.
*
* @param int $user_id
*
* @return bool
*/
public function execute_in_user_author_meta( int $user_id = 0 ) {
if ( empty( $user_id ) ) {
return false;
}
/*
* TODO: Use an global process_extensive flag and use `get_user_meta()` only with $user_id (no key).
*/
$processor = $this->get_processor();
if ( is_wp_error( $processor ) ) {
return false;
}
$link = $this->get_link_object();
$content = get_user_meta( $user_id, 'description', true );
$new_content = $processor->execute(
$content,
$link->get_link(),
$link->get_new_link()
);
return update_user_meta( $user_id, 'description', $new_content );
}
/**
* Executes link action in comments by comment id.
* TODO: Revisit comments links because WP wraps urls with `` tags when comment is displayed. The means that because of that links will be found as broken but not replaced.
*
* @param int $id comment id
*
* @return bool
*/
public function execute_in_comment( int $id = 0, string $content = null ) {
$processor = $this->get_processor();
if ( is_wp_error( $processor ) ) {
return false;
}
if ( empty( $content ) ) {
$comment = get_comment( $id );
if ( $comment instanceof WP_Comment || $comment instanceof \WP_Comment ) {
$content = $comment->comment_content;
}
}
// Make WordPress auto-link urls in comment content so the processor can find the broken link wrapped in tag and replace it. Otherwise, the processor will not find the link because of the HTML tags.
$content = make_clickable( $content );
// When unlinking in comments, we need to make sure that the replacement does not contain URL schema because of the auto-linking of urls in comments.
add_filter( 'wpmudev_blc_unlink_replacements', array( $this, 'encode_comment_urls' ), 10, 1 );
$link = $this->get_link_object();
$new_content = $processor->execute(
$content,
$link->get_link(),
$link->get_new_link()
);
remove_filter( 'wpmudev_blc_unlink_replacements', array( $this, 'encode_comment_urls' ), 10, 1 );
if ( $new_content !== $content ) {
$update = wp_update_comment(
array(
'comment_ID' => intval( $id ),
'comment_content' => $new_content,
)
);
return $update && ! is_wp_error( $update );
}
return false;
}
/**
* Encodes url schema in the replacement string when unlinking in comments to avoid having the old link show up
* in the new content because of the HTML tags and the auto-linking of urls in comments.
*
* @param array $replacements List of replacements to be made in the content,
* where key is the string to be replaced, and value is the replacement string.
*
* @return array
*/
public function encode_comment_urls( array $replacements = array() ) {
// When unlinking in comments, we need to make sure that the replacement does not contain URL schema because of the auto-linking of urls in comments.
return array_map(
function ( $replacement ) {
return str_replace( array( ':', '/' ), array( ':', '/' ), $replacement );
},
$replacements
);
}
/**
* Executes in postmeta by meta_id.
*
* @param int|null $meta_id
* @param string|null $content
*
* @return bool
*/
public function execute_in_postmeta( int $meta_id = null, string $content = null ) {
return $this->execute_in_meta_table( 'post', $meta_id, $content );
}
/**
* Executes the action in usermeta table by meta_id.
*
* @param int|null $meta_id
* @param string|null $content
*
* @return false
*/
public function execute_in_usermeta( int $meta_id = null, string $content = null ) {
return $this->execute_in_meta_table( 'user', $meta_id, $content );
}
public function execute_in_meta_table( string $type = null, int $meta_id = null, string $content = null ) {
if ( empty( $meta_id ) || ! in_array( $type, array( 'user', 'post' ) ) ) {
return false;
}
if ( empty( $content ) ) {
$meta = get_metadata_by_mid( $type, $meta_id );
$content = property_exists( $meta, 'meta_value' ) ? $meta->meta_value : null;
}
if ( ! empty( $content ) ) {
$processor = $this->get_processor();
if ( is_wp_error( $processor ) ) {
return false;
}
$link = $this->get_link_object();
$new_content = $processor->execute(
$content,
$link->get_link(),
$link->get_new_link()
);
return update_metadata_by_mid( $type, $meta_id, $new_content );
}
return false;
}
/**
* Returns the processor object that will execute the specified action (replace|unlink|nofollow).
*
* @param bool $reset Whether to reset the processor data before returning it. Default is false.
* @return Link_Processor|null
*/
public function get_processor( $reset = false ) {
if ( $this->processor instanceof Link_Processor && $reset ) {
// Clear previous processor data if there is any.
$this->processor->load();
}
return $this->processor;
}
/**
* Determines the processor to execute based on the Link type.
*
* @return Link_Processor|\WP_Error
*/
public function determine_processor() {
if ( empty( $this->get_link_object() ) ) {
return new \WP_Error(
'blc-link-execution-error',
esc_html__(
'Missing link',
'broken-link-checker'
)
);
}
$type = $this->get_link_object()->get_type();
if ( empty( $this->actions_map()[ $type ] ) ) {
return new \WP_Error(
'blc-link-execution-error',
esc_html__(
'Unmapped action',
'broken-link-checker'
)
);
}
return $this->actions_map()[ $type ];
}
/**
* Map with allowed actions and classes that handle link actions.
*
* @return array
*/
public function actions_map() {
return apply_filters(
'wpmudev_blc_link_actions_map',
array(
'replace' => Replace_Link::instance(),
'unlink' => Unlink_Link::instance(),
'nofollow' => Nofollow_Link::instance(),
),
$this
);
}
}