HEX
Server: nginx/1.18.0
System: Linux mail.dakarash.co.id 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64
User: www-data (33)
PHP: 8.1.2-1ubuntu2.23
Disabled: NONE
Upload Files
File: /home/dakarash.co.id/public_html/wp-content/plugins/visual-portfolio/classes/class-rest.php
<?php
/**
 * Rest API functions
 *
 * @package visual-portfolio
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class Visual_Portfolio_Rest
 */
class Visual_Portfolio_Rest extends WP_REST_Controller {
	/**
	 * Namespace.
	 *
	 * @var string
	 */
	protected $namespace = 'visual-portfolio/v';

	/**
	 * Version.
	 *
	 * @var string
	 */
	protected $version = '1';

	/**
	 * Visual_Portfolio_Rest constructor.
	 */
	public function __construct() {
		add_action( 'rest_api_init', array( $this, 'register_routes' ) );
	}

	/**
	 * Register rest routes.
	 */
	public function register_routes() {
		$namespace = $this->namespace . $this->version;

		// Get layouts list.
		register_rest_route(
			$namespace,
			'/get_layouts/',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_layouts' ),
				'permission_callback' => array( $this, 'get_layouts_permission' ),
			)
		);

		// Update layout data.
		register_rest_route(
			$namespace,
			'/update_layout/',
			array(
				'methods'             => WP_REST_Server::EDITABLE,
				'callback'            => array( $this, 'update_layout' ),
				'permission_callback' => array( $this, 'update_layout_permission' ),
			)
		);

		// Update gallery items count notice state.
		register_rest_route(
			$namespace,
			'/update_gallery_items_count_notice_state/',
			array(
				'methods'             => WP_REST_Server::EDITABLE,
				'callback'            => array( $this, 'update_gallery_items_count_notice_state' ),
				'permission_callback' => array( $this, 'update_gallery_items_count_notice_state_permission' ),
			)
		);

