dvadf
File manager - Edit - /home/centroca/public_html/wp/wp-content/plugins/nextgen-gallery/src/REST/DataMappers/SettingsREST.php
Back
<?php /** * REST API endpoints for NextGEN Gallery settings. * * @package Imagely\NGG\REST\DataMappers */ namespace Imagely\NGG\REST\DataMappers; use WP_REST_Request; use WP_REST_Response; use WP_Error; use Imagely\NGG\Settings\GalleryPathValidation; use Imagely\NGG\Settings\Settings; use Imagely\NGG\Settings\GlobalSettings; use Imagely\NGG\Util\Security; use Imagely\NGG\DynamicThumbnails\Manager as DynamicThumbnailsManager; use Imagely\NGG\DataMappers\Image as ImageMapper; use Imagely\NGG\DataStorage\Manager as StorageManager; use Imagely\NGG\DataMappers\Gallery as GalleryMapper; /** * Class SettingsREST * Handles REST API endpoints for NextGEN Gallery settings. * * @package Imagely\NGG\REST\DataMappers */ class SettingsREST { /** * Register the REST API routes for settings */ public static function register_routes() { register_rest_route( 'imagely/v1', '/settings', [ [ 'methods' => 'GET', 'callback' => [ self::class, 'get_settings' ], 'permission_callback' => [ self::class, 'check_read_permission' ], ], [ 'methods' => 'PUT', 'callback' => [ self::class, 'update_settings' ], 'permission_callback' => [ self::class, 'check_edit_permission' ], ], ] ); // Get and update global settings (multisite). register_rest_route( 'imagely/v1', '/settings/global', [ [ 'methods' => 'GET', 'callback' => [ self::class, 'get_global_settings' ], 'permission_callback' => [ self::class, 'check_read_permission' ], ], [ 'methods' => 'PUT', 'callback' => [ self::class, 'update_global_settings' ], 'permission_callback' => [ self::class, 'check_edit_permission' ], ], ] ); // Generate watermark preview. register_rest_route( 'imagely/v1', '/settings/watermark-preview', [ 'methods' => 'POST', 'callback' => [ self::class, 'get_watermark_preview' ], 'permission_callback' => [ self::class, 'check_edit_permission' ], 'args' => [ 'watermark_options' => [ 'type' => 'object', 'required' => false, 'description' => 'Watermark configuration options', 'validate_callback' => function ( $param ) { return is_array( $param ) || is_object( $param ); }, ], ], ] ); // Reset settings to defaults. register_rest_route( 'imagely/v1', '/settings/reset', [ 'methods' => 'POST', 'callback' => [ self::class, 'reset_settings' ], 'permission_callback' => [ self::class, 'check_edit_permission' ], 'args' => [ 'confirm' => [ 'type' => 'boolean', 'required' => true, 'description' => 'Confirmation flag to prevent accidental resets', 'validate_callback' => function ( $param ) { return is_bool( $param ) && $param; }, ], 'settings_type' => [ 'type' => 'string', 'required' => false, 'default' => 'all', 'description' => 'Type of settings to reset: all, non_ecommerce, or ecommerce', 'enum' => [ 'all', 'non_ecommerce', 'ecommerce' ], 'validate_callback' => function ( $param ) { return in_array( $param, [ 'all', 'non_ecommerce', 'ecommerce' ], true ); }, ], ], ] ); // Clear all cache. register_rest_route( 'imagely/v1', '/cache/clear', [ 'methods' => 'POST', 'callback' => [ self::class, 'clear_cache' ], 'permission_callback' => [ self::class, 'check_edit_permission' ], ] ); // Regenerate all thumbnails. register_rest_route( 'imagely/v1', '/thumbnails/regenerate', [ 'methods' => 'POST', 'callback' => [ self::class, 'regenerate_thumbnails' ], 'permission_callback' => [ self::class, 'check_edit_permission' ], ] ); // Get system information. register_rest_route( 'imagely/v1', '/system-info', [ 'methods' => 'GET', 'callback' => [ self::class, 'get_system_info' ], 'permission_callback' => [ self::class, 'check_read_permission' ], ] ); // Check if gtag.js is loaded on the site frontend (for GA4 integration). register_rest_route( 'imagely/v1', '/settings/gtag-status', [ 'methods' => 'GET', 'callback' => [ self::class, 'get_gtag_status' ], 'permission_callback' => [ self::class, 'check_read_permission' ], 'args' => [ 'force' => [ 'type' => 'boolean', 'default' => false, ], ], ] ); } /** * Get the value type for a setting based on the example JSON. * Handles nested objects and stringified numbers/booleans. * * @param mixed $value The value to check. * @return string */ protected static function get_value_type( $value ) { if ( is_null( $value ) ) { // For null values, allow mixed types (string, integer, or null) return [ 'string', 'integer', 'null' ]; } if ( is_array( $value ) ) { // Check if associative (object) or sequential (array) return self::is_assoc( $value ) ? 'object' : 'array'; } if ( is_bool( $value ) ) { return 'boolean'; } if ( is_int( $value ) ) { return 'integer'; } if ( is_float( $value ) ) { return 'number'; } if ( is_string( $value ) ) { // Only detect stringified types if they are obvious candidates // This prevents over-aggressive type conversion if ( 'true' === $value || 'false' === $value ) { return 'boolean'; } // Be more conservative with numeric detection // Only convert if it's a simple numeric string without leading zeros if ( is_numeric( $value ) && (string) (int) $value === $value && false === strpos( $value, '.' ) ) { return 'integer'; } if ( is_numeric( $value ) && false !== strpos( $value, '.' ) && (string) (float) $value === $value ) { return 'number'; } return 'string'; } return 'string'; // Default to string for unknown types. } /** * Helper to check if an array is associative (object-like). * * @param array $arr The array to check. * @return bool */ protected static function is_assoc( array $arr ) { if ( [] === $arr ) { return false; } return array_keys( $arr ) !== range( 0, count( $arr ) - 1 ); } /** * Sanitize a setting value for security. * Simple approach - detect if content needs HTML/newlines preserved. * * @param mixed $value The value to sanitize. * @param string $key The setting key (to determine appropriate sanitization). * @return mixed */ protected static function sanitize_setting( $value, $key = '' ) { // Handle arrays and objects recursively if ( is_array( $value ) ) { return array_map( function ( $v ) use ( $key ) { return self::sanitize_setting( $v, $key ); }, $value ); } // Settings class will convert objects to arrays if ( is_object( $value ) ) { return (array) $value; } // Let null, boolean, and numeric values pass through as-is if ( is_null( $value ) || is_bool( $value ) || is_numeric( $value ) ) { return $value; } // Convert to string for processing $string_value = (string) $value; // Fields that should preserve HTML and newlines if ( self::should_preserve_html( $key, $string_value ) ) { return wp_kses_post( $string_value ); } // Fields that should preserve newlines but strip HTML if ( self::should_preserve_newlines( $key, $string_value ) ) { return sanitize_textarea_field( $string_value ); } // GA4 Measurement ID: must start with G- followed by alphanumeric characters (e.g. G-XXXXXXXXXX) if ( 'ga4_measurement_id' === $key ) { $trimmed = trim( $string_value ); if ( empty( $trimmed ) ) { return ''; } if ( preg_match( '/^G-[A-Za-z0-9_-]+$/', $trimmed ) ) { return sanitize_text_field( $trimmed ); } return ''; } // Default: strip HTML and newlines for security return sanitize_text_field( $string_value ); } /** * Check if a field should preserve HTML content. * * @param string $key The setting key. * @param string $value The setting value. * @return bool */ protected static function should_preserve_html( $key, $value ) { // Fields that commonly contain HTML or special placeholders $html_fields = [ 'ecommerce_cheque_instructions', 'proofing_user_confirmation_template', 'proofing_email_template', // Contains %% placeholders that need protection 'relatedHeading', ]; // Check if field name indicates HTML content if ( in_array( $key, $html_fields, true ) ) { return true; } // Auto-detect: if value contains HTML tags, preserve them if ( wp_strip_all_tags( $value ) !== $value ) { return true; } return false; } /** * Check if a field should preserve newlines. * * @param string $key The setting key. * @param string $value The setting value. * @return bool */ protected static function should_preserve_newlines( $key, $value ) { // Fields that commonly contain newlines (email templates, etc.) $newline_fields = [ 'ecommerce_email_notification_body', 'ecommerce_email_receipt_body', 'proofing_user_email_template', ]; // Check if field name indicates newline content if ( in_array( $key, $newline_fields, true ) ) { return true; } // Auto-detect: if value contains newlines, preserve them if ( strpos( $value, "\n" ) !== false ) { return true; } return false; } /** * Check if user has permission to read settings * * @return bool */ public static function check_read_permission() { return Security::is_allowed( 'NextGEN Change options' ); } /** * Check if user has permission to edit settings * * @return bool */ public static function check_edit_permission() { return Security::is_allowed( 'NextGEN Change options' ); } /** * Get all settings * * @return \WP_REST_Response */ public static function get_settings() { $settings = Settings::get_instance(); return new WP_REST_Response( $settings->to_array(), 200 ); } /** * @param mixed $gallerypath Raw request value. * @return true|\WP_Error */ protected static function validate_gallerypath_rest_value( $gallerypath ) { $result = GalleryPathValidation::validate_relative_under_galleries_root( $gallerypath ); if ( is_wp_error( $result ) ) { $data = $result->get_error_data(); if ( ! is_array( $data ) ) { $data = []; } $data['status'] = 400; return new WP_Error( $result->get_error_code(), $result->get_error_message(), $data ); } return true; } /** * Update settings via REST API request. * * @param \WP_REST_Request $request The request object. * @return \WP_REST_Response|\WP_Error */ public static function update_settings( WP_REST_Request $request ) { $new_settings = $request->get_json_params(); if ( ! is_array( $new_settings ) ) { $new_settings = []; } if ( is_multisite() && isset( $new_settings['gallerypath'] ) && ! is_main_site() && ! is_super_admin() ) { unset( $new_settings['gallerypath'] ); } if ( isset( $new_settings['gallerypath'] ) ) { $gallerypath_validation = self::validate_gallerypath_rest_value( $new_settings['gallerypath'] ); if ( is_wp_error( $gallerypath_validation ) ) { return $gallerypath_validation; } } $settings = Settings::get_instance(); foreach ( $new_settings as $key => $value ) { // Security sanitization with context-aware handling $sanitized_value = self::sanitize_setting( $value, $key ); $settings->set( $key, $sanitized_value ); } try { $settings->save(); return new WP_REST_Response( $settings->to_array(), 200 ); } catch ( \Exception $e ) { return new WP_Error( 'settings_update_failed', $e->getMessage(), [ 'status' => 500 ] ); } } /** * Get global settings * * @return \WP_REST_Response | \WP_Error */ public static function get_global_settings() { if ( ! is_multisite() ) { return new WP_Error( 'not_multisite', __( 'Global settings are only available in multisite installations', 'nggallery' ), [ 'status' => 400 ] ); } $settings = GlobalSettings::get_instance(); return new WP_REST_Response( $settings->to_array(), 200 ); } /** * Update global settings * * @param \WP_REST_Request $request The request object. * @return \WP_REST_Response|\WP_Error */ public static function update_global_settings( WP_REST_Request $request ) { if ( ! is_multisite() ) { return new WP_Error( 'not_multisite', __( 'Global settings are only available in multisite installations', 'nggallery' ), [ 'status' => 400 ] ); } $new_settings = $request->get_json_params(); $settings = GlobalSettings::get_instance(); foreach ( $new_settings as $key => $value ) { // Security sanitization with context-aware handling $sanitized_value = self::sanitize_setting( $value, $key ); $settings->set( $key, $sanitized_value ); } try { $settings->save(); return new WP_REST_Response( $settings->to_array(), 200 ); } catch ( \Exception $e ) { return new WP_Error( 'settings_update_failed', $e->getMessage(), [ 'status' => 500 ] ); } } /** * Get watermark preview * * @param \WP_REST_Request $request The request object. * @return \WP_REST_Response|\WP_Error */ public static function get_watermark_preview( WP_REST_Request $request ) { $watermark_options = $request->get_param( 'watermark_options' ); // Check if watermark options are provided if ( empty( $watermark_options ) ) { return new WP_Error( 'missing_watermark_options', __( 'Watermark options are required', 'nggallery' ), [ 'status' => 400 ] ); } // Validate watermark type $wm_type = $watermark_options['wmType'] ?? ''; if ( empty( $wm_type ) || ! in_array( $wm_type, [ 'text', 'image' ], true ) ) { return new WP_Error( 'invalid_watermark_type', __( 'Invalid watermark type. Must be "text" or "image".', 'nggallery' ), [ 'status' => 400 ] ); } // Validate text watermark requirements if ( 'text' === $wm_type ) { $wm_text = $watermark_options['wmText'] ?? ''; if ( empty( trim( $wm_text ) ) ) { return new WP_Error( 'empty_watermark_text', __( 'Watermark text cannot be empty when using text watermarks.', 'nggallery' ), [ 'status' => 400 ] ); } } // Validate image watermark requirements if ( 'image' === $wm_type ) { $wm_path = trim( $watermark_options['wmPath'] ?? '' ); if ( empty( $wm_path ) ) { return new WP_Error( 'missing_watermark_image', __( 'Watermark image path is required when using image watermarks.', 'nggallery' ), [ 'status' => 400 ] ); } // Allow either absolute URLs, absolute filesystem paths, or server-relative paths. $is_url = is_string( $wm_path ) && preg_match( '/^https?:\/\//i', $wm_path ); $is_abs = is_string( $wm_path ) && ( 0 === strpos( $wm_path, '/' ) || preg_match( '/^[A-Za-z]:[\\\\\/]*/', $wm_path ) ); // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf -- URL case has no file check. if ( $is_url ) { // URL will be downloaded later; no file_exists check here. } elseif ( $is_abs ) { // Absolute filesystem path must exist if ( ! file_exists( $wm_path ) ) { return new WP_Error( 'invalid_watermark_image', __( 'Watermark image file does not exist.', 'nggallery' ), [ 'status' => 400 ] ); } } else { // Treat as server-relative path under ABSPATH $full_path = ABSPATH . ltrim( $wm_path, '/' ); if ( ! file_exists( $full_path ) ) { return new WP_Error( 'invalid_watermark_image', __( 'Watermark image file does not exist.', 'nggallery' ), [ 'status' => 400 ] ); } } } try { // Get NextGen managers $mapper = ImageMapper::get_instance(); $storage = StorageManager::get_instance(); // Find the first available NextGen image $images = $mapper->find_all(); if ( empty( $images ) ) { return new WP_Error( 'no_images_found', __( 'No images found in NextGen Gallery. Please add images to a gallery first.', 'nggallery' ), [ 'status' => 400 ] ); } $original_image = null; // Look for the first usable image foreach ( $images as $image ) { // Skip dynamic thumbnails and thumbs if ( strpos( $image->filename, '-nggid' ) === false && strpos( $image->filename, 'thumbs-' ) !== 0 ) { // Verify the image file actually exists $image_path = $storage->get_image_abspath( $image ); if ( $image_path && file_exists( $image_path ) ) { $original_image = $image; break; } } } if ( ! $original_image ) { return new WP_Error( 'no_usable_images', __( 'No usable images found for preview generation.', 'nggallery' ), [ 'status' => 400 ] ); } // Prepare watermark path for generation $final_wm_path = trim( $watermark_options['wmPath'] ?? '' ); $downloaded_tmp = false; $tmp_download_path = ''; if ( 'image' === ( $watermark_options['wmType'] ?? '' ) && ! empty( $final_wm_path ) ) { $is_url = is_string( $final_wm_path ) && preg_match( '/^https?:\/\//i', $final_wm_path ); $is_abs = is_string( $final_wm_path ) && ( 0 === strpos( $final_wm_path, '/' ) || preg_match( '/^[A-Za-z]:[\\\\\/]*/', $final_wm_path ) ); if ( $is_url ) { // Download remote watermark image to uploads folder for preview use $upload_dir = wp_upload_dir(); $preview_dir = trailingslashit( $upload_dir['basedir'] ) . 'ngg-watermark-previews/'; if ( ! is_dir( $preview_dir ) ) { wp_mkdir_p( $preview_dir ); } // SSRF hardening: validate URL parts and block private/loopback/link-local hosts before fetch. $parsed_url = wp_parse_url( $final_wm_path ); $scheme = isset( $parsed_url['scheme'] ) ? strtolower( $parsed_url['scheme'] ) : ''; $host = isset( $parsed_url['host'] ) ? strtolower( $parsed_url['host'] ) : ''; if ( ! in_array( $scheme, [ 'http', 'https' ], true ) || '' === $host ) { return new WP_Error( 'invalid_watermark_image', __( 'Invalid watermark image URL.', 'nggallery' ), [ 'status' => 400 ] ); } // Reject hostnames/IP literals (incl. bracketed IPv6) that resolve to non-public IPs (loopback, RFC1918, link-local, unique-local, cloud metadata). $host_unbracketed = preg_replace( '/^\[|\]$/', '', $host ); // Strip IPv6 brackets for filter_var validation. if ( filter_var( $host_unbracketed, FILTER_VALIDATE_IP ) ) { // Direct IP literal (v4 or v6) — validate public-range inline; skip DNS to avoid gethostbyname IPv4-only gap. if ( ! filter_var( $host_unbracketed, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) { return new WP_Error( 'invalid_watermark_image', __( 'Watermark URL host is not allowed.', 'nggallery' ), [ 'status' => 400 ] ); } } else { // Hostname — resolve A and AAAA records; reject if any resolved IP is private/reserved. $resolved_a = gethostbyname( $host_unbracketed ); $resolved_a = ( $resolved_a !== $host_unbracketed ) ? $resolved_a : null; // gethostbyname() only resolves IPv4 (A records); also check IPv6 (AAAA) to prevent SSRF bypass. $aaaa_records = dns_get_record( $host_unbracketed, DNS_AAAA ); $resolved_aaaa = ( is_array( $aaaa_records ) && ! empty( $aaaa_records ) && isset( $aaaa_records[0]['ipv6'] ) ) ? $aaaa_records[0]['ipv6'] : null; if ( null === $resolved_a && null === $resolved_aaaa ) { return new WP_Error( 'invalid_watermark_image', __( 'Unable to resolve watermark URL host.', 'nggallery' ), [ 'status' => 400 ] ); } foreach ( array_filter( [ $resolved_a, $resolved_aaaa ] ) as $resolved_ip ) { if ( ! filter_var( $resolved_ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) { return new WP_Error( 'invalid_watermark_image', __( 'Watermark URL host is not allowed.', 'nggallery' ), [ 'status' => 400 ] ); } } } // Force safe image extension rather than trusting attacker-controlled URL path extension (RCE hardening). $url_ext = strtolower( (string) pathinfo( wp_parse_url( $final_wm_path, PHP_URL_PATH ), PATHINFO_EXTENSION ) ); $allowed_exts = [ 'png', 'jpg', 'jpeg', 'gif', 'webp' ]; $ext = in_array( $url_ext, $allowed_exts, true ) ? $url_ext : 'png'; $tmp_name = 'wm_' . uniqid( '', true ) . '.' . $ext; $tmp_path = $preview_dir . $tmp_name; $response = wp_safe_remote_get( $final_wm_path, [ 'timeout' => 15 ] ); // Use wp_safe_remote_get to honor HTTP request safe-host filters. if ( is_wp_error( $response ) || (int) wp_remote_retrieve_response_code( $response ) !== 200 ) { return new WP_Error( 'invalid_watermark_image', __( 'Unable to fetch watermark image from the provided URL.', 'nggallery' ), [ 'status' => 400 ] ); } $body = wp_remote_retrieve_body( $response ); if ( empty( $body ) ) { return new WP_Error( 'invalid_watermark_image', __( 'Empty response when fetching watermark image URL.', 'nggallery' ), [ 'status' => 400 ] ); } // Write file to disk file_put_contents( $tmp_path, $body ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents if ( ! file_exists( $tmp_path ) ) { return new WP_Error( 'invalid_watermark_image', __( 'Failed to store downloaded watermark image.', 'nggallery' ), [ 'status' => 500 ] ); } // Verify downloaded bytes are an actual image; prevents storing PHP/HTML payload under ngg-watermark-previews/. $image_info = @getimagesize( $tmp_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged if ( false === $image_info ) { @unlink( $tmp_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.unlink_unlink return new WP_Error( 'invalid_watermark_image', __( 'Downloaded file is not a valid image.', 'nggallery' ), [ 'status' => 400 ] ); } // Use the absolute filesystem path directly — downstream ngglegacy_thumbnail checks is_file() first, so absolute paths work without conversion. str_replace(ABSPATH) breaks when uploads dir lives outside ABSPATH. $final_wm_path = $tmp_path; $downloaded_tmp = true; $tmp_download_path = $tmp_path; } elseif ( $is_abs ) { // Path-traversal hardening: resolve symlinks/.. segments and constrain absolute paths to ABSPATH or the uploads dir only. $real_wm_path = realpath( $final_wm_path ); // realpath normalizes ../ and resolves symlinks before allowlist check. $upload_dir = wp_upload_dir(); $real_abspath = realpath( ABSPATH ); // Normalize ABSPATH too so string prefix match is apples-to-apples. $real_uploads = isset( $upload_dir['basedir'] ) ? realpath( $upload_dir['basedir'] ) : false; // Uploads dir may live outside ABSPATH on some installs. $under_abspath = ( $real_wm_path && $real_abspath && 0 === strpos( trailingslashit( $real_wm_path ), trailingslashit( $real_abspath ) ) ); // trailingslashit prevents partial dir-name match (e.g. /var/www/html_secret matching /var/www/html). $under_uploads = ( $real_wm_path && $real_uploads && 0 === strpos( trailingslashit( $real_wm_path ), trailingslashit( $real_uploads ) ) ); // Allow uploads dir when outside ABSPATH. if ( ! $real_wm_path || ( ! $under_abspath && ! $under_uploads ) ) { // Reject arbitrary filesystem reads (e.g. /etc/passwd) via watermark path — prior code copied any readable file into a public previews dir. return new WP_Error( 'invalid_watermark_image', __( 'Watermark image path is not allowed.', 'nggallery' ), [ 'status' => 400 ] ); } // Extension allowlist: prevents non-image files (e.g. .php) from being referenced as watermark source. $abs_ext = strtolower( (string) pathinfo( $real_wm_path, PATHINFO_EXTENSION ) ); $allowed_exts = [ 'png', 'jpg', 'jpeg', 'gif', 'webp' ]; if ( ! in_array( $abs_ext, $allowed_exts, true ) ) { return new WP_Error( 'invalid_watermark_image', __( 'Watermark image must be png, jpg, jpeg, gif, or webp.', 'nggallery' ), [ 'status' => 400 ] ); } // Verify file is a real image before downstream uses it, not just extension match. if ( false === @getimagesize( $real_wm_path ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged return new WP_Error( 'invalid_watermark_image', __( 'Watermark file is not a valid image.', 'nggallery' ), [ 'status' => 400 ] ); } if ( $real_abspath && 0 === strpos( trailingslashit( $real_wm_path ), trailingslashit( $real_abspath ) ) ) { // Under ABSPATH: convert to web-root-relative form expected by downstream renderer. $final_wm_path = ltrim( str_replace( $real_abspath, '/', $real_wm_path ), '/' ); } else { // Under uploads (outside ABSPATH): copy into previews dir so downstream code can serve from a known location. $preview_dir = trailingslashit( $upload_dir['basedir'] ) . 'ngg-watermark-previews/'; if ( ! is_dir( $preview_dir ) ) { wp_mkdir_p( $preview_dir ); } $tmp_name = 'wm_' . uniqid( '', true ) . '.' . $abs_ext; // Use validated extension, not attacker-controlled. $tmp_path = $preview_dir . $tmp_name; // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.copy_copy if ( ! @copy( $real_wm_path, $tmp_path ) ) { // Source already constrained to uploads dir above. return new WP_Error( 'invalid_watermark_image', __( 'Failed to copy watermark image for preview.', 'nggallery' ), [ 'status' => 500 ] ); } // Use absolute path directly — downstream is_file() check handles it; str_replace(ABSPATH) breaks when uploads is outside ABSPATH. $final_wm_path = $tmp_path; $downloaded_tmp = true; $tmp_download_path = $tmp_path; } } } // Prepare size parameters for watermarked preview $size_params = [ 'width' => 250, 'height' => 250, 'quality' => 100, 'watermark' => 1, 'wmType' => sanitize_text_field( $watermark_options['wmType'] ?? 'text' ), 'wmText' => sanitize_text_field( $watermark_options['wmText'] ?? 'Preview' ), 'wmPos' => sanitize_text_field( $watermark_options['wmPos'] ?? 'midCenter' ), 'wmXpos' => intval( $watermark_options['wmXpos'] ?? 15 ), 'wmYpos' => intval( $watermark_options['wmYpos'] ?? 5 ), 'wmPath' => sanitize_text_field( $final_wm_path ), 'wmFont' => sanitize_text_field( $watermark_options['wmFont'] ?? 'arial.ttf' ), 'wmSize' => intval( $watermark_options['wmSize'] ?? 30 ), 'wmColor' => sanitize_text_field( $watermark_options['wmColor'] ?? 'ffffff' ), 'wmOpaque' => intval( $watermark_options['wmOpaque'] ?? 33 ), ]; // Generate a unique size name for this watermark preview $imagegen = DynamicThumbnailsManager::get_instance(); $size_name = $imagegen->get_size_name( $size_params ); // Generate the watermarked image using NextGen's system $result = $storage->generate_image_size( $original_image, $size_name, $size_params ); if ( $result ) { // Get the URL for the generated watermarked image $preview_url = $storage->get_image_url( $original_image, $size_name ); if ( $preview_url ) { // Add timestamp to prevent caching $preview_url_with_timestamp = $preview_url . '?' . time(); // Cleanup temporary download, if any if ( ! empty( $downloaded_tmp ) && $tmp_download_path && file_exists( $tmp_download_path ) ) { @unlink( $tmp_download_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink, WordPress.PHP.NoSilencedErrors.Discouraged } return new WP_REST_Response( [ 'success' => true, 'thumbnail_url' => $preview_url_with_timestamp, ], 200 ); } } // If we get here, generation failed return new WP_Error( 'generation_failed', __( 'Could not generate watermarked preview. Please check that watermark settings are valid.', 'nggallery' ), [ 'status' => 500 ] ); } catch ( \Exception $e ) { // Cleanup on error as well if ( ! empty( $downloaded_tmp ) && $tmp_download_path && file_exists( $tmp_download_path ) ) { @unlink( $tmp_download_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink, WordPress.PHP.NoSilencedErrors.Discouraged } return new WP_Error( 'preview_error', // translators: %s is the error message sprintf( __( 'An error occurred while generating the watermark preview: %s', 'nggallery' ), $e->getMessage() ), [ 'status' => 500 ] ); } } /** * Reset settings to defaults * * @param \WP_REST_Request $request The request object. * @return \WP_REST_Response|\WP_Error */ public static function reset_settings( WP_REST_Request $request ) { $confirm = $request->get_param( 'confirm' ); $settings_type = $request->get_param( 'settings_type' ) ?? 'all'; // Double-check the confirmation if ( ! $confirm ) { return new WP_Error( 'reset_not_confirmed', __( 'Settings reset must be confirmed by setting "confirm" to true.', 'nggallery' ), [ 'status' => 400 ] ); } try { global $wpdb; switch ( $settings_type ) { case 'all': // Complete reset - same as old admin reset_action self::perform_complete_reset( $wpdb ); $message = __( 'All settings have been reset to default values.', 'nggallery' ); break; case 'non_ecommerce': // Reset non-ecommerce settings - same as old admin reset_non_ecommerce_settings_action self::perform_non_ecommerce_reset( $wpdb ); $message = __( 'All non-ecommerce settings have been reset to default values.', 'nggallery' ); break; case 'ecommerce': // Reset only ecommerce settings - same as old admin reset_ecommerce_settings_action self::perform_ecommerce_reset(); $message = __( 'Ecommerce settings have been reset to default values.', 'nggallery' ); break; default: return new WP_Error( 'invalid_settings_type', __( 'Invalid settings type. Must be "all", "non_ecommerce", or "ecommerce".', 'nggallery' ), [ 'status' => 400 ] ); } return new WP_REST_Response( [ 'success' => true, 'message' => $message, 'settings_type' => $settings_type, ], 200 ); } catch ( \Exception $e ) { return new WP_Error( 'reset_failed', // translators: %s is the error message sprintf( __( 'Failed to reset settings: %s', 'nggallery' ), $e->getMessage() ), [ 'status' => 500 ] ); } } /** * Perform complete reset - same as old admin reset_action * * @param \wpdb $wpdb WordPress database object. * @throws \Exception If reset fails. */ private static function perform_complete_reset( $wpdb ) { // Flush the cache \Imagely\NGG\Util\Transient::flush(); // Uninstall the plugins in the correct order $settings = \Imagely\NGG\Settings\Settings::get_instance(); if ( defined( 'NGG_PRO_PLUGIN_VERSION' ) || defined( 'NEXTGEN_GALLERY_PRO_VERSION' ) ) { \Imagely\NGG\Util\Installer::uninstall( 'photocrati-nextgen-pro' ); } if ( defined( 'NGG_PLUS_PLUGIN_VERSION' ) ) { \Imagely\NGG\Util\Installer::uninstall( 'photocrati-nextgen-plus' ); } \Imagely\NGG\Util\Installer::uninstall( 'photocrati-nextgen' ); // Remove all ngg_options entry in wp_options $settings->reset(); $settings->destroy(); // Remove duplicate display types and lightboxes from database // (fixes issues from 1.9x to 2.0x upgrades) // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->posts} WHERE post_type = %s", 'display_type' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->posts} WHERE post_type = %s", 'lightbox_library' ) ); // Trigger reinstallation by making a request to plugins.php wp_remote_get( admin_url( 'plugins.php' ), [ 'timeout' => 180, 'blocking' => true, 'sslverify' => false, ] ); } /** * Perform non-ecommerce reset - same as old admin reset_non_ecommerce_settings_action * * @param \wpdb $wpdb WordPress database object. * @throws \Exception If reset fails. */ private static function perform_non_ecommerce_reset( $wpdb ) { // Flush the cache \Imagely\NGG\Util\Transient::flush(); // Get Pro installers and identify ecommerce ones $installers = apply_filters( 'ngg_pro_settings_reset_installers', [] ); $ecomm_installers = []; foreach ( $installers as $installer_classname ) { if ( class_exists( $installer_classname ) ) { $installer = new $installer_classname(); $actions = $installer->get_groups(); if ( in_array( 'ecommerce', $actions, true ) ) { $ecomm_installers[] = $installer; } } } // Load current ecommerce settings to preserve them foreach ( $ecomm_installers as $installer ) { $installer->load_current_settings(); } // Wipe out all settings (same as complete reset) if ( defined( 'NGG_PRO_PLUGIN_VERSION' ) || defined( 'NEXTGEN_GALLERY_PRO_VERSION' ) ) { \Imagely\NGG\Util\Installer::uninstall( 'photocrati-nextgen-pro' ); } if ( defined( 'NGG_PLUS_PLUGIN_VERSION' ) ) { \Imagely\NGG\Util\Installer::uninstall( 'photocrati-nextgen-plus' ); } \Imagely\NGG\Util\Installer::uninstall( 'photocrati-nextgen' ); $settings = \Imagely\NGG\Settings\Settings::get_instance(); $settings->reset(); $settings->destroy(); // Remove duplicate display types and lightboxes from database // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->posts} WHERE post_type = %s", 'display_type' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->posts} WHERE post_type = %s", 'lightbox_library' ) ); // Trigger reinstallation wp_remote_get( admin_url( 'plugins.php' ), [ 'timeout' => 180, 'blocking' => true, 'sslverify' => false, ] ); // Reload settings and restore ecommerce settings $settings = \Imagely\NGG\Settings\Settings::get_instance(); $settings->load(); foreach ( $ecomm_installers as $installer ) { $installer->set_current_settings(); } $settings->save(); } /** * Perform ecommerce-only reset - same as old admin reset_ecommerce_settings_action * * @throws \Exception If reset fails. */ private static function perform_ecommerce_reset() { // Flush the cache \Imagely\NGG\Util\Transient::flush(); // Get Pro installers and reset only ecommerce ones $installers = apply_filters( 'ngg_pro_settings_reset_installers', [] ); foreach ( $installers as $installer_classname ) { if ( class_exists( $installer_classname ) ) { $installer = new $installer_classname(); $actions = $installer->get_groups(); if ( in_array( 'ecommerce', $actions, true ) ) { $installer->reset(); } } } } /** * Clear all cache * * @return \WP_REST_Response|\WP_Error */ public static function clear_cache() { try { $storage_manager = StorageManager::get_instance(); $gallery_mapper = GalleryMapper::get_instance(); // Get all galleries $galleries = $gallery_mapper->find_all(); // Clear cache for each gallery foreach ( $galleries as $gallery ) { $storage_manager->flush_cache( $gallery ); } // Clear WordPress transients global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_ngg_%'" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_ngg_%'" ); // Clear NextGen specific transients \Imagely\NGG\Util\Transient::flush(); return new WP_REST_Response( [ 'success' => true, 'message' => __( 'Cache cleared successfully', 'nggallery' ), ], 200 ); } catch ( \Exception $e ) { return new WP_Error( 'cache_clear_failed', // translators: %s is the error message sprintf( __( 'Failed to clear cache: %s', 'nggallery' ), $e->getMessage() ), [ 'status' => 500 ] ); } } /** * Regenerate all thumbnails * * @return \WP_REST_Response|\WP_Error */ public static function regenerate_thumbnails() { try { $image_mapper = ImageMapper::get_instance(); $storage_manager = StorageManager::get_instance(); // Get all images $images = $image_mapper->find_all(); if ( empty( $images ) ) { return new WP_REST_Response( [ 'success' => true, 'message' => __( 'No images found to regenerate', 'nggallery' ), 'count' => 0, ], 200 ); } $success_count = 0; $error_count = 0; // Regenerate thumbnails for each image foreach ( $images as $image ) { try { $result = $storage_manager->generate_thumbnail( $image ); if ( $result ) { ++$success_count; } else { ++$error_count; } } catch ( \Exception $e ) { ++$error_count; continue; } } $total_count = count( $images ); if ( $error_count > 0 ) { return new WP_REST_Response( [ 'success' => true, 'message' => sprintf( // translators: %1$d is success count, %2$d is total count, %3$d is error count __( 'Thumbnail regeneration completed: %1$d of %2$d images processed successfully, %3$d errors', 'nggallery' ), $success_count, $total_count, $error_count ), 'total_count' => $total_count, 'success_count' => $success_count, 'error_count' => $error_count, ], 200 ); } return new WP_REST_Response( [ 'success' => true, 'message' => sprintf( // translators: %d is the number of thumbnails regenerated __( 'All %d thumbnails regenerated successfully', 'nggallery' ), $success_count ), 'total_count' => $total_count, 'success_count' => $success_count, 'error_count' => 0, ], 200 ); } catch ( \Exception $e ) { return new WP_Error( 'thumbnail_regeneration_failed', // translators: %s is the error message sprintf( __( 'Failed to regenerate thumbnails: %s', 'nggallery' ), $e->getMessage() ), [ 'status' => 500 ] ); } } /** * Check if gtag.js is actually loaded on the site frontend by fetching the home page HTML. * This is the only reliable way to confirm gtag is present — a plugin being active does not * guarantee it outputs gtag.js (it must also be configured with a Measurement ID). * * Pass ?force=true to skip the transient cache and immediately re-check (used by the * "Refresh Status" button in the admin UI). * * @param \WP_REST_Request $request Request object. * @return WP_REST_Response { detected: bool } */ public static function get_gtag_status( \WP_REST_Request $request ) { $transient_key = 'ngg_gtag_status_detected'; $force = (bool) $request->get_param( 'force' ); if ( $force ) { delete_transient( $transient_key ); } else { $cached = get_transient( $transient_key ); if ( false !== $cached ) { return new WP_REST_Response( [ 'detected' => (bool) $cached ], 200 ); } } $home_url = home_url( '/' ); $args = [ // 5s is safe here because cookies=>[] strips the admin session, preventing PHP-FPM self-deadlocks. 'timeout' => 5, 'cookies' => [], 'user-agent' => 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ), ]; // Allow self-signed certs for local/dev environments (Docker with HTTPS, etc.). $host = wp_parse_url( $home_url, PHP_URL_HOST ); if ( $host && ( 'localhost' === $host || '127.0.0.1' === $host || 'host.docker.internal' === $host || false !== strpos( (string) $host, '.localhost' ) ) ) { $args['sslverify'] = false; } $response = wp_remote_get( $home_url, $args ); if ( is_wp_error( $response ) ) { // Cache the failure briefly so repeated admin page loads don't each trigger a fresh timeout. set_transient( $transient_key, '0', 2 * MINUTE_IN_SECONDS ); return new WP_REST_Response( [ 'detected' => false ], 200 ); } $body = wp_remote_retrieve_body( $response ); $code = wp_remote_retrieve_response_code( $response ); if ( 200 !== (int) $code || empty( $body ) ) { // Same short-TTL cache for non-200 responses (e.g. maintenance mode, redirects). set_transient( $transient_key, '0', 2 * MINUTE_IN_SECONDS ); return new WP_REST_Response( [ 'detected' => false ], 200 ); } // Match any of the patterns that confirm gtag.js is actively loaded and configured. $detected = ( false !== strpos( $body, 'googletagmanager.com/gtag/js' ) || // gtag.js script src false !== strpos( $body, 'gtag("config"' ) || // double-quote variant false !== strpos( $body, "gtag('config'" ) || // single-quote variant false !== strpos( $body, 'gtag("config"' ) || // escaped double-quote false !== strpos( $body, 'gtag/js?id=G-' ) // GA4 Measurement ID in src ); // Cache for 15 minutes — gtag.js installation status changes infrequently. set_transient( $transient_key, $detected ? '1' : '0', 15 * MINUTE_IN_SECONDS ); return new WP_REST_Response( [ 'detected' => $detected ], 200 ); } /** * Get system information * * @return WP_REST_Response System information */ public static function get_system_info() { // Use the existing method from Admin\App class if ( class_exists( '\Imagely\NGG\Admin\App' ) ) { $system_info = \Imagely\NGG\Admin\App::get_system_info(); return new WP_REST_Response( $system_info, 200 ); } return new WP_Error( 'system_info_unavailable', __( 'System information is not available', 'nggallery' ), [ 'status' => 500 ] ); } }
dvadf
dvadf
| ver. 1.4 |
Github
|
.
| PHP 7.3.33 | Generation time: 0 |
proxy
|
phpinfo
|
Settings