\x20\40\x20\40 HEX
HEX
Server: Apache
System: Linux web1.jenscom.net 4.18.0-553.111.1.el8_10.x86_64 #1 SMP Sun Mar 8 20:06:07 EDT 2026 x86_64
User: sps (1059)
PHP: 8.3.30
Disabled: NONE
Upload Files
File: /home/sps/www/wp-content/plugins/nextgen-gallery/src/REST/ConvertGallery/ConvertGalleryTrait.php
<?php
/**
 * Convert Gallery Trait - Core conversion logic for WP Gallery to NextGEN Gallery.
 *
 * @package Imagely\NGG\REST\ConvertGallery
 * @since 3.x
 */

namespace Imagely\NGG\REST\ConvertGallery;

use Imagely\NGG\DataMappers\Gallery as GalleryMapper;
use Imagely\NGG\DataMappers\Image as ImageMapper;
use Imagely\NGG\DataStorage\Manager as StorageManager;
use Imagely\NGG\DataTypes\Gallery;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Convert Gallery Trait.
 */
trait ConvertGalleryTrait {

	/**
	 * Create an Imagely (NextGEN) Gallery using WordPress Gallery data.
	 *
	 * @param array $passed_data WP gallery passed data array.
	 * @return array Result with gallery_id or error.
	 */
	public function create_imagely_gallery_from_wp_gallery( $passed_data ) {
		$post_id       = $passed_data['post_id'] ?? null;
		$columns       = $passed_data['columns'] ?? 3;
		$size_slug     = $passed_data['size_slug'] ?? 'thumbnail';
		$link_target   = $passed_data['link_target'] ?? '';
		$images        = $passed_data['images'] ?? [];
		$block_content = $passed_data['block_content'] ?? '';

		// Generate gallery title.
		$date_now      = wp_date( 'Y-m-d H:i:s' );
		$gallery_title = sprintf( 'Converted-%s', $date_now );

		if ( ! empty( $post_id ) ) {
			// Save the block content to post meta using a unique meta key for backup.
			$date_prefix = wp_date( 'Ymd_His' );
			$meta_key    = 'wp_gallery_block_bkp_' . wp_rand( 1000, 9999 ) . '_' . $date_prefix;
			update_post_meta( $post_id, $meta_key, $block_content );

			// Get the post title.
			$post_title = get_the_title( $post_id );

			if ( ! empty( $post_title ) ) {
				// Truncate title to 15-20 characters (using mb_* functions for multibyte support).
				$truncated_title = mb_strlen( $post_title ) > 20 ? mb_substr( $post_title, 0, 20 ) : $post_title;

				// Generate a unique gallery title.
				$gallery_title = sprintf(
					'%s-%d-Converted-%s',
					$truncated_title,
					$post_id,
					$date_now
				);
			}
		}

		// Create the NextGEN Gallery.
		$gallery_mapper = GalleryMapper::get_instance();
		$gallery        = $gallery_mapper->create( [ 'title' => $gallery_title ] );

		if ( ! $gallery->save() ) {
			return [
				'error' => __(
					'There was a problem creating the gallery. Please try again.',
					'nggallery'
				),
			];
		}

		$gallery_id = $gallery->id();

		// Import images from WordPress Media Library.
		$storage      = StorageManager::get_instance();
		$image_mapper = ImageMapper::get_instance();
		$image_ids    = [];
		$errors       = [];

		// Raise memory limit for image processing.
		if ( function_exists( 'wp_raise_memory_limit' ) ) {
			wp_raise_memory_limit( 'image' );
		}

		foreach ( $images as $image_data ) {
			$attachment_id = $image_data['id'] ?? 0;

			if ( empty( $attachment_id ) ) {
				continue;
			}

			try {
				$abspath = get_attached_file( $attachment_id );

				if ( ! $abspath || ! file_exists( $abspath ) ) {
					$errors[] = sprintf(
						// translators: %d is the attachment ID.
						__( 'Could not find file for attachment ID %d', 'nggallery' ),
						$attachment_id
					);
					continue;
				}

				$file_data = file_get_contents( $abspath );

				if ( empty( $file_data ) ) {
					$errors[] = sprintf(
						// translators: %d is the attachment ID.
						__( 'Could not read file for attachment ID %d', 'nggallery' ),
						$attachment_id
					);
					continue;
				}

				$file_name  = \Imagely\NGG\Display\I18N::mb_basename( $abspath );
				$attachment = get_post( $attachment_id );
				$ngg_image  = $storage->upload_image( $gallery_id, $file_name, $file_data );

				if ( $ngg_image ) {
					// Import metadata from WordPress attachment.
					$ngg_image = $image_mapper->find( $ngg_image );

					// Use the alt text from the passed data, WordPress attachment, or title as fallback.
					// Priority: 1) Provided alt text, 2) WP attachment caption, 3) WP attachment alt meta, 4) Title as last resort.
					if ( ! empty( $image_data['alt'] ) ) {
						$ngg_image->alttext = $image_data['alt'];
					} elseif ( $attachment instanceof \WP_Post && ! empty( $attachment->post_excerpt ) ) {
						$ngg_image->alttext = $attachment->post_excerpt;
					} else {
						$attachment_alt = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );
						if ( $attachment_alt ) {
							$ngg_image->alttext = $attachment_alt;
						} elseif ( ! empty( $image_data['title'] ) ) {
							// Only use title as fallback if no alt text is available.
							$ngg_image->alttext = $image_data['title'];
						}
					}
					// Use description from WordPress attachment.
					if ( $attachment instanceof \WP_Post && ! empty( $attachment->post_content ) ) {
						$ngg_image->description = $attachment->post_content;
					}

					// Apply filters and save.
					$ngg_image = apply_filters( 'ngg_wp_gallery_converted_image', $ngg_image, $attachment, $image_data );
					$image_mapper->save( $ngg_image );
					$image_ids[] = $ngg_image->{$ngg_image->id_field};
				} else {
					$errors[] = sprintf(
						// translators: %s is the filename.
						__( 'Failed to import image: %s', 'nggallery' ),
						$file_name
					);
				}
			} catch ( \RuntimeException $ex ) {
				$errors[] = $ex->getMessage();
			} catch ( \Exception $ex ) {
				$errors[] = sprintf(
					// translators: %d is the attachment ID.
					__( 'Unexpected error importing attachment ID %d', 'nggallery' ),
					$attachment_id
				);
			}
		}

