post = $post;
$this->parsedPostContent = $parsedPostContent;
}
/**
* Extracts the images from third-party content.
*
* @since 4.2.2
*
* @return array[mixed] The image URLs and IDs.
*/
public function extract() {
$integrations = [
'acf',
'bricks',
'divi',
'nextGen',
'oxygen',
'wooCommerce',
'kadenceBlocks'
];
foreach ( $integrations as $integration ) {
$this->{$integration}();
}
return $this->images;
}
/**
* Extracts image URLs from ACF fields.
*
* @since 4.2.2
*
* @return void
*/
private function acf() {
if ( ! class_exists( 'ACF' ) || ! function_exists( 'get_fields' ) ) {
return;
}
$fields = get_fields( $this->post->ID );
if ( ! $fields ) {
return;
}
$images = $this->acfHelper( $fields );
$this->images = array_merge( $this->images, $images );
}
/**
* Helper function for acf().
*
* @since 4.2.2
*
* @param array $fields The ACF fields.
* @return array[string] The image URLs or IDs.
*/
private function acfHelper( $fields ) {
$images = [];
foreach ( $fields as $value ) {
if ( is_array( $value ) ) {
// Recursively loop over grouped fields.
// We continue on since arrays aren't necessarily groups and might also simply aLready contain the value we're looking for.
$images = array_merge( $images, $this->acfHelper( $value ) );
if ( isset( $value['type'] ) && 'image' !== strtolower( $value['type'] ) ) {
$images[] = $value['url'];
}
continue;
}
// Capture the value if it's an image URL, but not the default thumbnail from ACF.
if ( is_string( $value ) && preg_match( aioseo()->sitemap->image->getImageExtensionRegexPattern(), (string) $value ) && ! preg_match( '/media\/default\.png$/i', (string) $value ) ) {
$images[] = $value;
continue;
}
// Capture the value if it's a numeric image ID, but make sure it's not an array of random field object properties.
if (
is_numeric( $value ) &&
! isset( $fields['ID'] ) &&
! isset( $fields['thumbnail'] )
) {
$images[] = $value;
}
}
return $images;
}
/**
* Extracts images from Bricks Builder content.
*
* Bricks stores content in post meta, not in post_content.
* We need to render the Bricks content and extract images from the rendered HTML.
*
* @since 4.9.2
*
* @return void
*/
private function bricks() {
$bricksIntegration = aioseo()->standalone->pageBuilderIntegrations['bricks'] ?? null;
if ( empty( $bricksIntegration ) || ! $bricksIntegration->isBuiltWith( $this->post->ID ) ) {
return;
}
// Process the Bricks content to get rendered HTML.
$renderedContent = $bricksIntegration->processContent( $this->post->ID );
if ( empty( $renderedContent ) ) {
return;
}
// Extract image URLs from img tags in the rendered content.
$urls = [];
preg_match_all( '#
]+src=["\']([^"\'>]+)["\']#i', (string) $renderedContent, $matches );
if ( ! empty( $matches[1] ) ) {
foreach ( $matches[1] as $url ) {
$urls[] = aioseo()->helpers->makeUrlAbsolute( $url );
}
}
// Also extract background images from inline styles.
preg_match_all( '/background(?:-image)?:\s*url\(["\']?([^"\')]+)["\']?\)/i', (string) $renderedContent, $bgMatches );
if ( ! empty( $bgMatches[1] ) ) {
foreach ( $bgMatches[1] as $url ) {
$urls[] = aioseo()->helpers->makeUrlAbsolute( $url );
}
}
$this->images = array_merge( $this->images, $urls );
}
/**
* Extracts images from Divi shortcodes.
*
* @since 4.1.8
*
* @return void
*/
private function divi() {
if ( ! defined( 'ET_BUILDER_VERSION' ) ) {
return;
}
$urls = [];
$regex = implode( '|', array_map( 'preg_quote', $this->shortcodes ) );
preg_match_all(
"/\[($regex)(?![\w-])([^\]\/]*(?:\/(?!\])[^\]\/]*)*?)(?:(\/)\]|\](?:([^\[]*+(?:\[(?!\/\2\])[^\[]*+)*+)\[\/\2\])?)(\]?)/i",
(string) $this->post->post_content,
$matches,
PREG_SET_ORDER
);
foreach ( $matches as $shortcode ) {
$attributes = shortcode_parse_atts( $shortcode[0] );
if ( ! empty( $attributes['src'] ) ) {
$urls[] = $attributes['src'];
}
if ( ! empty( $attributes['image_src'] ) ) {
$urls[] = $attributes['image_src'];
}
if ( ! empty( $attributes['image_url'] ) ) {
$urls[] = $attributes['image_url'];
}
if ( ! empty( $attributes['portrait_url'] ) ) {
$urls[] = $attributes['portrait_url'];
}
if ( ! empty( $attributes['image'] ) ) {
$urls[] = $attributes['image'];
}
if ( ! empty( $attributes['background_image'] ) ) {
$urls[] = $attributes['background_image'];
}
if ( ! empty( $attributes['logo'] ) ) {
$urls[] = $attributes['logo'];
}
if ( ! empty( $attributes['gallery_ids'] ) ) {
$attachmentIds = explode( ',', $attributes['gallery_ids'] );
foreach ( $attachmentIds as $attachmentId ) {
$urls[] = wp_get_attachment_url( $attachmentId );
}
}
}
$this->images = array_merge( $this->images, $urls );
}
/**
* Extracts the image IDs of more advanced NextGen Pro gallerlies like the Mosaic and Thumbnail Grid.
*
* @since 4.2.5
*
* @return void
*/
private function nextGen() {
if ( ! defined( 'NGG_PLUGIN_BASENAME' ) && ! defined( 'NGG_PRO_PLUGIN_BASENAME' ) ) {
return;
}
preg_match_all( '/data-image-id=\"([0-9]*)\"/i', (string) $this->parsedPostContent, $imageIds );
if ( ! empty( $imageIds[1] ) ) {
$this->images = array_merge( $this->images, $imageIds[1] );
}
// For this specific check, we only want to parse blocks and do not want to run shortcodes because some NextGen blocks (e.g. Mosaic) are parsed into shortcodes.
// And after parsing the shortcodes, the attributes we're looking for are gone.
$contentWithBlocksParsed = do_blocks( $this->post->post_content );
$imageIds = [];
preg_match_all( '/\[ngg.*src="galleries" ids="(.*?)".*\]/i', (string) $contentWithBlocksParsed, $shortcodes );
if ( empty( $shortcodes[1] ) ) {
return;
}
foreach ( $shortcodes[1] as $shortcode ) {
$galleryIds = explode( ',', $shortcode[0] );
foreach ( $galleryIds as $galleryId ) {
global $nggdb;
$galleryImageIds = $nggdb->get_ids_from_gallery( $galleryId );
if ( empty( $galleryImageIds ) ) {
continue;
}
foreach ( $galleryImageIds as $galleryImageId ) {
$image = $nggdb->find_image( $galleryImageId );
if ( ! empty( $image ) ) {
$imageIds[] = $image->get_permalink();
}
}
}
}
$this->images = array_merge( $this->images, $imageIds );
}
/**
* Extracts images from Oxygen Builder content.
*
* Oxygen (via Breakdance engine) stores content in its own data structure.
* We need to render the content and extract images from the rendered HTML.
*
* @since 4.9.2
*
* @return void
*/
private function oxygen() {
$oxygenIntegration = aioseo()->standalone->pageBuilderIntegrations['oxygen'] ?? null;
if ( empty( $oxygenIntegration ) || ! $oxygenIntegration->isBuiltWith( $this->post->ID ) ) {
return;
}
// Process the Oxygen content to get rendered HTML.
$renderedContent = $oxygenIntegration->processContent( $this->post->ID );
if ( empty( $renderedContent ) ) {
return;
}
// Extract image URLs from img tags in the rendered content.
$urls = [];
preg_match_all( '#
]+src=["\']([^"\'>]+)["\']#i', (string) $renderedContent, $matches );
if ( ! empty( $matches[1] ) ) {
foreach ( $matches[1] as $url ) {
$urls[] = aioseo()->helpers->makeUrlAbsolute( $url );
}
}
// Also extract background images from inline styles.
preg_match_all( '/background(?:-image)?:\s*url\(["\']?([^"\')]+)["\']?\)/i', (string) $renderedContent, $bgMatches );
if ( ! empty( $bgMatches[1] ) ) {
foreach ( $bgMatches[1] as $url ) {
$urls[] = aioseo()->helpers->makeUrlAbsolute( $url );
}
}
$this->images = array_merge( $this->images, $urls );
}
/**
* Extracts the image IDs of WooCommerce product galleries.
*
* @since 4.1.2
*
* @return void
*/
private function wooCommerce() {
if ( ! aioseo()->helpers->isWooCommerceActive() || 'product' !== $this->post->post_type ) {
return;
}
$productImageIds = get_post_meta( $this->post->ID, '_product_image_gallery', true );
if ( ! $productImageIds ) {
return;
}
$productImageIds = explode( ',', $productImageIds );
$this->images = array_merge( $this->images, $productImageIds );
}
/**
* Extracts the image IDs of Kadence Block galleries.
*
* @since 4.4.5
*
* @return void
*/
private function kadenceBlocks() {
if ( ! defined( 'KADENCE_BLOCKS_VERSION' ) ) {
return [];
}
$blocks = aioseo()->helpers->parseBlocks( $this->post );
foreach ( $blocks as $block ) {
if ( 'kadence/advancedgallery' === $block['blockName'] && ! empty( $block['attrs']['ids'] ) ) {
$this->images = array_merge( $this->images, $block['attrs']['ids'] );
}
}
}
}