Commit inicial - WordPress Análisis de Precios Unitarios

- WordPress core y plugins
- Tema Twenty Twenty-Four configurado
- Plugin allow-unfiltered-html.php simplificado
- .gitignore configurado para excluir wp-config.php y uploads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-11-03 21:04:30 -06:00
commit a22573bf0b
24068 changed files with 4993111 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
<?php
namespace Advanced_Ads_Pro\Rest_Api;
/**
* Admin UI for the REST API.
*/
class Admin_UI {
/**
* The Rest_Api object.
*
* @var Rest_Api
*/
private $rest_api;
/**
* Constructor.
*
* @param Rest_Api $rest_api The Rest_Api object.
*/
public function __construct( Rest_Api $rest_api ) {
$this->rest_api = $rest_api;
}
/**
* Register the settings field for the REST API module.
*
* @return void
*/
public function settings_init() {
add_settings_field(
'module-rest-api',
__( 'REST API', 'advanced-ads-pro' ),
[ $this, 'render_settings' ],
\Advanced_Ads_Pro::OPTION_KEY . '-settings',
\Advanced_Ads_Pro::OPTION_KEY . '_modules-enable'
);
}
/**
* Setup and render the REST API module setting.
*
* @return void
*/
public function render_settings() {
$module_enabled = $this->rest_api->is_enabled();
$option_key = \Advanced_Ads_Pro::OPTION_KEY . '[rest-api]';
require_once __DIR__ . '/../views/module-enable.php';
}
}

View File

@@ -0,0 +1,91 @@
<?php // phpcs:ignoreFile
namespace Advanced_Ads_Pro\Rest_Api;
/**
* REST API extension of the Ad class.
*/
class Ad {
private $ad = null;
/**
* Get the ad if ID is passed, otherwise the ad is new and empty.
*
* @throws Rest_Exception Throw an exception if the provided id is not an ad.
*
* @param Ad|WP_Post|int $ad Ad to init.
*/
public function __construct( $ad = 0 ) {
$this->ad = wp_advads_get_ad( $ad );
add_filter( 'advanced-ads-tracking-link-attributes', [ $this, 'filter_tracking_attributes' ], 10, 2 );
}
/**
* Return ad details for API response.
*
* @return array
*/
public function get_rest_response() {
return [
'ID' => $this->ad->get_id(),
'title' => $this->ad->get_title(),
'type' => $this->ad->get_type(),
'start_date' => get_post_datetime( $this->ad->get_id() )->getTimestamp(),
'expiration_date' => $this->ad->get_expiry_date(),
'content' => $this->prepare_rest_output(),
];
}
/**
* Parse the ad content according to ad type, but without adding any wrappers.
*
* @return string
*/
private function prepare_rest_output() {
$user_supplied_content = $this->ad->get_prop( 'change-ad.content', false );
if ( $user_supplied_content ) {
// output was provided by the user.
return $user_supplied_content;
}
// load ad type specific content filter.
$output = $this->ad->prepare_output();
// Remove superfluous whitespace
$output = str_replace( [ "\n", "\r", "\t" ], ' ', $output );
$output = preg_replace( '/\s+/', ' ', $output );
/**
* Allow filtering of the API ad markup.
*
* @var string $output The ad content.
* @var AD $this The current ad object.
*/
$output = (string) apply_filters( 'advanced-ads-rest-ad-content', $output, $this->ad );
return $output;
}
/**
* If tracking is active, filter the attributes to remove tracking-specific frontend attributes.
*
* @param array $attributes Keys are attribute names, values their respective values.
* @param Ad $ad Ad instance.
*
* @return array
*/
public function filter_tracking_attributes( array $attributes, Ad $ad ) {
if ( $this->ad->get_id() !== $ad->get_id() ) {
return $attributes;
}
unset(
$attributes['data-bid'],
$attributes['data-id'],
$attributes['class']
);
return $attributes;
}
}

View File

@@ -0,0 +1,51 @@
<?php // phpcs:ignoreFile
namespace Advanced_Ads_Pro\Rest_Api;
use WP_Error;
use AdvancedAds\Abstracts\Group as BaseGroup;
/**
* REST API extension for the base Group.
*/
class Group extends BaseGroup {
/**
* Get the group if ID is passed, otherwise the group is new and empty.
*
* @throws Rest_Exception Throw an exception if the provided id is not a group.
*
* @param Group|WP_Term|int $group Group to init.
*/
public function __construct( $group = 0 ) {
parent::__construct( $group, [] );
if ( 0 === $this->get_id() ) {
throw new Rest_Exception(
serialize(
new WP_Error(
'rest_post_invalid_id',
__( 'Invalid group ID.', 'advanced-ads-pro' ),
[ 'status' => 404 ]
)
)
);
}
}
/**
* Return group details for API response.
*
* @return array
*/
public function get_rest_response() {
$ad_ids = $this->get_ordered_ad_ids();
return [
'ID' => $this->get_id(),
'name' => $this->get_title(),
'type' => $this->get_type(),
'ads' => $ad_ids,
'ad_weights' => $this->get_ad_weights(),
];
}
}