		// Set the first image as preview if we imported any.
		if ( ! empty( $image_ids ) ) {
			$gallery->previewpic = $image_ids[0];
			$gallery_mapper->save( $gallery );
		}

		// If no images were imported, delete the gallery and return error.
		if ( empty( $image_ids ) ) {
			$gallery_mapper->destroy( $gallery_id );
			return [
				'error' => __(
					'No images could be imported. The gallery was not created.',
					'nggallery'
				),
			];
		}

		$response_data = [
			'gallery_id'  => $gallery_id,
			'title'       => $gallery_title,
			'columns'     => $columns,
			'image_count' => count( $image_ids ),
			'image_ids'   => $image_ids,
			'errors'      => $errors,
			'message'     => __( 'Converted successfully. Don\'t forget to save your changes!', 'nggallery' ),
		];

		return $response_data;
	}

	/**
	 * Process gallery shortcodes in post content.
	 *
	 * @param string $updated_content Reference to content being updated.
	 * @param int    $post_id         Post ID.
	 * @param bool   $needs_update    Reference to flag indicating if update is needed.
	 * @return \WP_REST_Response|void
	 */
	public function process_gallery_shortcodes( &$updated_content, $post_id, &$needs_update ) {
		if ( ! has_shortcode( $updated_content, 'gallery' ) ) {
			return;
		}

		preg_match_all( '/\[gallery(.*?)\]/', $updated_content, $matches, PREG_SET_ORDER );

		foreach ( $matches as $shortcode ) {
			$shortcode_string = $shortcode[0];
			$shortcode_attrs  = shortcode_parse_atts( $shortcode[1] );

			// Extract gallery attributes.
			$ids        = isset( $shortcode_attrs['ids'] ) ? explode( ',', $shortcode_attrs['ids'] ) : [];
			$include    = isset( $shortcode_attrs['include'] ) ? explode( ',', $shortcode_attrs['include'] ) : [];
			$exclude    = isset( $shortcode_attrs['exclude'] ) ? explode( ',', $shortcode_attrs['exclude'] ) : [];
			$columns    = isset( $shortcode_attrs['columns'] ) ? absint( $shortcode_attrs['columns'] ) : 3;
			$size_slug  = isset( $shortcode_attrs['size'] ) ? sanitize_text_field( $shortcode_attrs['size'] ) : 'thumbnail';
			$gallery_id = isset( $shortcode_attrs['id'] ) ? absint( $shortcode_attrs['id'] ) : 0;

			// Fetch attachments for the shortcode.
			if ( empty( $ids ) ) {
				$query_args = [
					'post_type'      => 'attachment',
					'post_status'    => 'inherit',
					'posts_per_page' => -1,
					'fields'         => 'ids',
					'post_parent'    => $gallery_id ? $gallery_id : $post_id,
				];

				if ( ! empty( $include ) ) {
					$query_args['post__in'] = array_map( 'absint', $include );
				}

				if ( ! empty( $exclude ) ) {
					$query_args['post__not_in'] = array_map( 'absint', $exclude );
				}

				$ids = get_posts( $query_args );
			}

			// Create gallery images array.
			$images = [];
			foreach ( $ids as $id ) {
				$image_id    = absint( $id );
				$image_url   = wp_get_attachment_url( $image_id );
				$image_title = get_the_title( $image_id );
				$image_alt   = get_post_meta( $image_id, '_wp_attachment_image_alt', true );

				if ( $image_url ) {
					$images[] = [
						'id'    => $image_id,
						'url'   => $image_url,
						'title' => $image_title,
						'alt'   => $image_alt,
					];
				}
			}

			if ( ! empty( $images ) ) {
				$passed_data = [
					'post_id'       => $post_id,
					'columns'       => $columns,
					'size_slug'     => $size_slug,
					'link_target'   => '_self',
					'images'        => $images,
					'block_content' => $shortcode_string,
				];

				$created_result = $this->create_imagely_gallery_from_wp_gallery( $passed_data );

				if ( isset( $created_result['error'] ) && ! empty( $created_result['error'] ) ) {
					return new \WP_REST_Response( [ 'message' => $created_result['error'] ], 400 );
				} else {
					$imagely_shortcode = "[imagely id=\"{$created_result['gallery_id']}\"]";
					$updated_content   = str_replace( $shortcode_string, $imagely_shortcode, $updated_content );
					$needs_update      = true;
				}
			}
		}
	}

	/**
	 * Process gallery blocks in post content.
	 *
	 * @param array $blocks       Reference to parsed blocks array.
	 * @param int   $post_id      Post ID.
	 * @param bool  $needs_update Reference to flag indicating if update is needed.
	 * @return void
	 */
	public function process_gallery_blocks( &$blocks, $post_id, &$needs_update ) {
		foreach ( $blocks as &$block ) {
			// If the block is a gallery block.
			if ( 'core/gallery' === $block['blockName'] ) {
				// Extract the attributes.
				$columns       = $block['attrs']['columns'] ?? 3;
				$size_slug     = $block['attrs']['sizeSlug'] ?? 'thumbnail';
				$link_target   = $block['attrs']['linkTo'] ?? '';
				$block_content = serialize_block( $block );

				$images = [];

				// Check if there are inner blocks (Gutenberg gallery structure).
				if ( isset( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
					foreach ( $block['innerBlocks'] as $inner_block ) {
						// If the inner block is an image block.
						if ( 'core/image' === $inner_block['blockName'] ) {
							$image_id = isset( $inner_block['attrs']['id'] ) ? absint( $inner_block['attrs']['id'] ) : 0;
							if ( $image_id ) {
								$image_url   = wp_get_attachment_url( $image_id );
								$image_title = get_the_title( $image_id );

								// Use the `alt` from the HTML if not available in attributes.
								$image_alt = '';
								if ( ! empty( $inner_block['innerHTML'] ) ) {
									$doc                          = new \DOMDocument( '1.0', 'UTF-8' );
									$previous_use_internal_errors = libxml_use_internal_errors( true );
									// Prepend UTF-8 encoding declaration to prevent character corruption.
									$loaded = $doc->loadHTML( '<?xml encoding="UTF-8">' . $inner_block['innerHTML'] );
									libxml_clear_errors();
									libxml_use_internal_errors( $previous_use_internal_errors );

									if ( $loaded ) {
										$img_tag = $doc->getElementsByTagName( 'img' )->item( 0 );
										if ( $img_tag && $img_tag->hasAttribute( 'alt' ) ) {
											$image_alt = $img_tag->getAttribute( 'alt' );
										}
									}
								}

								// Fallback: If `alt` is not in HTML, try `_wp_attachment_image_alt` meta.
								if ( ! $image_alt ) {
									$image_alt = get_post_meta( $image_id, '_wp_attachment_image_alt', true );
								}

								$images[] = [
									'id'    => $image_id,
									'url'   => $image_url,
									'title' => $image_title,
									'alt'   => $image_alt,
								];
							}
						}
					}
				}

				if ( empty( $images ) || ! is_array( $images ) ) {
					// Recursively process inner blocks even if this gallery has no images.
					if ( isset( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
						$this->process_gallery_blocks( $block['innerBlocks'], $post_id, $needs_update );
					}
					continue;
				}

				$passed_data = [
					'post_id'       => $post_id,
					'columns'       => $columns,
					'size_slug'     => $size_slug,
					'link_target'   => $link_target,
					'images'        => $images,
					'block_content' => $block_content,
				];

				$created_result = $this->create_imagely_gallery_from_wp_gallery( $passed_data );

				if ( isset( $created_result['error'] ) && ! empty( $created_result['error'] ) ) {
					// Continue processing other blocks even if one fails.
					continue;
				} else {
					// Replace the core/gallery block with imagely/main-block.
					$block['blockName']    = 'imagely/main-block';
					$block['attrs']        = [
						'content' => "[imagely id=\"{$created_result['gallery_id']}\"]",
					];
					$block['innerBlocks']  = [];
					$block['innerHTML']    = "[imagely id=\"{$created_result['gallery_id']}\"]";
					$block['innerContent'] = [
						"[imagely id=\"{$created_result['gallery_id']}\"]",
					];

					$needs_update = true;
				}
			}

			// Recursively process inner blocks.
			if ( isset( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
				$this->process_gallery_blocks( $block['innerBlocks'], $post_id, $needs_update );
			}
		}
	}

	/**
	 * Check if the current user can edit the post.
	 *
	 * @param int $post_id Post ID.
	 * @return bool
	 */
	public function can_edit_post( $post_id ) {
		return current_user_can( 'edit_post', $post_id );
	}
}