diff --git a/inc/api/class-opalestate-api-auth.php b/inc/api/class-opalestate-api-auth.php deleted file mode 100644 index 58bbc647..00000000 --- a/inc/api/class-opalestate-api-auth.php +++ /dev/null @@ -1,110 +0,0 @@ - - * @license https://opensource.org/licenses/gpl-license GNU Public License - * @since 1.0 - */ -/** - * Api_Auth class for authorizing to access api resources - * - * @since 1.0.0 - * @package Opal_Job - * @subpackage Opal_Job/API - */ -class Opalestate_Api_Auth extends Opalestate_Base_API { - - /** - * Register user endpoints. - * - * to check post method need authorization to continue completing action - * - * @since 1.0 - * - * @return avoid - */ - public function register_routes() { - // check all request must to have public key and token - register_rest_route( $this->namespace, '/job/list', array( - 'methods' => 'GET', - 'permission_callback' => array( $this, 'validate_request' ), - ), 9 ); - - ////////////////// Check User Authorizcation must to have account logined - // check authorcation for all delete in route - register_rest_route($this->namespace, '/(?P[\S]+)/delete', array( - 'methods' => 'GET', - 'callback' => array( $this, 'check' ), - )); - // check authorcation for all delete in route - register_rest_route($this->namespace, '/(?P[\S]+)/edit', array( - 'methods' => 'GET', - 'callback' => array( $this, 'check' ), - )); - // check authorcation for all delete in route - register_rest_route($this->namespace, '/(?P[\S]+)/create', array( - 'methods' => 'GET', - 'callback' => array( $this, 'check' ), - )); - } - - - /** - * Check authorization - * - * check user request having passing username and password, then check them be valid or not. - * - * @param WP_REST_Request $request - * @since 1.0 - * - * @return WP_REST_Response is json data - */ - public function check( WP_REST_Request $request ) { - $response = array(); - - $default = array( - 'username' => '', - 'password' => '' - ); - - $parameters = $request->get_params(); - $parameters = array_merge( $default, $parameters ); - - $username = sanitize_text_field( $parameters['username'] ); - $password = sanitize_text_field( $parameters['password'] ); - - // Error Handling. - $error = new WP_Error(); - if ( empty( $username ) ) { - $error->add( - 400, - __( "Username field is required", 'rest-api-endpoints' ), - array( 'status' => 400 ) - ); - return $error; - } - if ( empty( $password ) ) { - $error->add( - 400, - __( "Password field is required", 'rest-api-endpoints' ), - array( 'status' => 400 ) - ); - return $error; - } - $user = wp_authenticate( $username, $password ); - - // If user found - if ( ! is_wp_error( $user ) ) { - $response['status'] = 200; - $response['user'] = $user; - } else { - // If user not found - $error->add( 406, esc_html_e( 'User not found. Check credentials', 'rest-api-endpoints' ) ); - return $error; - } - return new WP_REST_Response( $response ); - } -} diff --git a/inc/api/class-opalestate-api-remove.php b/inc/api/class-opalestate-api-remove.php deleted file mode 100755 index 805ceec5..00000000 --- a/inc/api/class-opalestate-api-remove.php +++ /dev/null @@ -1,1367 +0,0 @@ -versions = array( - 'v1' => 'Opalestate_API', - ); - - - - add_action( 'init', array( $this, 'add_endpoint' ) ); - add_action( 'wp', array( $this, 'process_query' ), - 1 ); - add_filter( 'query_vars', array( $this, 'query_vars' ) ); - add_action( 'show_user_profile', array( $this, 'user_key_field' ) ); - add_action( 'edit_user_profile', array( $this, 'user_key_field' ) ); - add_action( 'personal_options_update', array( $this, 'update_key' ) ); - add_action( 'edit_user_profile_update', array( $this, 'update_key' ) ); - add_action( 'opalestate_process_api_key', array( $this, 'process_api_key' ) ); - - // Setup a backwards compatibility check for user API Keys - add_filter( 'get_user_metadata', array( $this, 'api_key_backwards_compat' ), 10, 4 ); - - // Determine if JSON_PRETTY_PRINT is available - $this->pretty_print = defined( 'JSON_PRETTY_PRINT' ) ? JSON_PRETTY_PRINT : null; - - // Allow API request logging to be turned off - $this->log_requests = apply_filters( 'opalestate_api_log_requests', $this->log_requests ); - - - } - - /** - * Registers a new rewrite endpoint for accessing the API - * - * @access public - * - * @param array $rewrite_rules WordPress Rewrite Rules - * - * @since 1.1 - */ - public function add_endpoint( $rewrite_rules ) { - add_rewrite_endpoint( 'opalestate-api', EP_ALL ); - } - - /** - * Registers query vars for API access - * - * @access public - * @since 1.1 - * - * @param array $vars Query vars - * - * @return string[] $vars New query vars - */ - public function query_vars( $vars ) { - - $vars[] = 'token'; - $vars[] = 'key'; - $vars[] = 'query'; - $vars[] = 'type'; - $vars[] = 'property'; - $vars[] = 'number'; - $vars[] = 'date'; - $vars[] = 'startdate'; - $vars[] = 'enddate'; - $vars[] = 'donor'; - $vars[] = 'propertyat'; - $vars[] = 'id'; - $vars[] = 'purchasekey'; - $vars[] = 'email'; - - return $vars; - } - - /** - * Retrieve the API versions - * - * @access public - * @since 1.1 - * @return array - */ - public function get_versions() { - return $this->versions; - } - - /** - * Retrieve the API version that was queried - * - * @access public - * @since 1.1 - * @return string - */ - public function get_queried_version() { - return $this->queried_version; - } - - /** - * Retrieves the default version of the API to use - * - * @access public - * @since 1.1 - * @return string - */ - public function get_default_version() { - - $version = get_option( 'opalestate_default_api_version' ); - - if ( defined( 'OPALESTATE_API_VERSION' ) ) { - $version = OPALESTATE_API_VERSION; - } elseif ( ! $version ) { - $version = 'v1'; - } - - return $version; - } - - /** - * Sets the version of the API that was queried. - * - * Falls back to the default version if no version is specified - * - * @access private - * @since 1.1 - */ - private function set_queried_version() { - - global $wp_query; - - $version = $wp_query->query_vars['opalestate-api']; - - if ( strpos( $version, '/' ) ) { - - $version = explode( '/', $version ); - $version = strtolower( $version[0] ); - - $wp_query->query_vars['opalestate-api'] = str_replace( $version . '/', '', $wp_query->query_vars['opalestate-api'] ); - - if ( array_key_exists( $version, $this->versions ) ) { - - $this->queried_version = $version; - - } else { - - $this->is_valid_request = false; - $this->invalid_version(); - } - - } else { - - $this->queried_version = $this->get_default_version(); - - } - - } - - /** - * Validate the API request - * - * Checks for the user's public key and token against the secret key - * - * @access private - * @global object $wp_query WordPress Query - * @uses Opalestate_API::get_user() - * @uses Opalestate_API::invalid_key() - * @uses Opalestate_API::invalid_auth() - * @since 1.1 - * @return void - */ - private function validate_request() { - global $wp_query; - - $this->override = false; - - // Make sure we have both user and api key - if ( ! empty( $wp_query->query_vars['opalestate-api'] ) && ( $wp_query->query_vars['opalestate-api'] != 'properties' || ! empty( $wp_query->query_vars['token'] ) ) ) { - - if ( empty( $wp_query->query_vars['token'] ) || empty( $wp_query->query_vars['key'] ) ) { - $this->missing_auth(); - } - - // Retrieve the user by public API key and ensure they exist - if ( ! ( $user = $this->get_user( $wp_query->query_vars['key'] ) ) ) { - - $this->invalid_key(); - - } else { - - $token = urldecode( $wp_query->query_vars['token'] ); - $secret = $this->get_user_secret_key( $user ); - $public = urldecode( $wp_query->query_vars['key'] ); - - if ( hash_equals( md5( $secret . $public ), $token ) ) { - $this->is_valid_request = true; - } else { - $this->invalid_auth(); - } - } - } elseif ( ! empty( $wp_query->query_vars['opalestate-api'] ) && $wp_query->query_vars['opalestate-api'] == 'properties' ) { - $this->is_valid_request = true; - $wp_query->set( 'key', 'public' ); - } - } - - /** - * Retrieve the user ID based on the public key provided - * - * @access public - * @since 1.1 - * @global WPDB $wpdb Used to query the database using the WordPress - * Database API - * - * @param string $key Public Key - * - * @return bool if user ID is found, false otherwise - */ - public function get_user( $key = '' ) { - global $wpdb, $wp_query; - - if ( empty( $key ) ) { - $key = urldecode( $wp_query->query_vars['key'] ); - } - - if ( empty( $key ) ) { - return false; - } - - $user = get_transient( md5( 'opalestate_api_user_' . $key ) ); - - if ( false === $user ) { - $user = $wpdb->get_var( $wpdb->prepare( "SELECT user_id FROM $wpdb->usermeta WHERE meta_key = %s LIMIT 1", $key ) ); - set_transient( md5( 'opalestate_api_user_' . $key ), $user, DAY_IN_SECONDS ); - } - - if ( $user != null ) { - $this->user_id = $user; - - return $user; - } - - return false; - } - - public function get_user_public_key( $user_id = 0 ) { - global $wpdb; - - if ( empty( $user_id ) ) { - return ''; - } - - $cache_key = md5( 'opalestate_api_user_public_key' . $user_id ); - $user_public_key = get_transient( $cache_key ); - - if ( empty( $user_public_key ) ) { - $user_public_key = $wpdb->get_var( $wpdb->prepare( "SELECT meta_key FROM $wpdb->usermeta WHERE meta_value = 'opalestate_user_public_key' AND user_id = %d", $user_id ) ); - set_transient( $cache_key, $user_public_key, HOUR_IN_SECONDS ); - } - - return $user_public_key; - } - - public function get_user_secret_key( $user_id = 0 ) { - global $wpdb; - - if ( empty( $user_id ) ) { - return ''; - } - - $cache_key = md5( 'opalestate_api_user_secret_key' . $user_id ); - $user_secret_key = get_transient( $cache_key ); - - if ( empty( $user_secret_key ) ) { - $user_secret_key = $wpdb->get_var( $wpdb->prepare( "SELECT meta_key FROM $wpdb->usermeta WHERE meta_value = 'opalestate_user_secret_key' AND user_id = %d", $user_id ) ); - set_transient( $cache_key, $user_secret_key, HOUR_IN_SECONDS ); - } - - return $user_secret_key; - } - - /** - * Displays a missing authentication error if all the parameters aren't - * provided - * - * @access private - * @uses Opalestate_API::output() - * @since 1.1 - */ - private function missing_auth() { - $error = array(); - $error['error'] = esc_html__( 'You must specify both a token and API key!', 'opalestate-pro' ); - - $this->data = $error; - $this->output( 401 ); - } - - /** - * Displays an authentication failed error if the user failed to provide valid - * credentials - * - * @access private - * @since 1.1 - * @uses Opalestate_API::output() - * @return void - */ - private function invalid_auth() { - $error = array(); - $error['error'] = esc_html__( 'Your request could not be authenticated!', 'opalestate-pro' ); - - $this->data = $error; - $this->output( 403 ); - } - - /** - * Displays an invalid API key error if the API key provided couldn't be - * validated - * - * @access private - * @since 1.1 - * @uses Opalestate_API::output() - * @return void - */ - private function invalid_key() { - $error = array(); - $error['error'] = esc_html__( 'Invalid API key!', 'opalestate-pro' ); - - $this->data = $error; - $this->output( 403 ); - } - - /** - * Displays an invalid version error if the version number passed isn't valid - * - * @access private - * @since 1.1 - * @uses Opalestate_API::output() - * @return void - */ - private function invalid_version() { - $error = array(); - $error['error'] = esc_html__( 'Invalid API version!', 'opalestate-pro' ); - - $this->data = $error; - $this->output( 404 ); - } - - /** - * Listens for the API and then processes the API requests - * - * @access public - * @global $wp_query - * @since 1.1 - * @return void - */ - public function process_query() { - - global $wp_query; - - // Start logging how long the request takes for logging - $before = microtime( true ); - - // Check for opalestate-api var. Get out if not present - if ( empty( $wp_query->query_vars['opalestate-api'] ) ) { - return; - } - - // Determine which version was queried - $this->set_queried_version(); - - // Determine the kind of query - $this->set_query_mode(); - - // Check for a valid user and set errors if necessary - $this->validate_request(); - - // Only proceed if no errors have been noted - if ( ! $this->is_valid_request ) { - return; - } - - if ( ! defined( 'OPALESTATE_DOING_API' ) ) { - define( 'OPALESTATE_DOING_API', true ); - } - - $data = array(); - $this->routes = new $this->versions[$this->get_queried_version()]; - $this->routes->validate_request(); - - switch ( $this->endpoint ) : - - - case 'properties' : - - $property = isset( $wp_query->query_vars['property'] ) ? $wp_query->query_vars['property'] : null; - - $data = $this->routes->get_properties( $property ); - - break; - case 'featured' : - - $property = isset( $wp_query->query_vars['property'] ) ? $wp_query->query_vars['property'] : null; - - $data = $this->routes->get_featured_properties( $property ); - - break; - - case 'agents' : - - $agent = isset( $wp_query->query_vars['agent'] ) ? $wp_query->query_vars['agent'] : null; - - $data = $this->routes->get_agents( $agent ); - - break; - - - endswitch; - - // Allow extensions to setup their own return data - $this->data = apply_filters( 'opalestate_api_output_data', $data, $this->endpoint, $this ); - - $after = microtime( true ); - $request_time = ( $after - $before ); - $this->data['request_speed'] = $request_time; - - // Log this API request, if enabled. We log it here because we have access to errors. - $this->log_request( $this->data ); - - // Send out data to the output function - $this->output(); - } - - /** - * Returns the API endpoint requested - * - * @access public - * @since 1.1 - * @return string $query Query mode - */ - public function get_query_mode() { - - return $this->endpoint; - } - - /** - * Determines the kind of query requested and also ensure it is a valid query - * - * @access public - * @since 1.1 - * @global $wp_query - */ - public function set_query_mode() { - - global $wp_query; - - // Whitelist our query options - $accepted = apply_filters( 'opalestate_api_valid_query_modes', array( - 'agents', - 'properties', - 'featured' - ) ); - - $query = isset( $wp_query->query_vars['opalestate-api'] ) ? $wp_query->query_vars['opalestate-api'] : null; - $query = str_replace( $this->queried_version . '/', '', $query ); - - $error = array(); - - // Make sure our query is valid - if ( ! in_array( $query, $accepted ) ) { - $error['error'] = esc_html__( 'Invalid query!', 'opalestate-pro' ); - - $this->data = $error; - // 400 is Bad Request - $this->output( 400 ); - } - - $this->endpoint = $query; - } - - /** - * Get page number - * - * @access public - * @since 1.1 - * @global $wp_query - * @return int $wp_query->query_vars['page'] if page number returned (default: 1) - */ - public function get_paged() { - global $wp_query; - - return isset( $wp_query->query_vars['page'] ) ? $wp_query->query_vars['page'] : 1; - } - - - /** - * Number of results to display per page - * - * @access public - * @since 1.1 - * @global $wp_query - * @return int $per_page Results to display per page (default: 10) - */ - public function per_page() { - global $wp_query; - - $per_page = isset( $wp_query->query_vars['number'] ) ? $wp_query->query_vars['number'] : 10; - - return apply_filters( 'opalestate_api_results_per_page', $per_page ); - } - - - /** - * - * - */ - public function get_agents( $agent = null ) { - - $agents = array(); - $error = array(); - - if ( $agent == null ) { - $agents['agents'] = array(); - - $property_list = get_posts( array( - 'post_type' => 'opalestate_agent', - 'posts_per_page' => $this->per_page(), - 'suppress_filters' => true, - 'paged' => $this->get_paged() - ) ); - - if ( $property_list ) { - $i = 0; - foreach ( $property_list as $agent_info ) { - $agents['agents'][ $i ] = $this->get_agent_data( $agent_info ); - $i ++; - } - } - } else { - if ( get_post_type( $agent ) == 'opalestate_property' ) { - $agent_info = get_post( $agent ); - - $agents['agents'][0] = $this->get_agent_data( $agent_info ); - - } else { - $error['error'] = sprintf( - /* translators: %s: property */ - esc_html__( 'Form %s not found!', 'opalestate-pro' ), - $agent - ); - - return $error; - } - } - - return $agents; - } - - /** - * - * - */ - public function get_agent_data( $agent_info ){ - $ouput = array(); - - $ouput['info']['id'] = $agent_info->ID; - $ouput['info']['slug'] = $agent_info->post_name; - $ouput['info']['title'] = $agent_info->post_title; - $ouput['info']['create_date'] = $agent_info->post_date; - $ouput['info']['modified_date'] = $agent_info->post_modified; - $ouput['info']['status'] = $agent_info->post_status; - $ouput['info']['link'] = html_entity_decode( $agent_info->guid ); - $ouput['info']['content'] = $agent_info->post_content; - $ouput['info']['thumbnail'] = wp_get_attachment_url( get_post_thumbnail_id( $agent_info->ID ) ); - - - - $agent = new OpalEstate_Agent( $agent_info->ID ); - - $ouput['info']['featured'] = (int)$agent->is_featured(); - $ouput['info']['email'] = get_post_meta( $agent_info->ID, OPALESTATE_AGENT_PREFIX . 'email', true ); - $ouput['info']['address'] = get_post_meta( $agent_info->ID, OPALESTATE_AGENT_PREFIX . 'address', true ); - - $terms = wp_get_post_terms( $agent_info->ID, 'opalestate_agent_location' ); - $ouput['info']['location'] = $terms && !is_wp_error($terms) ? $terms : array(); - - $ouput['socials'] = $agent->get_socials(); - - $ouput['levels'] = wp_get_post_terms( $agent_info->ID, 'opalestate_agent_level' ); - - - return apply_filters( 'opalestate_api_agents', $ouput ); - } - - /** - * Process Get Products API Request - * - * @access public - * @since 1.1 - * - * @param int $property Opalestate Form ID - * - * @return array $customers Multidimensional array of the properties - */ - public function get_featured_properties( $property = null ) { - - $properties = array(); - $error = array(); - - if ( $property == null ) { - $properties['properties'] = array(); - - $property_list = get_posts( array( - 'post_type' => 'opalestate_property', - 'posts_per_page' => $this->per_page(), - 'suppress_filters' => true, - 'paged' => $this->get_paged(), - 'meta_key' => OPALESTATE_PROPERTY_PREFIX . 'featured', - 'meta_value' => 1, - 'meta_compare' => '=' - ) ); - - if ( $property_list ) { - $i = 0; - foreach ( $property_list as $property_info ) { - $properties['properties'][ $i ] = $this->get_property_data( $property_info ); - $i ++; - } - } - } else { - if ( get_post_type( $property ) == 'opalestate_property' ) { - $property_info = get_post( $property ); - - $properties['properties'][0] = $this->get_property_data( $property_info ); - - } else { - $error['error'] = sprintf( - /* translators: %s: property */ - esc_html__( 'Form %s not found!', 'opalestate-pro' ), - $property - ); - - return $error; - } - } - - return $properties; - } - - /** - * Process Get Products API Request - * - * @access public - * @since 1.1 - * - * @param int $property Opalestate Form ID - * - * @return array $customers Multidimensional array of the properties - */ - public function get_properties( $property = null ) { - - $properties = array(); - $error = array(); - - if ( $property == null ) { - $properties['properties'] = array(); - - $property_list = get_posts( array( - 'post_type' => 'opalestate_property', - 'posts_per_page' => $this->per_page(), - 'suppress_filters' => true, - 'paged' => $this->get_paged() - ) ); - - if ( $property_list ) { - $i = 0; - foreach ( $property_list as $property_info ) { - $properties['properties'][ $i ] = $this->get_property_data( $property_info ); - $i ++; - } - } - } else { - if ( get_post_type( $property ) == 'opalestate_property' ) { - $property_info = get_post( $property ); - - $properties['properties'][0] = $this->get_property_data( $property_info ); - - } else { - $error['error'] = sprintf( - /* translators: %s: property */ - esc_html__( 'Form %s not found!', 'opalestate-pro' ), - $property - ); - - return $error; - } - } - - return $properties; - } - - /** - * Opalestaten a opalestate_property post object, generate the data for the API output - * - * @since 1.1 - * - * @param object $property_info The Download Post Object - * - * @return array Array of post data to return back in the API - */ - private function get_property_data( $property_info ) { - - $property = array(); - - $property['info']['id'] = $property_info->ID; - $property['info']['slug'] = $property_info->post_name; - $property['info']['title'] = $property_info->post_title; - $property['info']['create_date'] = $property_info->post_date; - $property['info']['modified_date'] = $property_info->post_modified; - $property['info']['status'] = $property_info->post_status; - $property['info']['link'] = html_entity_decode( $property_info->guid ); - $property['info']['content'] = $property_info->post_content; - $property['info']['thumbnail'] = wp_get_attachment_url( get_post_thumbnail_id( $property_info->ID ) ); - - $data = opalesetate_property( $property_info->ID ); - $gallery = $data->get_gallery(); - $property['info']['gallery'] = isset($gallery[0]) && !empty($gallery[0]) ? $gallery[0]: array(); - $property['info']['price'] = opalestate_price_format( $data->get_price() ); - $property['info']['map'] = $data->get_map(); - $property['info']['address'] = $data->get_address(); - $property['meta'] = $data->get_meta_shortinfo(); - - $property['status'] = $data->get_status(); - $property['locations'] = $data->get_locations(); - $property['amenities'] = $data->get_amenities(); - $property['types'] = $data->get_types_tax(); - - - if ( user_can( $this->user_id, 'view_opalestate_sensitive_data' ) || $this->override ) { - - //Sensitive data here - do_action( 'opalestate_api_sensitive_data' ); - - } - - return apply_filters( 'opalestate_api_properties_property', $property ); - - } - - - - - /** - * Retrieve the output propertyat - * - * Determines whether results should be displayed in XML or JSON - * - * @since 1.1 - * @access public - * - * @return mixed|void - */ - public function get_output_propertyat() { - global $wp_query; - - $propertyat = isset( $wp_query->query_vars['propertyat'] ) ? $wp_query->query_vars['propertyat'] : 'json'; - - return apply_filters( 'opalestate_api_output_propertyat', $propertyat ); - } - - - /** - * Log each API request, if enabled - * - * @access private - * @since 1.1 - * - * @global Opalestate_Logging $opalestate_logs - * @global WP_Query $wp_query - * - * @param array $data - * - * @return void - */ - private function log_request( $data = array() ) { - if ( ! $this->log_requests ) { - return; - } - - /** - * @var Opalestate_Logging $opalestate_logs - */ - global $opalestate_logs; - - /** - * @var WP_Query $wp_query - */ - global $wp_query; - - $query = array( - 'opalestate-api' => $wp_query->query_vars['opalestate-api'], - 'key' => isset( $wp_query->query_vars['key'] ) ? $wp_query->query_vars['key'] : null, - 'token' => isset( $wp_query->query_vars['token'] ) ? $wp_query->query_vars['token'] : null, - 'query' => isset( $wp_query->query_vars['query'] ) ? $wp_query->query_vars['query'] : null, - 'type' => isset( $wp_query->query_vars['type'] ) ? $wp_query->query_vars['type'] : null, - 'property' => isset( $wp_query->query_vars['property'] ) ? $wp_query->query_vars['property'] : null, - 'customer' => isset( $wp_query->query_vars['customer'] ) ? $wp_query->query_vars['customer'] : null, - 'date' => isset( $wp_query->query_vars['date'] ) ? $wp_query->query_vars['date'] : null, - 'startdate' => isset( $wp_query->query_vars['startdate'] ) ? $wp_query->query_vars['startdate'] : null, - 'enddate' => isset( $wp_query->query_vars['enddate'] ) ? $wp_query->query_vars['enddate'] : null, - 'id' => isset( $wp_query->query_vars['id'] ) ? $wp_query->query_vars['id'] : null, - 'purchasekey' => isset( $wp_query->query_vars['purchasekey'] ) ? $wp_query->query_vars['purchasekey'] : null, - 'email' => isset( $wp_query->query_vars['email'] ) ? $wp_query->query_vars['email'] : null, - ); - - $log_data = array( - 'log_type' => 'api_request', - 'post_excerpt' => http_build_query( $query ), - 'post_content' => ! empty( $data['error'] ) ? $data['error'] : '', - ); - - $log_meta = array( - 'request_ip' => opalestate_get_ip(), - 'user' => $this->user_id, - 'key' => isset( $wp_query->query_vars['key'] ) ? $wp_query->query_vars['key'] : null, - 'token' => isset( $wp_query->query_vars['token'] ) ? $wp_query->query_vars['token'] : null, - 'time' => $data['request_speed'], - 'version' => $this->get_queried_version() - ); - } - - - /** - * Retrieve the output data - * - * @access public - * @since 1.1 - * @return array - */ - public function get_output() { - return $this->data; - } - - /** - * Output Query in either JSON/XML. The query data is outputted as JSON - * by default - * - * @since 1.1 - * @global WP_Query $wp_query - * - * @param int $status_code - */ - public function output( $status_code = 200 ) { - /** - * @var WP_Query $wp_query - */ - global $wp_query; - - $propertyat = $this->get_output_propertyat(); - - status_header( $status_code ); - - do_action( 'opalestate_api_output_before', $this->data, $this, $propertyat ); - - switch ( $propertyat ) : - - case 'xml' : - - require_once OPALESTATE_PLUGIN_DIR . 'inc/libraries/array2xml.php'; - $xml = Array2XML::createXML( 'opalestate-pro', $this->data ); - echo $xml->saveXML(); - - break; - - case 'json' : - - header( 'Content-Type: application/json' ); - if ( ! empty( $this->pretty_print ) ) { - echo json_encode( $this->data, $this->pretty_print ); - } else { - echo json_encode( $this->data ); - } - - break; - - - default : - - // Allow other propertyats to be added via extensions - do_action( 'opalestate_api_output_' . $propertyat, $this->data, $this ); - - break; - - endswitch; - - do_action( 'opalestate_api_output_after', $this->data, $this, $propertyat ); - - die(); - } - - /** - * Modify User Profile - * - * Modifies the output of profile.php to add key generation/revocation - * - * @access public - * @since 1.1 - * - * @param object $user Current user info - * - * @return void - */ - function user_key_field( $user ) { - - if ( ( opalestate_get_option( 'api_allow_user_keys', false ) || current_user_can( 'manage_opalestate_settings' ) ) && current_user_can( 'edit_user', $user->ID ) ) { - $user = get_userdata( $user->ID ); - ?> -
- - - - - - - -
- - - get_user_public_key( $user->ID ); - $secret_key = $this->get_user_secret_key( $user->ID ); - ?> - opalestate_user_public_key ) ) { ?> - - - -   - -
-   - -
-   - -
- - - -
- 403 ) ); - - } - - if ( empty( $args['user_id'] ) ) { - wp_die( esc_html__( 'User ID Required.', 'opalestate-pro' ), esc_html__( 'Error', 'opalestate-pro' ), array( 'response' => 401 ) ); - } - - if ( is_numeric( $args['user_id'] ) ) { - $user_id = isset( $args['user_id'] ) ? absint( $args['user_id'] ) : get_current_user_id(); - } else { - $userdata = get_user_by( 'login', $args['user_id'] ); - $user_id = $userdata->ID; - } - $process = isset( $args['opalestate_api_process'] ) ? strtolower( $args['opalestate_api_process'] ) : false; - - if ( $user_id == get_current_user_id() && ! opalestate_get_option( 'allow_user_api_keys' ) && ! current_user_can( 'manage_opalestate_settings' ) ) { - wp_die( - sprintf( - /* translators: %s: process */ - esc_html__( 'You do not have permission to %s API keys for this user.', 'opalestate-pro' ), - $process - ), - esc_html__( 'Error', 'opalestate-pro' ), - array( 'response' => 403 ) - ); - } elseif ( ! current_user_can( 'manage_opalestate_settings' ) ) { - wp_die( - sprintf( - /* translators: %s: process */ - esc_html__( 'You do not have permission to %s API keys for this user.', 'opalestate-pro' ), - $process - ), - esc_html__( 'Error', 'opalestate-pro' ), - array( 'response' => 403 ) - ); - } - - switch ( $process ) { - case 'generate': - if ( $this->generate_api_key( $user_id ) ) { - delete_transient( 'opalestate_total_api_keys' ); - wp_redirect( add_query_arg( 'opalestate-message', 'api-key-generated', 'edit.php?post_type=opalestate_property&page=opalestate-settings&tab=api' ) ); - exit(); - } else { - wp_redirect( add_query_arg( 'opalestate-message', 'api-key-failed', 'edit.php?post_type=opalestate_property&page=opalestate-settings&tab=api' ) ); - exit(); - } - break; - case 'regenerate': - $this->generate_api_key( $user_id, true ); - delete_transient( 'opalestate_total_api_keys' ); - wp_redirect( add_query_arg( 'opalestate-message', 'api-key-regenerated', 'edit.php?post_type=opalestate_property&page=opalestate-settings&tab=api' ) ); - exit(); - break; - case 'revoke': - $this->revoke_api_key( $user_id ); - delete_transient( 'opalestate_total_api_keys' ); - wp_redirect( add_query_arg( 'opalestate-message', 'api-key-revoked', 'edit.php?post_type=opalestate_property&page=opalestate-settings&tab=api' ) ); - exit(); - break; - default; - break; - } - } - - /** - * Generate new API keys for a user - * - * @access public - * @since 1.1 - * - * @param int $user_id User ID the key is being generated for - * @param boolean $regenerate Regenerate the key for the user - * - * @return boolean True if (re)generated succesfully, false otherwise. - */ - public function generate_api_key( $user_id = 0, $regenerate = false ) { - - if ( empty( $user_id ) ) { - return false; - } - - $user = get_userdata( $user_id ); - - if ( ! $user ) { - return false; - } - - $public_key = $this->get_user_public_key( $user_id ); - $secret_key = $this->get_user_secret_key( $user_id ); - - if ( empty( $public_key ) || $regenerate == true ) { - $new_public_key = $this->generate_public_key( $user->user_email ); - $new_secret_key = $this->generate_private_key( $user->ID ); - } else { - return false; - } - - if ( $regenerate == true ) { - $this->revoke_api_key( $user->ID ); - } - - update_user_meta( $user_id, $new_public_key, 'opalestate_user_public_key' ); - update_user_meta( $user_id, $new_secret_key, 'opalestate_user_secret_key' ); - - return true; - } - - /** - * Revoke a users API keys - * - * @access public - * @since 1.1 - * - * @param int $user_id User ID of user to revoke key for - * - * @return string - */ - public function revoke_api_key( $user_id = 0 ) { - - if ( empty( $user_id ) ) { - return false; - } - - $user = get_userdata( $user_id ); - - if ( ! $user ) { - return false; - } - - $public_key = $this->get_user_public_key( $user_id ); - $secret_key = $this->get_user_secret_key( $user_id ); - if ( ! empty( $public_key ) ) { - delete_transient( md5( 'opalestate_api_user_' . $public_key ) ); - delete_transient( md5( 'opalestate_api_user_public_key' . $user_id ) ); - delete_transient( md5( 'opalestate_api_user_secret_key' . $user_id ) ); - delete_user_meta( $user_id, $public_key ); - delete_user_meta( $user_id, $secret_key ); - } else { - return false; - } - - return true; - } - - public function get_version() { - return self::VERSION; - } - - - /** - * Generate and Save API key - * - * Generates the key requested by user_key_field and stores it in the database - * - * @access public - * @since 1.1 - * - * @param int $user_id - * - * @return void - */ - public function update_key( $user_id ) { - if ( current_user_can( 'edit_user', $user_id ) && isset( $_POST['opalestate_set_api_key'] ) ) { - - $user = get_userdata( $user_id ); - - $public_key = $this->get_user_public_key( $user_id ); - $secret_key = $this->get_user_secret_key( $user_id ); - - if ( empty( $public_key ) ) { - $new_public_key = $this->generate_public_key( $user->user_email ); - $new_secret_key = $this->generate_private_key( $user->ID ); - - update_user_meta( $user_id, $new_public_key, 'opalestate_user_public_key' ); - update_user_meta( $user_id, $new_secret_key, 'opalestate_user_secret_key' ); - } else { - $this->revoke_api_key( $user_id ); - } - } - } - - /** - * Generate the public key for a user - * - * @access private - * @since 1.1 - * - * @param string $user_email - * - * @return string - */ - private function generate_public_key( $user_email = '' ) { - $auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : ''; - $public = hash( 'md5', $user_email . $auth_key . date( 'U' ) ); - - return $public; - } - - /** - * Generate the secret key for a user - * - * @access private - * @since 1.1 - * - * @param int $user_id - * - * @return string - */ - private function generate_private_key( $user_id = 0 ) { - $auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : ''; - $secret = hash( 'md5', $user_id . $auth_key . date( 'U' ) ); - - return $secret; - } - - /** - * Retrieve the user's token - * - * @access private - * @since 1.1 - * - * @param int $user_id - * - * @return string - */ - public function get_token( $user_id = 0 ) { - return hash( 'md5', $this->get_user_secret_key( $user_id ) . $this->get_user_public_key( $user_id ) ); - } - - /** - * API Key Backwards Compatibility - * - * A Backwards Compatibility call for the change of meta_key/value for users API Keys - * - * @since 1.3.6 - * - * @param string $check Whether to check the cache or not - * @param int $object_id The User ID being passed - * @param string $meta_key The user meta key - * @param bool $single If it should return a single value or array - * - * @return string The API key/secret for the user supplied - */ - public function api_key_backwards_compat( $check, $object_id, $meta_key, $single ) { - - if ( $meta_key !== 'opalestate_user_public_key' && $meta_key !== 'opalestate_user_secret_key' ) { - return $check; - } - - $return = $check; - - switch ( $meta_key ) { - case 'opalestate_user_public_key': - $return = Opalestate()->api->get_user_public_key( $object_id ); - break; - case 'opalestate_user_secret_key': - $return = Opalestate()->api->get_user_secret_key( $object_id ); - break; - } - - if ( ! $single ) { - $return = array( $return ); - } - - return $return; - - } - -} diff --git a/inc/api/class-opalestate-api.php b/inc/api/class-opalestate-api.php index 3d316dab..6d91d786 100755 --- a/inc/api/class-opalestate-api.php +++ b/inc/api/class-opalestate-api.php @@ -38,12 +38,12 @@ class Opalestate_API { $this->includes( [ 'class-opalestate-admin-api-keys.php', 'class-opalestate-admin-api-keys-table-list.php', + 'class-opalestate-rest-authentication.php', 'class-opalestate-base-api.php', 'v1/property.php', 'v1/agent.php', 'v1/agency.php', 'v1/search-form.php', - 'class-opalestate-api-auth.php', 'functions.php', ] ); diff --git a/inc/api/class-opalestate-base-api.php b/inc/api/class-opalestate-base-api.php index 58613bdd..c0198a5d 100644 --- a/inc/api/class-opalestate-base-api.php +++ b/inc/api/class-opalestate-base-api.php @@ -144,6 +144,17 @@ abstract class Opalestate_Base_API { return apply_filters( 'opalestate_api_results_per_page', $per_page ); } + /** + * Get object. + * + * @param int $id Object ID. + * @return object WC_Data object or WP_Error object. + */ + protected function get_object( $id ) { + // translators: %s: Class method name. + return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass.", 'opalestate-pro' ), __METHOD__ ), array( 'status' => 405 ) ); + } + /** * Displays a missing authentication error if all the parameters aren't * provided @@ -161,8 +172,6 @@ abstract class Opalestate_Base_API { * credentials * * @access private - * @since 1.1 - * @uses Opaljob_API::output() * @return WP_Error with message key rest_forbidden */ private function invalid_auth() { @@ -195,6 +204,22 @@ abstract class Opalestate_Base_API { return true; } + /** + * Check if a given request has access to read an item. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|boolean + */ + public function get_item_permissions_check( $request ) { + $object = $this->get_object( (int) $request['id'] ); + + if ( $object && 0 !== $object->get_id() && ! opalestate_rest_check_post_permissions( $this->post_type, 'read', $object->get_id() ) ) { + return new WP_Error( 'opalestate_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'opalestate-pro' ), array( 'status' => rest_authorization_required_code() ) ); + } + + return true; + } + /** * Check if a given request has access to create an item. * @@ -209,6 +234,22 @@ abstract class Opalestate_Base_API { return true; } + /** + * Check if a given request has access to update an item. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|boolean + */ + public function update_item_permissions_check( $request ) { + $object = $this->get_object( (int) $request['id'] ); + + if ( $object && 0 !== $object->get_id() && ! opalestate_rest_check_post_permissions( $this->post_type, 'edit', $object->get_id() ) ) { + return new WP_Error( 'opalestate_rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'opalestate-pro' ), array( 'status' => rest_authorization_required_code() ) ); + } + + return true; + } + /** * Get the query params for collections of attachments. * diff --git a/inc/api/class-opalestate-rest-authentication.php b/inc/api/class-opalestate-rest-authentication.php new file mode 100644 index 00000000..42761ab1 --- /dev/null +++ b/inc/api/class-opalestate-rest-authentication.php @@ -0,0 +1,608 @@ +is_request_to_rest_api() ) { + return $user_id; + } + + if ( is_ssl() ) { + $user_id = $this->perform_basic_authentication(); + } + + if ( $user_id ) { + return $user_id; + } + + return $this->perform_oauth_authentication(); + } + + /** + * Check for authentication error. + * + * @param WP_Error|null|bool $error Error data. + * @return WP_Error|null|bool + */ + public function check_authentication_error( $error ) { + // Pass through other errors. + if ( ! empty( $error ) ) { + return $error; + } + + return $this->get_error(); + } + + /** + * Set authentication error. + * + * @param WP_Error $error Authentication error data. + */ + protected function set_error( $error ) { + // Reset user. + $this->user = null; + + $this->error = $error; + } + + /** + * Get authentication error. + * + * @return WP_Error|null. + */ + protected function get_error() { + return $this->error; + } + + /** + * Basic Authentication. + * + * SSL-encrypted requests are not subject to sniffing or man-in-the-middle + * attacks, so the request can be authenticated by simply looking up the user + * associated with the given consumer key and confirming the consumer secret + * provided is valid. + * + * @return int|bool + */ + private function perform_basic_authentication() { + $this->auth_method = 'basic_auth'; + $consumer_key = ''; + $consumer_secret = ''; + + // If the $_GET parameters are present, use those first. + if ( ! empty( $_GET['consumer_key'] ) && ! empty( $_GET['consumer_secret'] ) ) { // WPCS: CSRF ok. + $consumer_key = $_GET['consumer_key']; // WPCS: CSRF ok, sanitization ok. + $consumer_secret = $_GET['consumer_secret']; // WPCS: CSRF ok, sanitization ok. + } + + // If the above is not present, we will do full basic auth. + if ( ! $consumer_key && ! empty( $_SERVER['PHP_AUTH_USER'] ) && ! empty( $_SERVER['PHP_AUTH_PW'] ) ) { + $consumer_key = $_SERVER['PHP_AUTH_USER']; // WPCS: CSRF ok, sanitization ok. + $consumer_secret = $_SERVER['PHP_AUTH_PW']; // WPCS: CSRF ok, sanitization ok. + } + + // Stop if don't have any key. + if ( ! $consumer_key || ! $consumer_secret ) { + return false; + } + + // Get user data. + $this->user = $this->get_user_data_by_consumer_key( $consumer_key ); + if ( empty( $this->user ) ) { + return false; + } + + // Validate user secret. + if ( ! hash_equals( $this->user->consumer_secret, $consumer_secret ) ) { // @codingStandardsIgnoreLine + $this->set_error( new WP_Error( 'opalestate_rest_authentication_error', __( 'Consumer secret is invalid.', 'opalestate-pro' ), [ 'status' => 401 ] ) ); + + return false; + } + + return $this->user->user_id; + } + + /** + * Parse the Authorization header into parameters. + * + * @param string $header Authorization header value (not including "Authorization: " prefix). + * + * @return array Map of parameter values. + */ + public function parse_header( $header ) { + if ( 'OAuth ' !== substr( $header, 0, 6 ) ) { + return []; + } + + // From OAuth PHP library, used under MIT license. + $params = []; + if ( preg_match_all( '/(oauth_[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches ) ) { + foreach ( $matches[1] as $i => $h ) { + $params[ $h ] = urldecode( empty( $matches[3][ $i ] ) ? $matches[4][ $i ] : $matches[3][ $i ] ); + } + if ( isset( $params['realm'] ) ) { + unset( $params['realm'] ); + } + } + + return $params; + } + + /** + * Get the authorization header. + * + * On certain systems and configurations, the Authorization header will be + * stripped out by the server or PHP. Typically this is then used to + * generate `PHP_AUTH_USER`/`PHP_AUTH_PASS` but not passed on. We use + * `getallheaders` here to try and grab it out instead. + * + * @return string Authorization header if set. + * + */ + public function get_authorization_header() { + if ( ! empty( $_SERVER['HTTP_AUTHORIZATION'] ) ) { + return wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ); // WPCS: sanitization ok. + } + + if ( function_exists( 'getallheaders' ) ) { + $headers = getallheaders(); + // Check for the authoization header case-insensitively. + foreach ( $headers as $key => $value ) { + if ( 'authorization' === strtolower( $key ) ) { + return $value; + } + } + } + + return ''; + } + + /** + * Get oAuth parameters from $_GET, $_POST or request header. + * + * @return array|WP_Error + */ + public function get_oauth_parameters() { + $params = array_merge( $_GET, $_POST ); // WPCS: CSRF ok. + $params = wp_unslash( $params ); + $header = $this->get_authorization_header(); + + if ( ! empty( $header ) ) { + // Trim leading spaces. + $header = trim( $header ); + $header_params = $this->parse_header( $header ); + + if ( ! empty( $header_params ) ) { + $params = array_merge( $params, $header_params ); + } + } + + $param_names = [ + 'oauth_consumer_key', + 'oauth_timestamp', + 'oauth_nonce', + 'oauth_signature', + 'oauth_signature_method', + ]; + + $errors = []; + $have_one = false; + + // Check for required OAuth parameters. + foreach ( $param_names as $param_name ) { + if ( empty( $params[ $param_name ] ) ) { + $errors[] = $param_name; + } else { + $have_one = true; + } + } + + // All keys are missing, so we're probably not even trying to use OAuth. + if ( ! $have_one ) { + return []; + } + + // If we have at least one supplied piece of data, and we have an error, + // then it's a failed authentication. + if ( ! empty( $errors ) ) { + $message = sprintf( + /* translators: %s: amount of errors */ + _n( 'Missing OAuth parameter %s', 'Missing OAuth parameters %s', count( $errors ), 'opalestate-pro' ), + implode( ', ', $errors ) + ); + + $this->set_error( new WP_Error( 'opalestate_rest_authentication_missing_parameter', $message, [ 'status' => 401 ] ) ); + + return []; + } + + return $params; + } + + /** + * Perform OAuth 1.0a "one-legged" (http://oauthbible.com/#oauth-10a-one-legged) authentication for non-SSL requests. + * + * This is required so API credentials cannot be sniffed or intercepted when making API requests over plain HTTP. + * + * This follows the spec for simple OAuth 1.0a authentication (RFC 5849) as closely as possible, with two exceptions: + * + * 1) There is no token associated with request/responses, only consumer keys/secrets are used. + * + * 2) The OAuth parameters are included as part of the request query string instead of part of the Authorization header, + * This is because there is no cross-OS function within PHP to get the raw Authorization header. + * + * @link http://tools.ietf.org/html/rfc5849 for the full spec. + * + * @return int|bool + */ + private function perform_oauth_authentication() { + $this->auth_method = 'oauth1'; + + $params = $this->get_oauth_parameters(); + if ( empty( $params ) ) { + return false; + } + + // Fetch WP user by consumer key. + $this->user = $this->get_user_data_by_consumer_key( $params['oauth_consumer_key'] ); + + if ( empty( $this->user ) ) { + $this->set_error( new WP_Error( 'opalestate_rest_authentication_error', __( 'Consumer key is invalid.', 'opalestate-pro' ), [ 'status' => 401 ] ) ); + + return false; + } + + // Perform OAuth validation. + $signature = $this->check_oauth_signature( $this->user, $params ); + if ( is_wp_error( $signature ) ) { + $this->set_error( $signature ); + + return false; + } + + $timestamp_and_nonce = $this->check_oauth_timestamp_and_nonce( $this->user, $params['oauth_timestamp'], $params['oauth_nonce'] ); + if ( is_wp_error( $timestamp_and_nonce ) ) { + $this->set_error( $timestamp_and_nonce ); + + return false; + } + + return $this->user->user_id; + } + + /** + * Verify that the consumer-provided request signature matches our generated signature, + * this ensures the consumer has a valid key/secret. + * + * @param stdClass $user User data. + * @param array $params The request parameters. + * @return true|WP_Error + */ + private function check_oauth_signature( $user, $params ) { + $http_method = isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( $_SERVER['REQUEST_METHOD'] ) : ''; // WPCS: sanitization ok. + $request_path = isset( $_SERVER['REQUEST_URI'] ) ? wp_parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH ) : ''; // WPCS: sanitization ok. + $wp_base = get_home_url( null, '/', 'relative' ); + if ( substr( $request_path, 0, strlen( $wp_base ) ) === $wp_base ) { + $request_path = substr( $request_path, strlen( $wp_base ) ); + } + $base_request_uri = rawurlencode( get_home_url( null, $request_path, is_ssl() ? 'https' : 'http' ) ); + + // Get the signature provided by the consumer and remove it from the parameters prior to checking the signature. + $consumer_signature = rawurldecode( str_replace( ' ', '+', $params['oauth_signature'] ) ); + unset( $params['oauth_signature'] ); + + // Sort parameters. + if ( ! uksort( $params, 'strcmp' ) ) { + return new WP_Error( 'opalestate_rest_authentication_error', __( 'Invalid signature - failed to sort parameters.', 'opalestate-pro' ), [ 'status' => 401 ] ); + } + + // Normalize parameter key/values. + $params = $this->normalize_parameters( $params ); + $query_string = implode( '%26', $this->join_with_equals_sign( $params ) ); // Join with ampersand. + $string_to_sign = $http_method . '&' . $base_request_uri . '&' . $query_string; + + if ( 'HMAC-SHA1' !== $params['oauth_signature_method'] && 'HMAC-SHA256' !== $params['oauth_signature_method'] ) { + return new WP_Error( 'opalestate_rest_authentication_error', __( 'Invalid signature - signature method is invalid.', 'opalestate-pro' ), [ 'status' => 401 ] ); + } + + $hash_algorithm = strtolower( str_replace( 'HMAC-', '', $params['oauth_signature_method'] ) ); + $secret = $user->consumer_secret . '&'; + $signature = base64_encode( hash_hmac( $hash_algorithm, $string_to_sign, $secret, true ) ); + + if ( ! hash_equals( $signature, $consumer_signature ) ) { // @codingStandardsIgnoreLine + return new WP_Error( 'opalestate_rest_authentication_error', __( 'Invalid signature - provided signature does not match.', 'opalestate-pro' ), [ 'status' => 401 ] ); + } + + return true; + } + + /** + * Creates an array of urlencoded strings out of each array key/value pairs. + * + * @param array $params Array of parameters to convert. + * @param array $query_params Array to extend. + * @param string $key Optional Array key to append. + * @return string Array of urlencoded strings. + */ + private function join_with_equals_sign( $params, $query_params = [], $key = '' ) { + foreach ( $params as $param_key => $param_value ) { + if ( $key ) { + $param_key = $key . '%5B' . $param_key . '%5D'; // Handle multi-dimensional array. + } + + if ( is_array( $param_value ) ) { + $query_params = $this->join_with_equals_sign( $param_value, $query_params, $param_key ); + } else { + $string = $param_key . '=' . $param_value; // Join with equals sign. + $query_params[] = opalestate_rest_urlencode_rfc3986( $string ); + } + } + + return $query_params; + } + + /** + * Normalize each parameter by assuming each parameter may have already been + * encoded, so attempt to decode, and then re-encode according to RFC 3986. + * + * Note both the key and value is normalized so a filter param like: + * + * 'filter[period]' => 'week' + * + * is encoded to: + * + * 'filter%255Bperiod%255D' => 'week' + * + * This conforms to the OAuth 1.0a spec which indicates the entire query string + * should be URL encoded. + * + * @param array $parameters Un-normalized parameters. + * @return array Normalized parameters. + * @see rawurlencode() + */ + private function normalize_parameters( $parameters ) { + $keys = opalestate_rest_urlencode_rfc3986( array_keys( $parameters ) ); + $values = opalestate_rest_urlencode_rfc3986( array_values( $parameters ) ); + $parameters = array_combine( $keys, $values ); + + return $parameters; + } + + /** + * Verify that the timestamp and nonce provided with the request are valid. This prevents replay attacks where + * an attacker could attempt to re-send an intercepted request at a later time. + * + * - A timestamp is valid if it is within 15 minutes of now. + * - A nonce is valid if it has not been used within the last 15 minutes. + * + * @param stdClass $user User data. + * @param int $timestamp The unix timestamp for when the request was made. + * @param string $nonce A unique (for the given user) 32 alphanumeric string, consumer-generated. + * @return bool|WP_Error + */ + private function check_oauth_timestamp_and_nonce( $user, $timestamp, $nonce ) { + global $wpdb; + + $valid_window = 15 * 60; // 15 minute window. + + if ( ( $timestamp < time() - $valid_window ) || ( $timestamp > time() + $valid_window ) ) { + return new WP_Error( 'opalestate_rest_authentication_error', __( 'Invalid timestamp.', 'opalestate-pro' ), [ 'status' => 401 ] ); + } + + $used_nonces = maybe_unserialize( $user->nonces ); + + if ( empty( $used_nonces ) ) { + $used_nonces = []; + } + + if ( in_array( $nonce, $used_nonces, true ) ) { + return new WP_Error( 'opalestate_rest_authentication_error', __( 'Invalid nonce - nonce has already been used.', 'opalestate-pro' ), [ 'status' => 401 ] ); + } + + $used_nonces[ $timestamp ] = $nonce; + + // Remove expired nonces. + foreach ( $used_nonces as $nonce_timestamp => $nonce ) { + if ( $nonce_timestamp < ( time() - $valid_window ) ) { + unset( $used_nonces[ $nonce_timestamp ] ); + } + } + + $used_nonces = maybe_serialize( $used_nonces ); + + $wpdb->update( + $wpdb->prefix . 'opalestate_api_keys', + [ 'nonces' => $used_nonces ], + [ 'key_id' => $user->key_id ], + [ '%s' ], + [ '%d' ] + ); + + return true; + } + + /** + * Return the user data for the given consumer_key. + * + * @param string $consumer_key Consumer key. + * @return array + */ + private function get_user_data_by_consumer_key( $consumer_key ) { + global $wpdb; + + $consumer_key = opalestate_api_hash( sanitize_text_field( $consumer_key ) ); + $user = $wpdb->get_row( + $wpdb->prepare( + " + SELECT key_id, user_id, permissions, consumer_key, consumer_secret, nonces + FROM {$wpdb->prefix}opalestate_api_keys + WHERE consumer_key = %s + ", + $consumer_key + ) + ); + + return $user; + } + + /** + * Check that the API keys provided have the proper key-specific permissions to either read or write API resources. + * + * @param string $method Request method. + * @return bool|WP_Error + */ + private function check_permissions( $method ) { + $permissions = $this->user->permissions; + + switch ( $method ) { + case 'HEAD': + case 'GET': + if ( 'read' !== $permissions && 'read_write' !== $permissions ) { + return new WP_Error( 'opalestate_rest_authentication_error', __( 'The API key provided does not have read permissions.', 'opalestate-pro' ), [ 'status' => 401 ] ); + } + break; + case 'POST': + case 'PUT': + case 'PATCH': + case 'DELETE': + if ( 'write' !== $permissions && 'read_write' !== $permissions ) { + return new WP_Error( 'opalestate_rest_authentication_error', __( 'The API key provided does not have write permissions.', 'opalestate-pro' ), [ 'status' => 401 ] ); + } + break; + case 'OPTIONS': + return true; + + default: + return new WP_Error( 'opalestate_rest_authentication_error', __( 'Unknown request method.', 'opalestate-pro' ), [ 'status' => 401 ] ); + } + + return true; + } + + /** + * Updated API Key last access datetime. + */ + private function update_last_access() { + global $wpdb; + + $wpdb->update( + $wpdb->prefix . 'opalestate_api_keys', + [ 'last_access' => current_time( 'mysql' ) ], + [ 'key_id' => $this->user->key_id ], + [ '%s' ], + [ '%d' ] + ); + } + + /** + * If the consumer_key and consumer_secret $_GET parameters are NOT provided + * and the Basic auth headers are either not present or the consumer secret does not match the consumer + * key provided, then return the correct Basic headers and an error message. + * + * @param WP_REST_Response $response Current response being served. + * @return WP_REST_Response + */ + public function send_unauthorized_headers( $response ) { + if ( is_wp_error( $this->get_error() ) && 'basic_auth' === $this->auth_method ) { + $auth_message = __( 'Opalestate API. Use a consumer key in the username field and a consumer secret in the password field.', 'opalestate-pro' ); + $response->header( 'WWW-Authenticate', 'Basic realm="' . $auth_message . '"', true ); + } + + return $response; + } + + /** + * Check for user permissions and register last access. + * + * @param mixed $result Response to replace the requested version with. + * @param WP_REST_Server $server Server instance. + * @param WP_REST_Request $request Request used to generate the response. + * @return mixed + */ + public function check_user_permissions( $result, $server, $request ) { + if ( $this->user ) { + // Check API Key permissions. + $allowed = $this->check_permissions( $request->get_method() ); + if ( is_wp_error( $allowed ) ) { + return $allowed; + } + + // Register last access. + $this->update_last_access(); + } + + return $result; + } +} + +new Opalestate_REST_Authentication(); diff --git a/inc/api/functions.php b/inc/api/functions.php index 1d910df4..dc1426ed 100644 --- a/inc/api/functions.php +++ b/inc/api/functions.php @@ -116,5 +116,20 @@ function opalestate_rand_hash() { * @return string */ function opalestate_api_hash( $data ) { - return hash_hmac( 'sha256', $data, 'opalestate-api' ); + return hash_hmac( 'sha256', $data, 'estate-api' ); +} + +/** + * Encodes a value according to RFC 3986. + * Supports multidimensional arrays. + * + * @param string|array $value The value to encode. + * @return string|array Encoded values. + */ +function opalestate_rest_urlencode_rfc3986( $value ) { + if ( is_array( $value ) ) { + return array_map( 'opalestate_rest_urlencode_rfc3986', $value ); + } + + return str_replace( array( '+', '%7E' ), array( ' ', '~' ), rawurlencode( $value ) ); } diff --git a/inc/api/v1/agency.php b/inc/api/v1/agency.php index 0e4e6cc0..695aaa1e 100755 --- a/inc/api/v1/agency.php +++ b/inc/api/v1/agency.php @@ -1,13 +1,4 @@ - * @license https://opensource.org/licenses/gpl-license GNU Public License - * @since 1.0 - */ // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; @@ -16,7 +7,6 @@ if ( ! defined( 'ABSPATH' ) ) { /** * @class Job_Api * - * @since 1.0.0 * @package Opal_Job * @subpackage Opal_Job/controllers */ @@ -25,7 +15,6 @@ class Opalestate_Agency_Api extends Opalestate_Base_API { /** * The unique identifier of the route resource. * - * @since 1.0.0 * @access public * @var string $base . */ @@ -42,9 +31,6 @@ class Opalestate_Agency_Api extends Opalestate_Base_API { * Register Routes * * Register all CURD actions with POST/GET/PUT and calling function for each - * - * @since 1.0 - * */ public function register_routes() { /** @@ -57,10 +43,10 @@ class Opalestate_Agency_Api extends Opalestate_Base_API { '/' . $this->base, [ [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'get_items' ], - // 'permission_callback' => [ $this, 'get_items_permissions_check' ], - 'args' => $this->get_collection_params(), + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_items' ], + 'permission_callback' => [ $this, 'get_items_permissions_check' ], + 'args' => $this->get_collection_params(), ], ] ); @@ -76,9 +62,9 @@ class Opalestate_Agency_Api extends Opalestate_Base_API { ], ], [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'get_item' ], - // 'permission_callback' => [ $this, 'get_item_permissions_check' ], + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_item' ], + 'permission_callback' => [ $this, 'get_item_permissions_check' ], ], // [ // 'methods' => WP_REST_Server::EDITABLE, @@ -99,14 +85,24 @@ class Opalestate_Agency_Api extends Opalestate_Base_API { ], ], [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'get_listings' ], - // 'permission_callback' => [ $this, 'get_item_permissions_check' ], + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_listings' ], + 'permission_callback' => [ $this, 'get_item_permissions_check' ], ], ] ); } + /** + * Get object. + * + * @param int $id Object ID. + * + * @return Opalestate_Agency + */ + protected function get_object( $id ) { + return opalesetate_agency( $id ); + } /** * Get List Of agencies. @@ -114,8 +110,6 @@ class Opalestate_Agency_Api extends Opalestate_Base_API { * Based on request to get collection * * @return WP_REST_Response is json data - * @since 1.0 - * */ public function get_items( $request ) { $agencies['agencies'] = []; @@ -149,8 +143,6 @@ class Opalestate_Agency_Api extends Opalestate_Base_API { * Based on request to get a agency. * * @return WP_REST_Response is json data - * @since 1.0 - * */ public function get_item( $request ) { $response = []; @@ -177,9 +169,7 @@ class Opalestate_Agency_Api extends Opalestate_Base_API { * * @param object $agency_info The Download Post Object * - * @return array Array of post data to return back in the API - * @since 1.0 - * + * @return array Array of post data to return back in the API */ public function get_agency_data( $agency_info ) { $agency = new OpalEstate_Agency( $agency_info->ID ); diff --git a/inc/api/v1/agent.php b/inc/api/v1/agent.php index 4dd28160..b4d69b42 100755 --- a/inc/api/v1/agent.php +++ b/inc/api/v1/agent.php @@ -48,10 +48,10 @@ class Opalestate_Agent_Api extends Opalestate_Base_API { '/' . $this->base, [ [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'get_items' ], - // 'permission_callback' => [ $this, 'get_items_permissions_check' ], - 'args' => $this->get_collection_params(), + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_items' ], + 'permission_callback' => [ $this, 'get_items_permissions_check' ], + 'args' => $this->get_collection_params(), ], ] ); @@ -67,9 +67,9 @@ class Opalestate_Agent_Api extends Opalestate_Base_API { ], ], [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'get_item' ], - // 'permission_callback' => [ $this, 'get_item_permissions_check' ], + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_item' ], + 'permission_callback' => [ $this, 'get_item_permissions_check' ], ], // [ // 'methods' => WP_REST_Server::EDITABLE, @@ -90,9 +90,9 @@ class Opalestate_Agent_Api extends Opalestate_Base_API { ], ], [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'get_listings' ], - // 'permission_callback' => [ $this, 'get_item_permissions_check' ], + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_listings' ], + 'permission_callback' => [ $this, 'get_item_permissions_check' ], ], ] ); diff --git a/inc/api/v1/property.php b/inc/api/v1/property.php index 3e1f5841..2db9093a 100644 --- a/inc/api/v1/property.php +++ b/inc/api/v1/property.php @@ -7,7 +7,6 @@ if ( ! defined( 'ABSPATH' ) ) { /** * Property_Api * - * @since 1.0.0 * @package Property_Api */ class Opalestate_Property_Api extends Opalestate_Base_API { @@ -15,7 +14,6 @@ class Opalestate_Property_Api extends Opalestate_Base_API { /** * The unique identifier of the route resource. * - * @since 1.0.0 * @access public * @var string $base . */ @@ -32,9 +30,6 @@ class Opalestate_Property_Api extends Opalestate_Base_API { * Register Routes * * Register all CURD actions with POST/GET/PUT and calling function for each - * - * @since 1.0 - * */ public function register_routes() { /** @@ -47,16 +42,16 @@ class Opalestate_Property_Api extends Opalestate_Base_API { '/' . $this->base, [ [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'get_items' ], - // 'permission_callback' => [ $this, 'get_items_permissions_check' ], - 'args' => $this->get_collection_params(), + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_items' ], + 'permission_callback' => [ $this, 'get_items_permissions_check' ], + 'args' => $this->get_collection_params(), + ], + [ + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => [ $this, 'create_item' ], + 'permission_callback' => [ $this, 'create_item_permissions_check' ], ], - // [ - // 'methods' => WP_REST_Server::CREATABLE, - // 'callback' => [ $this, 'create_item' ], - // // 'permission_callback' => [ $this, 'create_item_permissions_check' ], - // ], ] ); @@ -71,9 +66,9 @@ class Opalestate_Property_Api extends Opalestate_Base_API { ], ], [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'get_item' ], - // 'permission_callback' => [ $this, 'get_item_permissions_check' ], + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_item' ], + 'permission_callback' => [ $this, 'get_item_permissions_check' ], ], // [ // 'methods' => WP_REST_Server::EDITABLE, @@ -100,15 +95,26 @@ class Opalestate_Property_Api extends Opalestate_Base_API { '/' . $this->base . '/search', [ [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'get_results' ], - // 'permission_callback' => [ $this, 'get_items_permissions_check' ], - 'args' => $this->get_search_params(), + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_results' ], + 'permission_callback' => [ $this, 'get_items_permissions_check' ], + 'args' => $this->get_search_params(), ], ] ); } + /** + * Get object. + * + * @param int $id Object ID. + * + * @return Opalestate_Property + */ + protected function get_object( $id ) { + return opalesetate_property( $id ); + } + /** * Get List Of Properties * @@ -152,8 +158,6 @@ class Opalestate_Property_Api extends Opalestate_Base_API { * Based on request to get a property. * * @return WP_REST_Response is json data - * @since 1.0 - * */ public function get_item( $request ) { $response = []; @@ -191,7 +195,7 @@ class Opalestate_Property_Api extends Opalestate_Base_API { } public function get_results( $request ) { - $properties = []; + $properties = []; $property_list = $this->get_search_results_query( $request ); if ( $property_list ) { @@ -214,9 +218,7 @@ class Opalestate_Property_Api extends Opalestate_Base_API { * * @param object $property_info The Download Post Object * - * @return array Array of post data to return back in the API - * @since 1.0 - * + * @return array Array of post data to return back in the API */ private function get_property_data( $property_info ) { return opalestate_api_get_property_data( $property_info ); diff --git a/inc/vendors/elementor/widgets/opalestate-agency-collection.php b/inc/vendors/elementor/widgets/opalestate-agency-collection.php index 61ab87d0..1528d511 100755 --- a/inc/vendors/elementor/widgets/opalestate-agency-collection.php +++ b/inc/vendors/elementor/widgets/opalestate-agency-collection.php @@ -32,15 +32,15 @@ class Opalestate_Agency_Collection_Elementor_Widget extends Opalestate_Elementor * * Used to set scripts dependencies required to run the widget. * - * @since 1.3.0 + * @return array Widget scripts dependencies. + * @since 1.3.0 * @access public * - * @return array Widget scripts dependencies. */ public function get_script_depends() { return [ 'jquery-slick' ]; } - + /** * Get widget title. * @@ -94,287 +94,210 @@ class Opalestate_Agency_Collection_Elementor_Widget extends Opalestate_Elementor 'label' => esc_html__( 'Agencies Search/Collection', 'opalestate-pro' ), ] ); - + + // $this->add_control( + // 'search_form', + // [ + // 'label' => esc_html__('Search Form', 'opalestate-pro'), + // 'type' => \Elementor\Controls_Manager::SELECT, + // 'default' => '', + // 'options' => array( + // '' => esc_html__( 'Advanded', 'opalestate-pro' ), + // 'address' => esc_html__( 'Search By Address', 'opalestate-pro' ), + // ) + // ] + // ); + $this->add_control( - 'search_form', - [ - 'label' => esc_html__('Search Form', 'opalestate-pro'), - 'type' => \Elementor\Controls_Manager::SELECT, - 'default' => '', - 'options' => array( - '' => esc_html__( 'Advanded', 'opalestate-pro' ), - 'address' => esc_html__( 'Search By Address', 'opalestate-pro' ), - ) + 'enable_sortable_bar', + [ + 'label' => esc_html__( 'Enable Sortable Bar', 'opalestate-pro' ), + 'type' => Controls_Manager::SWITCHER, ] ); - $this->add_control( - 'enable_sortable_bar', - [ - 'label' => esc_html__('Enable Sortable Bar', 'opalestate-pro'), - 'type' => Controls_Manager::SWITCHER, - ] - ); - $this->add_control( 'item_layout', - [ - 'label' => esc_html__('Item Layout', 'opalestate-pro'), - 'type' => \Elementor\Controls_Manager::SELECT, - 'default' => 'grid', - 'options' => array( - 'grid' => esc_html__( 'Grid', 'opalestate-pro' ), - 'list' => esc_html__( 'List', 'opalestate-pro' ), - ) + [ + 'label' => esc_html__( 'Item Layout', 'opalestate-pro' ), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'grid', + 'options' => [ + 'grid' => esc_html__( 'Grid', 'opalestate-pro' ), + 'list' => esc_html__( 'List', 'opalestate-pro' ), + ], + ] + ); + $this->add_responsive_control( + 'column', + [ + 'label' => esc_html__( 'Columns', 'opalestate-pro' ), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 3, + 'options' => [ 1 => 1, 2 => 2, 3 => 3, 4 => 4, 6 => 6 ], + 'prefix_class' => 'elementor-grid%s-', + 'condition' => [ + 'item_layout' => 'grid', + ], + ] ); - $this->add_responsive_control( - 'column', - [ - 'label' => esc_html__('Columns', 'opalestate-pro'), - 'type' => \Elementor\Controls_Manager::SELECT, - 'default' => 3, - 'options' => [1 => 1, 2 => 2, 3 => 3, 4 => 4, 6 => 6], - 'prefix_class' => 'elementor-grid%s-', - 'condition' => [ - 'item_layout' => 'grid' - ] - - ] - ); $this->add_control( - 'column_gap', - [ - 'label' => esc_html__( 'Columns Gap', 'opalestate-pro' ), - 'type' => Controls_Manager::SLIDER, - 'range' => [ - 'px' => [ - 'min' => 0, - 'max' => 100, - ], - ], - 'selectors' => [ - '{{WRAPPER}} .elementor-items-container' => 'grid-column-gap: {{SIZE}}{{UNIT}}' - - ], - 'condition' => [ - 'item_layout' => 'grid' - ] - ] - ); + 'column_gap', + [ + 'label' => esc_html__( 'Columns Gap', 'opalestate-pro' ), + 'type' => Controls_Manager::SLIDER, + 'range' => [ + 'px' => [ + 'min' => 0, + 'max' => 100, + ], + ], + 'selectors' => [ + '{{WRAPPER}} .elementor-items-container' => 'grid-column-gap: {{SIZE}}{{UNIT}}', - $this->add_control( - 'enable_carousel', - [ - 'label' => esc_html__('Enable', 'opalestate-pro'), - 'type' => Controls_Manager::SWITCHER, - ] - ); + ], + 'condition' => [ + 'item_layout' => 'grid', + ], + ] + ); + + $this->add_control( + 'enable_carousel', + [ + 'label' => esc_html__( 'Enable', 'opalestate-pro' ), + 'type' => Controls_Manager::SWITCHER, + ] + ); $this->end_controls_section(); - $this->add_slick_controls( array('enable_carousel' => 'yes') , ' .agency-slick-carousel ' ); + $this->add_slick_controls( [ 'enable_carousel' => 'yes' ], ' .agency-slick-carousel ' ); $this->start_controls_section( - 'section_query', - [ - 'label' => esc_html__('Query', 'opalestate-pro'), - 'tab' => Controls_Manager::TAB_CONTENT, - ] - ); + 'section_query', + [ + 'label' => esc_html__( 'Query', 'opalestate-pro' ), + 'tab' => Controls_Manager::TAB_CONTENT, + ] + ); - $this->add_control( - 'posts_per_page', - [ - 'label' => esc_html__('Posts Per Page', 'opalestate-pro'), - 'type' => Controls_Manager::NUMBER, - 'default' => 6, - ] - ); + $this->add_control( + 'posts_per_page', + [ + 'label' => esc_html__( 'Posts Per Page', 'opalestate-pro' ), + 'type' => Controls_Manager::NUMBER, + 'default' => 6, + ] + ); + $this->add_control( + 'advanced', + [ + 'label' => esc_html__( 'Advanced', 'opalestate-pro' ), + 'type' => Controls_Manager::HEADING, + ] + ); - $this->add_control( - 'advanced', - [ - 'label' => esc_html__('Advanced', 'opalestate-pro'), - 'type' => Controls_Manager::HEADING, - ] - ); + $this->add_control( + 'orderby', + [ + 'label' => esc_html__( 'Order By', 'opalestate-pro' ), + 'type' => Controls_Manager::SELECT, + 'default' => 'post_date', + 'options' => [ + 'post_date' => esc_html__( 'Date', 'opalestate-pro' ), + 'post_title' => esc_html__( 'Title', 'opalestate-pro' ), + 'menu_order' => esc_html__( 'Menu Order', 'opalestate-pro' ), + 'rand' => esc_html__( 'Random', 'opalestate-pro' ), + ], + ] + ); - $this->add_control( - 'orderby', - [ - 'label' => esc_html__('Order By', 'opalestate-pro'), - 'type' => Controls_Manager::SELECT, - 'default' => 'post_date', - 'options' => [ - 'post_date' => esc_html__('Date', 'opalestate-pro'), - 'post_title' => esc_html__('Title', 'opalestate-pro'), - 'menu_order' => esc_html__('Menu Order', 'opalestate-pro'), - 'rand' => esc_html__('Random', 'opalestate-pro'), - ], - ] - ); + $this->add_control( + 'order', + [ + 'label' => esc_html__( 'Order', 'opalestate-pro' ), + 'type' => Controls_Manager::SELECT, + 'default' => 'desc', + 'options' => [ + 'asc' => esc_html__( 'ASC', 'opalestate-pro' ), + 'desc' => esc_html__( 'DESC', 'opalestate-pro' ), + ], + ] + ); - $this->add_control( - 'order', - [ - 'label' => esc_html__('Order', 'opalestate-pro'), - 'type' => Controls_Manager::SELECT, - 'default' => 'desc', - 'options' => [ - 'asc' => esc_html__('ASC', 'opalestate-pro'), - 'desc' => esc_html__('DESC', 'opalestate-pro'), - ], - ] - ); + $this->add_control( + 'categories', + [ + 'label' => esc_html__( 'Categories', 'opalestate-pro' ), + 'type' => Controls_Manager::SELECT2, + 'options' => $this->get_post_categories(), + 'multiple' => true, + ] + ); - $this->add_control( - 'categories', - [ - 'label' => esc_html__('Categories', 'opalestate-pro'), - 'type' => Controls_Manager::SELECT2, - 'options' => $this->get_post_categories(), - 'multiple' => true, - ] - ); + $this->add_control( + 'cat_operator', + [ + 'label' => esc_html__( 'Category Operator', 'opalestate-pro' ), + 'type' => Controls_Manager::SELECT, + 'default' => 'IN', + 'options' => [ + 'AND' => esc_html__( 'AND', 'opalestate-pro' ), + 'IN' => esc_html__( 'IN', 'opalestate-pro' ), + 'NOT IN' => esc_html__( 'NOT IN', 'opalestate-pro' ), + ], + 'condition' => [ + 'categories!' => '', + ], + ] + ); - $this->add_control( - 'cat_operator', - [ - 'label' => esc_html__('Category Operator', 'opalestate-pro'), - 'type' => Controls_Manager::SELECT, - 'default' => 'IN', - 'options' => [ - 'AND' => esc_html__('AND', 'opalestate-pro'), - 'IN' => esc_html__('IN', 'opalestate-pro'), - 'NOT IN' => esc_html__('NOT IN', 'opalestate-pro'), - ], - 'condition' => [ - 'categories!' => '' - ], - ] - ); + $this->end_controls_section(); - $this->end_controls_section(); + $this->start_controls_section( + 'section_pagination', + [ + 'label' => esc_html__( 'Pagination', 'opalestate-pro' ), + ] + ); - $this->start_controls_section( - 'section_pagination', - [ - 'label' => esc_html__('Pagination', 'opalestate-pro'), - 'condition' => [ - 'enable_carousel!' => 'yes' - ], - ] - ); + $this->add_control( + 'pagination', + [ + 'label' => esc_html__( 'Pagination', 'opalestate-pro' ), + 'type' => Controls_Manager::SELECT, + 'default' => '', + 'options' => [ + '' => esc_html__( 'None', 'opalestate-pro' ), + 'show' => esc_html__( 'Show', 'opalestate-pro' ), + ], + ] + ); - $this->add_control( - 'pagination_type', - [ - 'label' => esc_html__('Pagination', 'opalestate-pro'), - 'type' => Controls_Manager::SELECT, - 'default' => '', - 'options' => [ - '' => esc_html__('None', 'opalestate-pro'), - 'numbers' => esc_html__('Numbers', 'opalestate-pro'), - 'prev_next' => esc_html__('Previous/Next', 'opalestate-pro'), - 'numbers_and_prev_next' => esc_html__('Numbers', 'opalestate-pro') . ' + ' . esc_html__('Previous/Next', 'opalestate-pro'), - ] - ] - ); + $this->add_control( + 'pagination_page_limit', + [ + 'label' => esc_html__( 'Page Limit', 'opalestate-pro' ), + 'type' => Controls_Manager::NUMBER, + 'default' => '3', + 'condition' => [ + 'pagination' => 'show', + ], + ] + ); - $this->add_control( - 'pagination_page_limit', - [ - 'label' => esc_html__('Page Limit', 'opalestate-pro'), - 'default' => '5', - 'condition' => [ - 'pagination_type!' => '' - ], - ] - ); - - $this->add_control( - 'pagination_numbers_shorten', - [ - 'label' => esc_html__('Shorten', 'opalestate-pro'), - 'type' => Controls_Manager::SWITCHER, - 'default' => '', - 'condition' => [ - 'pagination_type' => [ - 'numbers', - 'numbers_and_prev_next', - ], - ], - ] - ); - - $this->add_control( - 'pagination_prev_label', - [ - 'label' => esc_html__('Previous Label', 'opalestate-pro'), - 'default' => esc_html__('« Previous', 'opalestate-pro'), - 'condition' => [ - 'pagination_type' => [ - 'prev_next', - 'numbers_and_prev_next', - ], - ], - ] - ); - - $this->add_control( - 'pagination_next_label', - [ - 'label' => esc_html__('Next Label', 'opalestate-pro'), - 'default' => esc_html__('Next »', 'opalestate-pro'), - 'condition' => [ - 'pagination_type' => [ - 'prev_next', - 'numbers_and_prev_next', - ], - ], - ] - ); - - $this->add_control( - 'pagination_align', - [ - 'label' => esc_html__('Alignment', 'opalestate-pro'), - 'type' => Controls_Manager::CHOOSE, - 'options' => [ - 'flex-start' => [ - 'title' => esc_html__('Left', 'opalestate-pro'), - 'icon' => 'fa fa-align-left', - ], - 'center' => [ - 'title' => esc_html__('Center', 'opalestate-pro'), - 'icon' => 'fa fa-align-center', - ], - 'flex-end' => [ - 'title' => esc_html__('Right', 'opalestate-pro'), - 'icon' => 'fa fa-align-right', - ], - ], - 'default' => 'flex-start', - 'selectors' => [ - '{{WRAPPER}} .pagination' => 'justify-content: {{VALUE}};', - ], - 'condition' => [ - 'pagination_type!' => '', - ], - ] - ); - - - $this->end_controls_section(); + $this->end_controls_section(); } public function get_post_categories() { - $list = array(); - return $list; + $list = []; + + return $list; } } diff --git a/inc/vendors/elementor/widgets/opalestate-agent-collection.php b/inc/vendors/elementor/widgets/opalestate-agent-collection.php index 1229ab4f..599b8372 100755 --- a/inc/vendors/elementor/widgets/opalestate-agent-collection.php +++ b/inc/vendors/elementor/widgets/opalestate-agent-collection.php @@ -32,15 +32,15 @@ class Opalestate_Agent_Collection_Elementor_Widget extends Opalestate_Elementor_ * * Used to set scripts dependencies required to run the widget. * - * @since 1.3.0 + * @return array Widget scripts dependencies. + * @since 1.3.0 * @access public * - * @return array Widget scripts dependencies. */ public function get_script_depends() { return [ 'jquery-slick' ]; } - + /** * Get widget title. * @@ -51,7 +51,7 @@ class Opalestate_Agent_Collection_Elementor_Widget extends Opalestate_Elementor_ * @return string Widget title. */ public function get_title() { - return esc_html__( 'Block: Agent Collection', 'opalestate-pro' ); + return esc_html__( 'Block: Agents Collection', 'opalestate-pro' ); } /** @@ -94,287 +94,210 @@ class Opalestate_Agent_Collection_Elementor_Widget extends Opalestate_Elementor_ 'label' => esc_html__( 'Agents Search Collection', 'opalestate-pro' ), ] ); - + + // $this->add_control( + // 'search_form', + // [ + // 'label' => esc_html__('Search Form', 'opalestate-pro'), + // 'type' => \Elementor\Controls_Manager::SELECT, + // 'default' => '', + // 'options' => array( + // '' => esc_html__( 'Advanded', 'opalestate-pro' ), + // 'address' => esc_html__( 'Search By Address', 'opalestate-pro' ), + // ) + // ] + // ); + $this->add_control( - 'search_form', - [ - 'label' => esc_html__('Search Form', 'opalestate-pro'), - 'type' => \Elementor\Controls_Manager::SELECT, - 'default' => '', - 'options' => array( - '' => esc_html__( 'Advanded', 'opalestate-pro' ), - 'address' => esc_html__( 'Search By Address', 'opalestate-pro' ), - ) + 'enable_sortable_bar', + [ + 'label' => esc_html__( 'Enable Sortable Bar', 'opalestate-pro' ), + 'type' => Controls_Manager::SWITCHER, ] ); - $this->add_control( - 'enable_sortable_bar', - [ - 'label' => esc_html__('Enable Sortable Bar', 'opalestate-pro'), - 'type' => Controls_Manager::SWITCHER, - ] - ); - $this->add_control( 'item_layout', - [ - 'label' => esc_html__('Item Layout', 'opalestate-pro'), - 'type' => \Elementor\Controls_Manager::SELECT, - 'default' => 'grid', - 'options' => array( - 'grid' => esc_html__( 'Grid', 'opalestate-pro' ), - 'list' => esc_html__( 'List', 'opalestate-pro' ), - ) + [ + 'label' => esc_html__( 'Item Layout', 'opalestate-pro' ), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 'grid', + 'options' => [ + 'grid' => esc_html__( 'Grid', 'opalestate-pro' ), + 'list' => esc_html__( 'List', 'opalestate-pro' ), + ], + ] + ); + $this->add_responsive_control( + 'column', + [ + 'label' => esc_html__( 'Columns', 'opalestate-pro' ), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => 3, + 'options' => [ 1 => 1, 2 => 2, 3 => 3, 4 => 4, 6 => 6 ], + 'prefix_class' => 'elementor-grid%s-', + 'condition' => [ + 'item_layout' => 'grid', + ], + ] ); - $this->add_responsive_control( - 'column', - [ - 'label' => esc_html__('Columns', 'opalestate-pro'), - 'type' => \Elementor\Controls_Manager::SELECT, - 'default' => 3, - 'options' => [1 => 1, 2 => 2, 3 => 3, 4 => 4, 6 => 6], - 'prefix_class' => 'elementor-grid%s-', - 'condition' => [ - 'item_layout' => 'grid' - ] - - ] - ); $this->add_control( - 'column_gap', - [ - 'label' => esc_html__( 'Columns Gap', 'opalestate-pro' ), - 'type' => Controls_Manager::SLIDER, - 'range' => [ - 'px' => [ - 'min' => 0, - 'max' => 100, - ], - ], - 'selectors' => [ - '{{WRAPPER}} .elementor-items-container' => 'grid-column-gap: {{SIZE}}{{UNIT}}' - - ], - 'condition' => [ - 'item_layout' => 'grid' - ] - ] - ); + 'column_gap', + [ + 'label' => esc_html__( 'Columns Gap', 'opalestate-pro' ), + 'type' => Controls_Manager::SLIDER, + 'range' => [ + 'px' => [ + 'min' => 0, + 'max' => 100, + ], + ], + 'selectors' => [ + '{{WRAPPER}} .elementor-items-container' => 'grid-column-gap: {{SIZE}}{{UNIT}}', - $this->add_control( - 'enable_carousel', - [ - 'label' => esc_html__('Enable', 'opalestate-pro'), - 'type' => Controls_Manager::SWITCHER, - ] - ); + ], + 'condition' => [ + 'item_layout' => 'grid', + ], + ] + ); + + $this->add_control( + 'enable_carousel', + [ + 'label' => esc_html__( 'Enable', 'opalestate-pro' ), + 'type' => Controls_Manager::SWITCHER, + ] + ); $this->end_controls_section(); - $this->add_slick_controls( array('enable_carousel' => 'yes') , ' .agent-slick-carousel ' ); + $this->add_slick_controls( [ 'enable_carousel' => 'yes' ], ' .agent-slick-carousel ' ); $this->start_controls_section( - 'section_query', - [ - 'label' => esc_html__('Query', 'opalestate-pro'), - 'tab' => Controls_Manager::TAB_CONTENT, - ] - ); + 'section_query', + [ + 'label' => esc_html__( 'Query', 'opalestate-pro' ), + 'tab' => Controls_Manager::TAB_CONTENT, + ] + ); - $this->add_control( - 'posts_per_page', - [ - 'label' => esc_html__('Posts Per Page', 'opalestate-pro'), - 'type' => Controls_Manager::NUMBER, - 'default' => 6, - ] - ); + $this->add_control( + 'posts_per_page', + [ + 'label' => esc_html__( 'Posts Per Page', 'opalestate-pro' ), + 'type' => Controls_Manager::NUMBER, + 'default' => 6, + ] + ); + $this->add_control( + 'advanced', + [ + 'label' => esc_html__( 'Advanced', 'opalestate-pro' ), + 'type' => Controls_Manager::HEADING, + ] + ); - $this->add_control( - 'advanced', - [ - 'label' => esc_html__('Advanced', 'opalestate-pro'), - 'type' => Controls_Manager::HEADING, - ] - ); + $this->add_control( + 'orderby', + [ + 'label' => esc_html__( 'Order By', 'opalestate-pro' ), + 'type' => Controls_Manager::SELECT, + 'default' => 'post_date', + 'options' => [ + 'post_date' => esc_html__( 'Date', 'opalestate-pro' ), + 'post_title' => esc_html__( 'Title', 'opalestate-pro' ), + 'menu_order' => esc_html__( 'Menu Order', 'opalestate-pro' ), + 'rand' => esc_html__( 'Random', 'opalestate-pro' ), + ], + ] + ); - $this->add_control( - 'orderby', - [ - 'label' => esc_html__('Order By', 'opalestate-pro'), - 'type' => Controls_Manager::SELECT, - 'default' => 'post_date', - 'options' => [ - 'post_date' => esc_html__('Date', 'opalestate-pro'), - 'post_title' => esc_html__('Title', 'opalestate-pro'), - 'menu_order' => esc_html__('Menu Order', 'opalestate-pro'), - 'rand' => esc_html__('Random', 'opalestate-pro'), - ], - ] - ); + $this->add_control( + 'order', + [ + 'label' => esc_html__( 'Order', 'opalestate-pro' ), + 'type' => Controls_Manager::SELECT, + 'default' => 'desc', + 'options' => [ + 'asc' => esc_html__( 'ASC', 'opalestate-pro' ), + 'desc' => esc_html__( 'DESC', 'opalestate-pro' ), + ], + ] + ); - $this->add_control( - 'order', - [ - 'label' => esc_html__('Order', 'opalestate-pro'), - 'type' => Controls_Manager::SELECT, - 'default' => 'desc', - 'options' => [ - 'asc' => esc_html__('ASC', 'opalestate-pro'), - 'desc' => esc_html__('DESC', 'opalestate-pro'), - ], - ] - ); + $this->add_control( + 'categories', + [ + 'label' => esc_html__( 'Categories', 'opalestate-pro' ), + 'type' => Controls_Manager::SELECT2, + 'options' => $this->get_post_categories(), + 'multiple' => true, + ] + ); - $this->add_control( - 'categories', - [ - 'label' => esc_html__('Categories', 'opalestate-pro'), - 'type' => Controls_Manager::SELECT2, - 'options' => $this->get_post_categories(), - 'multiple' => true, - ] - ); + $this->add_control( + 'cat_operator', + [ + 'label' => esc_html__( 'Category Operator', 'opalestate-pro' ), + 'type' => Controls_Manager::SELECT, + 'default' => 'IN', + 'options' => [ + 'AND' => esc_html__( 'AND', 'opalestate-pro' ), + 'IN' => esc_html__( 'IN', 'opalestate-pro' ), + 'NOT IN' => esc_html__( 'NOT IN', 'opalestate-pro' ), + ], + 'condition' => [ + 'categories!' => '', + ], + ] + ); - $this->add_control( - 'cat_operator', - [ - 'label' => esc_html__('Category Operator', 'opalestate-pro'), - 'type' => Controls_Manager::SELECT, - 'default' => 'IN', - 'options' => [ - 'AND' => esc_html__('AND', 'opalestate-pro'), - 'IN' => esc_html__('IN', 'opalestate-pro'), - 'NOT IN' => esc_html__('NOT IN', 'opalestate-pro'), - ], - 'condition' => [ - 'categories!' => '' - ], - ] - ); + $this->end_controls_section(); - $this->end_controls_section(); + $this->start_controls_section( + 'section_pagination', + [ + 'label' => esc_html__( 'Pagination', 'opalestate-pro' ), + ] + ); - $this->start_controls_section( - 'section_pagination', - [ - 'label' => esc_html__('Pagination', 'opalestate-pro'), - 'condition' => [ - 'enable_carousel!' => 'yes' - ], - ] - ); + $this->add_control( + 'pagination', + [ + 'label' => esc_html__( 'Pagination', 'opalestate-pro' ), + 'type' => Controls_Manager::SELECT, + 'default' => '', + 'options' => [ + '' => esc_html__( 'None', 'opalestate-pro' ), + 'show' => esc_html__( 'Show', 'opalestate-pro' ), + ], + ] + ); - $this->add_control( - 'pagination_type', - [ - 'label' => esc_html__('Pagination', 'opalestate-pro'), - 'type' => Controls_Manager::SELECT, - 'default' => '', - 'options' => [ - '' => esc_html__('None', 'opalestate-pro'), - 'numbers' => esc_html__('Numbers', 'opalestate-pro'), - 'prev_next' => esc_html__('Previous/Next', 'opalestate-pro'), - 'numbers_and_prev_next' => esc_html__('Numbers', 'opalestate-pro') . ' + ' . esc_html__('Previous/Next', 'opalestate-pro'), - ] - ] - ); + $this->add_control( + 'pagination_page_limit', + [ + 'label' => esc_html__( 'Page Limit', 'opalestate-pro' ), + 'type' => Controls_Manager::NUMBER, + 'default' => '3', + 'condition' => [ + 'pagination' => 'show', + ], + ] + ); - $this->add_control( - 'pagination_page_limit', - [ - 'label' => esc_html__('Page Limit', 'opalestate-pro'), - 'default' => '5', - 'condition' => [ - 'pagination_type!' => '' - ], - ] - ); - - $this->add_control( - 'pagination_numbers_shorten', - [ - 'label' => esc_html__('Shorten', 'opalestate-pro'), - 'type' => Controls_Manager::SWITCHER, - 'default' => '', - 'condition' => [ - 'pagination_type' => [ - 'numbers', - 'numbers_and_prev_next', - ], - ], - ] - ); - - $this->add_control( - 'pagination_prev_label', - [ - 'label' => esc_html__('Previous Label', 'opalestate-pro'), - 'default' => esc_html__('« Previous', 'opalestate-pro'), - 'condition' => [ - 'pagination_type' => [ - 'prev_next', - 'numbers_and_prev_next', - ], - ], - ] - ); - - $this->add_control( - 'pagination_next_label', - [ - 'label' => esc_html__('Next Label', 'opalestate-pro'), - 'default' => esc_html__('Next »', 'opalestate-pro'), - 'condition' => [ - 'pagination_type' => [ - 'prev_next', - 'numbers_and_prev_next', - ], - ], - ] - ); - - $this->add_control( - 'pagination_align', - [ - 'label' => esc_html__('Alignment', 'opalestate-pro'), - 'type' => Controls_Manager::CHOOSE, - 'options' => [ - 'flex-start' => [ - 'title' => esc_html__('Left', 'opalestate-pro'), - 'icon' => 'fa fa-align-left', - ], - 'center' => [ - 'title' => esc_html__('Center', 'opalestate-pro'), - 'icon' => 'fa fa-align-center', - ], - 'flex-end' => [ - 'title' => esc_html__('Right', 'opalestate-pro'), - 'icon' => 'fa fa-align-right', - ], - ], - 'default' => 'flex-start', - 'selectors' => [ - '{{WRAPPER}} .pagination' => 'justify-content: {{VALUE}};', - ], - 'condition' => [ - 'pagination_type!' => '', - ], - ] - ); - - - $this->end_controls_section(); + $this->end_controls_section(); } public function get_post_categories() { - $list = array(); - return $list; + $list = []; + + return $list; } } diff --git a/inc/vendors/elementor/widgets/opalestate-property-collection.php b/inc/vendors/elementor/widgets/opalestate-property-collection.php index 3c04e751..094955d5 100755 --- a/inc/vendors/elementor/widgets/opalestate-property-collection.php +++ b/inc/vendors/elementor/widgets/opalestate-property-collection.php @@ -316,9 +316,10 @@ class Opalestate_Property_collection_Elementor_Widget extends Opalestate_Element 'pagination_page_limit', [ 'label' => esc_html__( 'Page Limit', 'opalestate-pro' ), - 'default' => '5', + 'type' => Controls_Manager::NUMBER, + 'default' => '3', 'condition' => [ - 'pagination_type!' => '', + 'pagination' => 'show', ], ] ); diff --git a/opal-estate-pro.php b/opal-estate-pro.php index 14379f27..065443aa 100755 --- a/opal-estate-pro.php +++ b/opal-estate-pro.php @@ -3,7 +3,7 @@ * Plugin Name: Opal Estate Pro * Plugin URI: http://www.wpopal.com/product/opal-estate-wordpress-plugin/ * Description: Opal Real Estate Plugin is an ideal solution and brilliant choice for you to set up a professional estate website. - * Version: 1.1.3 + * Version: 1.1.4 * Author: WPOPAL * Author URI: http://www.wpopal.com * Requires at least: 4.6 @@ -151,7 +151,7 @@ if ( ! class_exists( 'OpalEstate' ) ) { */ public function __clone() { // Cloning instances of the class is forbidden - _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin’ huh?', 'opalestate-pro' ), '1.1.3' ); + _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin’ huh?', 'opalestate-pro' ), '1.1.4' ); } /** @@ -160,7 +160,7 @@ if ( ! class_exists( 'OpalEstate' ) ) { public function setup_constants() { // Plugin version if ( ! defined( 'OPALESTATE_VERSION' ) ) { - define( 'OPALESTATE_VERSION', '1.1.3' ); + define( 'OPALESTATE_VERSION', '1.1.4' ); } // Plugin Folder Path diff --git a/readme.txt b/readme.txt index 0c2e7cc5..d8820c97 100755 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Donate link: http://www.wpopal.com/product/opal-estate-wordpress-plugin/ Tags: estate, property, opalestate, house for rent, agency for lease, estate submission, agents estate property, property marketplace Requires at least: 4.6 Tested up to: 5.2.3 -Stable tag: 1.1.3 +Stable tag: 1.1.4 License: GPLv3 License URI: http://www.gnu.org/licenses/gpl-3.0.html @@ -153,6 +153,11 @@ This section describes how to install the plugin and get it working. * System tickets support 24/7 available : [free support](https://wpopal.ticksy.com/ "Visit the Plugin support Page") == Changelog == += 1.1.4 - 2019-10-17 = +* Fixes - Properties collection pagination. +* Fixes - Agents collection pagination. +* Fixes - Agencies collection pagination. + = 1.1.3 - 2019-10-15 = * Fixes - Submission. diff --git a/templates/elementor-templates/opalestate-agency-collection.php b/templates/elementor-templates/opalestate-agency-collection.php index 4ae9b95a..45d333f6 100755 --- a/templates/elementor-templates/opalestate-agency-collection.php +++ b/templates/elementor-templates/opalestate-agency-collection.php @@ -1,5 +1,6 @@ get_settings_for_display(); +extract( $settings ); $layout = $settings['item_layout']; $attrs = $this->get_render_attribute_string( 'wrapper-style' ); if ( isset( $_GET['display'] ) && $_GET['display'] == 'grid' ) { @@ -9,13 +10,20 @@ if ( isset( $_GET['display'] ) && $_GET['display'] == 'grid' ) { $layout = 'list'; $attrs = 'class="column-list"'; } -$onlyfeatured = 0; -if ( isset( $_GET['s_agents'] ) ) { - $query = Opalestate_Agency_Query::get_agencies( [ "posts_per_page" => $limit, 'paged' => $paged ], $onlyfeatured ); + +if ( is_front_page() ) { + $paged = ( get_query_var( 'page' ) ) ? get_query_var( 'page' ) : 1; } else { - $query = Opalestate_Agency_Query::get_search_agencies_query(); + $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1; } +$onlyfeatured = 0; +// if ( isset( $_GET['s_agents'] ) ) { + $query = Opalestate_Agency_Query::get_agencies( [ 'posts_per_page' => $posts_per_page, 'paged' => $paged ], $onlyfeatured ); +// } else { +// $query = Opalestate_Agency_Query::get_search_agencies_query(); +// } + $rowcls = apply_filters( 'opalestate_row_container_class', 'opal-row' ); ?> @@ -48,10 +56,8 @@ $rowcls = apply_filters( 'opalestate_row_container_class', 'opal-row' ); - max_num_pages ): ?> -
- max_num_pages ); ?> -
+ +
diff --git a/templates/elementor-templates/opalestate-agent-collection.php b/templates/elementor-templates/opalestate-agent-collection.php index d089ea11..7ec3096b 100755 --- a/templates/elementor-templates/opalestate-agent-collection.php +++ b/templates/elementor-templates/opalestate-agent-collection.php @@ -1,5 +1,6 @@ get_settings_for_display(); +extract( $settings ); $layout = $settings['item_layout']; $attrs = $this->get_render_attribute_string( 'wrapper-style' ); if ( isset( $_GET['display'] ) && $_GET['display'] == 'grid' ) { @@ -10,13 +11,19 @@ if ( isset( $_GET['display'] ) && $_GET['display'] == 'grid' ) { $attrs = 'class="column-list"'; } +if ( is_front_page() ) { + $paged = ( get_query_var( 'page' ) ) ? get_query_var( 'page' ) : 1; +} else { + $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1; +} + $onlyfeatured = 0; -if ( isset( $_GET['s_agents'] ) ) { - $query = Opalestate_Query::get_agents( [ "posts_per_page" => $limit, 'paged' => $paged ], $onlyfeatured ); -} else { - $query = OpalEstate_Search::get_search_agents_query(); -} +// if ( isset( $_GET['s_agents'] ) ) { + $query = Opalestate_Query::get_agents( [ "posts_per_page" => $posts_per_page, 'paged' => $paged ], $onlyfeatured ); +// } else { +// $query = OpalEstate_Search::get_search_agents_query(); +// } $form = $settings['search_form'] ? "search-agents-form-" . $settings['search_form'] : "search-agents-form"; $rowcls = apply_filters( 'opalestate_row_container_class', 'opal-row' ); @@ -51,10 +58,8 @@ $rowcls = apply_filters( 'opalestate_row_container_class', 'opal-row' );
- max_num_pages ): ?> -
- max_num_pages ); ?> -
+ +