View File

@@ -0,0 +1,65 @@
<?php // phpcs:ignoreFile
namespace Advanced_Ads_Pro\Rest_Api;
use AdvancedAds\Constants;
/**
* Extend the WP_Query to get ads suitable for REST API request.
*/
class Rest_Ads_Query extends \WP_Query {
/**
* Parse the user supplied query params and merge with defaults.
*
* @param array $query_params The $_GET query parameters.
*/
public function __construct( array $query_params ) {
parent::__construct( array_filter( array_merge(
Rest_Query_Params_Helper::setup_query_params( $query_params ),
[
'post_type' => Constants::POST_TYPE_AD,
'fields' => 'ids',
]
) ) );
$this->check_invalid_paging();
}
/**
* Check if the requested page offset exists.
*
* @return void
* @throws Rest_Exception Throw an exception if the provided page number is larger than the available pages.
*/
private function check_invalid_paging() {
if ( $this->found_posts > 0 || $this->query_vars['paged'] === 1 ) {
return;
}
// Out-of-bounds, run the query again without LIMIT for total count.
$page = $this->query_vars['paged'];
unset( $this->query['paged'] );
$this->query( $this->query );
if ( $page > $this->max_num_pages && $this->found_posts > 0 ) {
throw new Rest_Exception( serialize( new \WP_Error(
'rest_post_invalid_page_number',
// phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- we're re-using a core translation.
__( 'The page number requested is larger than the number of pages available.' ),
[ 'status' => 400 ]
) ) );
}
}
/**
* Map array of ad ids into array of \Advanced_Ads_Pro\Rest_Api\Ad arrays.
*
* @return array[]
*/
public function get_ads() {
return array_map(
function ( $ad_id ) {
return ( new Ad( $ad_id ) )->get_rest_response();
},
$this->posts
);
}
}

View File