		// Get filter items.
		register_rest_route(
			$namespace,
			'/get_filter_items/',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_filter_items' ),
				'permission_callback' => array( $this, 'get_filter_items_permission' ),
				'args'                => array(
					'content_source' => array(
						'required'          => true,
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					),
					'posts_source' => array(
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					),
					'posts_taxonomies' => array(
						'type'              => 'array',
						'sanitize_callback' => function( $taxonomies ) {
							return array_map( 'absint', (array) $taxonomies );
						},
					),
					'images' => array(
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					),
				),
			)
		);

		register_rest_route(
			$namespace,
			'/get-max-pages/',
			array(
				'methods'             => WP_REST_Server::READABLE . ', ' . WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'get_max_pages' ),
				'permission_callback' => array( $this, 'get_max_pages_permission' ),
			)
		);
	}

	/**
	 * Check permission for getting max pages.
	 *
	 * @return bool Whether the current user has permission.
	 */
	public function get_max_pages_permission() {
		return current_user_can( 'edit_posts' );
	}

	/**
	 * Calculate max pages based on query attributes.
	 *
	 * @param array $params Full query data.
	 * @return int $max_pages Response max pages data.
	 */
	public function calculate_max_pages( $params ) {
		// Convert modern params to legacy format.
		$params = Visual_Portfolio_Convert_Attributes::modern_to_legacy( $params );

		$params = Visual_Portfolio_Security::validate_calculate_max_pages_params( $params );

		$content_source = $params['content_source'] ?? '';
		$items_count    = (int) ( $params['items_count'] ?? 0 );

		// Add filter from GET if not in params.
		if ( empty( $params['vp_filter'] ) && ! empty( $_GET['vp_filter'] ) ) {
			$params['vp_filter'] = sanitize_text_field( wp_unslash( $_GET['vp_filter'] ) );
		}

		// Decode JSON images if needed.
		if ( 'images' === $content_source &&
			is_string( $params['images'] ?? '' ) &&
			0 === strpos( $params['images'], '[' ) ) {
			$decoded = json_decode( $params['images'], true );
			if ( JSON_ERROR_NONE === json_last_error() ) {
				$params['images'] = $decoded;
			}
		}

		// Get query options and calculate max pages.
		$options = array_merge(
			array(
				'content_source' => $content_source,
				'items_count'    => $items_count,
			),
			$params
		);

		$query_opts = Visual_Portfolio_Get::get_query_params( $options, false );

		if ( isset( $query_opts['max_num_pages'] ) ) {
			return max( 1, $query_opts['max_num_pages'] );
		}

		switch ( $content_source ) {
			case 'post-based':
				$query     = new WP_Query( $query_opts );
				$max_pages = $query->max_num_pages ? $query->max_num_pages : ceil( $query->found_posts / $items_count );
				return max( 1, $max_pages );

			case 'images':
			case 'social-stream':
				$images_count = count( $query_opts['images'] ?? array() );
				return max( 1, ceil( $images_count / $items_count ) );

			default:
				return 1;
		}
	}

	/**
	 * Get max pages based on query attributes.
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return WP_REST_Response Response object with max pages data.
	 */
	public function get_max_pages( $request ) {
		// Get parameters from either query params or request body.
		$params = $request->get_params();

		// If this is a POST request, also check for JSON body data.
		if ( 'POST' === $request->get_method() ) {
			$json_params = $request->get_json_params();
			if ( ! empty( $json_params ) ) {
				$params = array_merge( $params, $json_params );
			}
		}

		$max_pages = $this->calculate_max_pages( $params );

		// Return response.
		return rest_ensure_response(
			array(
				'max_pages' => $max_pages,
			)
		);
	}

	/**
	 * Get filter items.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_filter_items( $request ) {
		$content_source = $request->get_param( 'content_source' );
		$post_id        = $request->get_param( 'post_id' );

		if ( ! $content_source ) {
			return $this->error(
				'missing_params',
				esc_html__( 'Required parameters are missing.', 'visual-portfolio' )
			);
		}

		$options = array(
			'content_source' => $content_source,
		);

		// Add additional parameters based on content source.
		if ( 'posts' === $content_source ) {
			$options['posts_source']              = $request->get_param( 'posts_source' );
			$options['posts_taxonomies']          = $request->get_param( 'posts_taxonomies' );
			$options['posts_taxonomies_relation'] = $request->get_param( 'posts_taxonomies_relation' );
			$options['posts_order_by']            = $request->get_param( 'posts_order_by' );
			$options['posts_order_direction']     = $request->get_param( 'posts_order_direction' );
		} elseif ( 'images' === $content_source ) {
			$images                                = $request->get_param( 'images' );
			$options['images']                     = is_string( $images ) ? json_decode( $images, true ) : $images;
			$options['images_titles_source']       = $request->get_param( 'images_titles_source' );
			$options['images_descriptions_source'] = $request->get_param( 'images_descriptions_source' );
			$options['images_order_by']            = $request->get_param( 'images_order_by' );
			$options['images_order_direction']     = $request->get_param( 'images_order_direction' );
			$options['items_count']                = $request->get_param( 'items_count' );
		}

		// Get query parameters.
		$query_opts = Visual_Portfolio_Get::get_query_params( $options, true );

		// Get active filter item.
		$active_item = Visual_Portfolio_Get::get_filter_active_item( $query_opts );

		// Get filter items.
		if ( 'images' === $content_source || 'social-stream' === $content_source ) {
			$term_items = Visual_Portfolio_Get::get_images_terms( $query_opts, $active_item );
		} else {
			$portfolio_query = new WP_Query( $query_opts );
			$term_items      = Visual_Portfolio_Get::get_posts_terms( $portfolio_query, $active_item );
		}

		// Helper function to generate filter URLs.
		$get_filter_url = function( $filter = '', $taxonomy = '' ) use ( $post_id, $content_source ) {
			// Get the permalink of the current post.
			$url = get_permalink( $post_id );

			// If no valid URL found, fallback to home URL.
			if ( ! $url ) {
				$url = home_url();
			}

			// Add new filter parameter if it exists.
			if ( $filter && '*' !== $filter ) {
				if ( 'images' === $content_source || 'social-stream' === $content_source ) {
					$url = add_query_arg( 'vp_filter', rawurlencode( $filter ), $url );
				}
				if ( 'posts' === $content_source ) {
					$post_filter = rawurlencode( $taxonomy . ':' ) . $filter;
					$url         = add_query_arg( 'vp_filter', $post_filter, $url );
				}
			}

			return $url;
		};

		// Prepare response.
		$response = array();

		// Add 'All' item.
		$response[] = array(
			'filter'      => '*',
			'label'       => esc_html__( 'All', 'visual-portfolio' ),
			'description' => '',
			'count'       => false,
			'active'      => ! $active_item,
			'url'         => $get_filter_url(),
			'taxonomy'    => '',
			'id'          => 0,
			'parent'      => 0,
		);

		// Add term items.
		if ( ! empty( $term_items['terms'] ) ) {
			foreach ( $term_items['terms'] as $term ) {
				$response[] = array(
					'filter'      => $term['filter'],
					'label'       => $term['label'],
					'description' => $term['description'],
					'count'       => $term['count'],
					'active'      => $term['active'],
					'url'         => $get_filter_url( $term['filter'], $term['taxonomy'] ),
					'taxonomy'    => $term['taxonomy'] ?? '',
					'id'          => $term['id'],
					'parent'      => $term['parent'],
				);
			}
		}

		return $this->success( $response );
	}

	/**
	 * Get filter items permission.
	 *
	 * @return mixed
	 */
	public function get_filter_items_permission() {
		if ( current_user_can( 'edit_posts' ) ) {
			return true;
		}

		foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
			if ( current_user_can( $post_type->cap->edit_posts ) ) {
				return true;
			}
		}

		return $this->error( 'not_allowed', esc_html__( 'Sorry, you are not allowed to get filter items.', 'visual-portfolio' ), true );
	}

	/**
	 * Get layout data permission.
	 *
	 * @return mixed
	 */
	public function get_layouts_permission() {
		if ( current_user_can( 'edit_posts' ) ) {
			return true;
		}

		foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
			if ( current_user_can( $post_type->cap->edit_posts ) ) {
				return true;
			}
		}

		return $this->error( 'not_allowed', esc_html__( 'Sorry, you are not allowed to get list of saved layouts.', 'visual-portfolio' ), true );
	}

	/**
	 * Get layout data.
	 *
	 * @return mixed
	 */
	public function get_layouts() {
		// get all visual-portfolio post types.
		// Don't use WP_Query on the admin side https://core.trac.wordpress.org/ticket/18408 .
		$layouts  = array();
		$vp_query = get_posts(
			array(
				'post_type'              => 'vp_lists',
				'posts_per_page'         => -1,
				'paged'                  => -1,
				'update_post_meta_cache' => false,
				'update_post_term_cache' => false,
			)
		);
		foreach ( $vp_query as $post ) {
			$layouts[] = array(
				'id'       => $post->ID,
				'title'    => $post->post_title,
				'edit_url' => admin_url( 'post.php?post=' . $post->ID ) . '&action=edit',
			);
		}

		if ( ! empty( $layouts ) ) {
			return $this->success( $layouts );
		} else {
			return $this->error( 'no_layouts_found', __( 'Layouts not found.', 'visual-portfolio' ) );
		}
	}

	/**
	 * Update layout data permission.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return true|WP_Error
	 */
	public function update_layout_permission( $request ) {
		$post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0;

		if ( ! $post_id ) {
			return $this->error( 'post_id_required', esc_html__( 'Post ID is required for this request.', 'visual-portfolio' ), true );
		}

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return $this->error( 'not_allowed', esc_html__( 'Sorry, you are not allowed to edit saved layouts data.', 'visual-portfolio' ), true );
		}

		return true;
	}

	/**
	 * Update layout data.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function update_layout( $request ) {
		$post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0;
		$data    = isset( $request['data'] ) ? $request['data'] : false;

		if ( $post_id && $data ) {
			$meta = array_keys( Visual_Portfolio_Get::get_options( array( 'id' => $post_id ) ) );

			foreach ( $meta as $name ) {
				// Save with prefix.
				$prefixed_name = 'vp_' . $name;

				if ( isset( $data[ $prefixed_name ] ) ) {
					if (
						'vp_images' === $prefixed_name ||
						'vp_layout_elements' === $prefixed_name ||
						'vp_custom_css' === $prefixed_name
					) {
						$result = $data[ $prefixed_name ];
					} elseif ( is_array( $data[ $prefixed_name ] ) ) {
						$result = array_map( 'sanitize_text_field', wp_unslash( $data[ $prefixed_name ] ) );
					} else {
						$result = sanitize_text_field( wp_unslash( $data[ $prefixed_name ] ) );
					}

					update_post_meta( $post_id, $prefixed_name, $result );
				}
			}
		}

		return $this->success( true );
	}

	/**
	 * Update gallery items count notice state permission.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return true|WP_Error
	 */
	public function update_gallery_items_count_notice_state_permission( $request ) {
		$post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0;

		if ( ! $post_id || ! current_user_can( 'manage_options' ) ) {
			return $this->error( 'user_dont_have_permission', esc_html__( 'User don\'t have permissions to change options.', 'visual-portfolio' ), true );
		}

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return $this->error( 'user_dont_have_permission', esc_html__( 'User don\'t have permissions to change options.', 'visual-portfolio' ), true );
		}

		return true;
	}

	/**
	 * Update layout data.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function update_gallery_items_count_notice_state( $request ) {
		update_option( 'visual_portfolio_items_count_notice_state', $request->get_param( 'notice_state' ) );

		return $this->success( true );
	}

	/**
	 * Success rest.
	 *
	 * @param mixed $response response data.
	 * @return mixed
	 */
	public function success( $response ) {
		return new WP_REST_Response(
			array(
				'success'  => true,
				'response' => $response,
			),
			200
		);
	}

	/**
	 * Error rest.
	 *
	 * @param mixed   $code       error code.
	 * @param mixed   $response   response data.
	 * @param boolean $true_error use true error response to stop the code processing.
	 * @return mixed
	 */
	public function error( $code, $response, $true_error = false ) {
		if ( $true_error ) {
			return new WP_Error( $code, $response, array( 'status' => 401 ) );
		}

		return new WP_REST_Response(
			array(
				'error'      => true,
				'success'    => false,
				'error_code' => $code,
				'response'   => $response,
			),
			401
		);
	}
}

new Visual_Portfolio_Rest();