@@ -0,0 +1,198 @@
<?php // phpcs:ignoreFile
namespace Advanced_Ads_Pro\Rest_Api;
/**
* Advanced Ads Rest API.
*/
class Rest_Api {
/**
* Injected instance of the current Advanced_Ads_Pro instance.
*
* @var \Advanced_Ads_Pro
*/
private $advanced_ads_pro;
/**
* Set the namespace for the endpoints.
*
* @const string
*/
const NS = 'advanced-ads/v1';
/**
* Constructor.
*
* @param \Advanced_Ads_Pro $advanced_ads_pro The current Advanced_Ads_Pro instance.
*/
public function __construct( \Advanced_Ads_Pro $advanced_ads_pro ) {
$this->advanced_ads_pro = $advanced_ads_pro;
}
/**
* Check if the REST API module is enabled.
*
* @return bool
*/
public function is_enabled() {
return ! empty( $this->advanced_ads_pro->get_options()['rest-api']['enabled'] );
}
/**
* Register all REST routes.
* Ad list, single ad. Group list, single group.
*
* @return void
*/
public function register_rest_routes() {
// register the main ad list
register_rest_route(
self::NS,
'/ads',
[
'methods' => 'GET',
'callback' => [ $this, 'get_ad_list' ],
'permission_callback' => '__return_true',
'args' => Rest_Query_Params_Helper::get_list_args(),
]
);
// register the endpoint to get an individual ad
register_rest_route(
self::NS,
'/ads/(?<id>\d+)',
[
'methods' => 'GET',
'callback' => [ $this, 'get_ad' ],
'args' => [
'id' => [
'validate_callback' => function( $param ) {
return is_numeric( $param );
},
],
],
'permission_callback' => '__return_true',
]
);
// register the endpoint to get ad groups
register_rest_route(
self::NS,
'/groups',
[
'methods' => 'GET',
'callback' => [ $this, 'get_group_list' ],
'permission_callback' => '__return_true',
'args' => Rest_Query_Params_Helper::get_list_args(),
]
);
// register the endpoint to get a single ad group
register_rest_route(
self::NS,
'/groups/(?<id>\d+)',
[
'methods' => 'GET',
'callback' => [ $this, 'get_group' ],
'args' => [
'id' => [
'validate_callback' => function( $param ) {
return is_numeric( $param );
},
],
],
'permission_callback' => '__return_true',
]
);
}
/**
* Get single ad callback.
* If the passed id is not an Ad, return a WP_Error.
*
* @param \WP_REST_Request $request The current request object.
*
* @return \WP_Error|\WP_REST_Response
*/
public function get_ad( \WP_REST_Request $request ) {
$id = (int) $request->get_param( 'id' );
try {
$ad = new Ad( $id );
} catch ( Rest_Exception $e ) {
return unserialize( $e->getMessage() );
}
/**
* Trigger an action when a single ad is requested via the API.
*
* @var Ad $ad The requested ad.
*/
do_action( 'advanced-ads-rest-ad-request', $ad );
return new \WP_REST_Response( $ad->get_rest_response() );
}
/**
* Get ad list callback.
* Return WP_Error if paging is incorrect.
* Set paging headers on successful request.
*
* @param \WP_REST_Request $request The current request object.
*
* @return \WP_Error|\WP_REST_Response
*/
public function get_ad_list( \WP_REST_Request $request ) {
try {
$ads = new Rest_Ads_Query( $request->get_query_params() );
} catch ( Rest_Exception $e ) {
return unserialize( $e->getMessage() );
}
return new \WP_REST_Response( $ads->get_ads(), 200, [
'X-WP-Total' => $ads->found_posts,
'X-WP-TotalPages' => $ads->max_num_pages,
] );
}
/**
* Get group list callback.
* Return WP_Error if paging is incorrect.
* Set paging headers on successful request.
*
* @param \WP_REST_Request $request The current request object.
*
* @return \WP_Error|\WP_REST_Response
*/
public function get_group_list( \WP_REST_Request $request ) {
try {
$groups = new Rest_Groups_Query( $request->get_query_params() );
} catch ( Rest_Exception $e ) {
return unserialize( $e->getMessage() );
}
return new \WP_REST_Response( $groups->get_groups(), 200, [
'X-WP-Total' => $groups->found_terms(),
'X-WP-TotalPages' => $groups->max_num_pages(),
] );
}
/**
* Get single group callback.
* Return WP_Error if passed id is not an Group.
*
* @param \WP_REST_Request $request The current request object.
*
* @return \WP_Error|\WP_REST_Response
*/
public function get_group( \WP_REST_Request $request ) {
try {
return new \WP_REST_Response(
( new Group( $request->get_param( 'id' ) ) )->get_rest_response()
);
} catch ( Rest_Exception $e ) {
return unserialize( $e->getMessage() );
}
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Advanced_Ads_Pro\Rest_Api;
/**
* Rest_Exception to extend built-in RuntimeException.
*/
class Rest_Exception extends \RuntimeException {
}

View File

@@ -0,0 +1,101 @@
<?php // phpcs:ignoreFile
namespace Advanced_Ads_Pro\Rest_Api;
use WP_Term_Query;
use AdvancedAds\Constants;
/**
* Extend \WP_Term_Query for REST API.
*/
class Rest_Groups_Query extends WP_Term_Query {
/**
* The amount of terms found for the current request.
*
* @var int
*/
private $found_terms;
/**
* The amount of max pages for the current request.
*
* @var int
*/
private $max_num_pages;
/**
* Constructor.
* Parse user request parameters.
* Get the amout of total items and pages.
* Run actual query.
*
* @param array $query_params The current user request parameters.
*/
public function __construct( array $query_params ) {
$query_params = array_merge(
Rest_Query_Params_Helper::setup_query_params( $query_params ),
[
'taxonomy' => Constants::TAXONOMY_GROUP,
'hide_empty' => false,
'hierarchical' => false,
]
);
$query_params['number'] = $query_params['posts_per_page'];
if ( isset( $query_params['paged'] ) ) {
$query_params['offset'] = ( $query_params['paged'] - 1 ) * $query_params['posts_per_page'];
}
parent::__construct();
$this->found_terms = $this->get_count( $query_params );
$this->query( $query_params );
$this->max_num_pages = (int) ceil( $this->found_terms / $this->query_vars['number'] );
}
/**
* Getter for found_terms.
*
* @return int
*/
public function found_terms() {
return $this->found_terms;
}
/**
* Getter for max_num_pages.
*
* @return int
*/
public function max_num_pages() {
return $this->max_num_pages;
}
/**
* Remove all limits and run current query to get the amount of total rows.
*
* @param array $query_params The current request parameters.
*
* @return int
*/
public function get_count( array $query_params ) {
$query_params['fields'] = 'count';
$query_params['number'] = '';
$query_params['offset'] = '';
return (int) $this->query( $query_params );
}
/**
* Map array of group ids into array of \Advanced_Ads_Pro\Rest_Api\Group response arrays.
*
* @return array[]
*/
public function get_groups() {
return array_map(
function ( $group_id ) {
return ( new Group( $group_id ) )->get_rest_response();
},
null !== $this->terms ? $this->terms : []
);
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace Advanced_Ads_Pro\Rest_Api;
/**
* Helper class to register and parse request parameters.
*/
class Rest_Query_Params_Helper {
/**
* Parse and populate default parameters.
*
* @param array $query_params The user-supplied request parameters.
*
* @return array
*/
public static function setup_query_params( array $query_params ) {
$parameters = [
'paged' => null,
'offset' => null,
'order' => null,
'orderby' => null,
'posts_per_page' => (int) ( isset( $query_params['per_page'] ) ? $query_params['per_page'] : get_option( 'posts_per_page' ) ),
];
// get paged request
if ( isset( $query_params['page'] ) && (int) $query_params['page'] > 1 ) {
$parameters['paged'] = (int) $query_params['page'];
}
// get offset
if ( isset( $query_params['offset'] ) ) {
$parameters['offset'] = (int) $query_params['offset'];
};
// get order
if ( isset( $query_params['order'] ) ) {
$order = strtoupper( $query_params['order'] );
if ( in_array( $order, [ 'ASC', 'DESC' ], true ) ) {
$parameters['order'] = $order;
}
}
// get order by
if ( isset( $query_params['orderby'] ) ) {
$parameters['orderby'] = $query_params['orderby'];
}
return $parameters;
}
/**
* Default arguments for list request. Isolated from WP core requests.
*
* phpcs:disable WordPress.WP.I18n.MissingArgDomain -- we're re-using core translations here.
*
* @return array[]
*/
public static function get_list_args() {
return [
'page' => [
'description' => __( 'Current page of the collection.' ),
'type' => 'integer',
'default' => 1,
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
'minimum' => 1,
],
'per_page' => [
'description' => __( 'Maximum number of items to be returned in result set.' ),
'type' => 'integer',
'default' => 10,
'minimum' => 1,
'maximum' => 100,
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
],
'offset' => [
'description' => __( 'Offset the result set by a specific number of items.' ),
'type' => 'integer',
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
],
'order' => [
'description' => __( 'Order sort attribute ascending or descending.' ),
'type' => 'string',
'default' => 'desc',
'enum' => [
'asc',
'desc',
],
'validate_callback' => 'rest_validate_request_arg',
],
'orderby' => [
'description' => __( 'Sort collection by object attribute.' ),
'type' => 'string',
'default' => 'date',
'enum' => [
'date',
'title',
'slug',
],
'validate_callback' => 'rest_validate_request_arg',
],
];
}
}

View File

@@ -0,0 +1,14 @@
<?php
$rest_api = new \Advanced_Ads_Pro\Rest_Api\Rest_Api( Advanced_Ads_Pro::get_instance() );
if ( is_admin() ) {
$rest_admin = new \Advanced_Ads_Pro\Rest_Api\Admin_UI( $rest_api );
add_action( 'advanced-ads-settings-init', [ $rest_admin, 'settings_init' ] );
}
if ( ! $rest_api->is_enabled() ) {
return;
}
add_action( 'rest_api_init', [ $rest_api, 'register_rest_routes' ] );

View File

@@ -0,0 +1,15 @@
<?php
/**
* Render the REST API module.
*
* @var bool $module_enabled
* @var string $option_key
*/
?>
<input name="<?php echo esc_attr( $option_key ); ?>[enabled]" id="advanced-ads-pro-rest-api-enabled" type="checkbox" value="1" <?php checked( $module_enabled ); ?> />
<label for="advanced-ads-pro-rest-api-enabled" class="description">
<?php esc_html_e( 'Activate module.', 'advanced-ads-pro' ); ?>
</label>
<a href="https://wpadvancedads.com/manual/rest-api/?utm_source=advanced-ads&utm_medium=link&utm_campaign=pro-rest-api-manual'; ?>" target="_blank" class="advads-manual-link"><?php esc_html_e( 'Manual', 'advanced-ads-pro' ); ?></a>