Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rss: Refactor RSS display logic for late escaping #27

Merged
merged 1 commit into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions admin/smaily-admin.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public function enqueue_scripts() {
$this->plugin_name,
'var smaily_settings = ' . wp_json_encode(
array(
'rss_feed_url' => Smaily_WC\Data_Handler::make_rss_feed_url(),
'rss_feed_url' => Smaily_WC\Rss::make_rss_feed_url(),
)
) . ';',
'before'
Expand Down Expand Up @@ -179,7 +179,6 @@ public function smaily_subscription_widget_init() {
*
*/
public function smaily_admin_save() {

// Ensure user has necessary permissions.
if ( ! current_user_can( 'manage_options' ) ) {
echo wp_json_encode(
Expand Down
2 changes: 1 addition & 1 deletion admin/template/smaily-admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
$rss_limit = $this->settings['woocommerce']['rss_limit'];
$rss_order_by = $this->settings['woocommerce']['rss_order_by'];
$rss_order = $this->settings['woocommerce']['rss_order'];
$rss_feed_url = Smaily_WC\Data_Handler::make_rss_feed_url( $rss_category, $rss_limit, $rss_order_by, $rss_order );
$rss_feed_url = Smaily_WC\Rss::make_rss_feed_url( $rss_category, $rss_limit, $rss_order_by, $rss_order );
$cat_args = array(
'taxonomy' => 'product_cat',
'orderby' => 'name',
Expand Down
52 changes: 38 additions & 14 deletions public/template/smaily-rss-feed.php
Original file line number Diff line number Diff line change
@@ -1,22 +1,46 @@
<?php
/**
* Generates RSS-feed based on url-vars or gets last 50 products updated.
*/

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

/**
* Generates RSS-feed based on url-vars or gets last 50 products updated.
*/
$category = sanitize_text_field( get_query_var( 'category' ) );
$limit = (int) sanitize_text_field( get_query_var( 'limit' ) );
$order_by = sanitize_text_field( get_query_var( 'order_by' ) );
$rss_order = sanitize_text_field( get_query_var( 'order' ) );
$currencysymbol = get_woocommerce_currency_symbol();

// Get variables from url.
$category = sanitize_text_field( get_query_var( 'category' ) );
$limit = (int) sanitize_text_field( get_query_var( 'limit' ) );
$order_by = sanitize_text_field( get_query_var( 'order_by' ) );
$rss_order = sanitize_text_field( get_query_var( 'order' ) );
// Generate RSS.feed. If no limit provided generates 50 products.
if ( $limit === 0 ) {
Smaily_WC\Data_Handler::generate_rss_feed( $category, 50, $order_by, $rss_order );
} else {
Smaily_WC\Data_Handler::generate_rss_feed( $category, $limit, $order_by, $rss_order );
}
// Default to 50 products.
$limit = $limit === 0 ? 50 : $limit;
$items = Smaily_WC\Rss::list_rss_feed_items( $category, $limit, $order_by, $order );

header( 'Content-Type: application/xml' );
?>

<rss xmlns:smly="https://sendsmaily.net/schema/editor/rss.xsd" version="2.0">
<channel>
<title><![CDATA[Store]]></title>
<link><![CDATA[<?php echo esc_url( get_site_url() ); ?>]]></link>
<description><![CDATA[Product Feed]]></description>
<lastBuildDate><![CDATA[<?php echo esc_html( wp_date( 'D, d M Y H:i:s' ) ); ?>]]></lastBuildDate>
<?php foreach ( $items as $item ) : ?>
<item>
<title><![CDATA[<?php echo esc_html( $item['title'] ); ?>]]></title>
<link><![CDATA[<?php echo esc_url( $item['url'] ); ?>]]></link>
<guid isPermaLink="True"><![CDATA[<?php echo esc_url( $item['url'] ); ?>]]></guid>
<pubDate><![CDATA[<?php echo esc_html( $item['created_at'] ); ?>]]></pubDate>
<description><![CDATA[<?php echo esc_html( $item['description'] ); ?>]]></description>
<enclosure url="<?php echo esc_html( $item['enclosure_url'] ); ?>"/>
<smly:price><![CDATA[<?php echo esc_html( number_format( floatval( $item['current_price'] ), 2, '.', ',' ) . html_entity_decode( $currencysymbol, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ) ); ?>]]></smly:price>
<?php if ( $item['discount'] > 0 ) : ?>
<smly:old_price><![CDATA[<?php echo esc_html( number_format( floatval( $item['regular_price'] ), 2, '.', ',' ) . html_entity_decode( $currencysymbol, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ) ); ?>]]></smly:old_price>
<smly:discount><![CDATA[-<?php echo esc_html( $item['discount'] ); ?>%]]></smly:discount>
<?php endif ?>
</item>
<?php endforeach ?>
</channel>
</rss>
118 changes: 3 additions & 115 deletions woocommerce/data-handler.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,84 +3,9 @@
namespace Smaily_WC;

/**
* Handles woocommerce related data retrieval
* Handles WooCommerce related data retrieval
*/
class Data_Handler {


/**
* Generates RSS-feed based on products in WooCommerce store.
*
* @param string $category Filter by products category.
* @param integer $limit Default value 50.
* @return void $rss Rss-feed for Smaily template.
*/
public static function generate_rss_feed( $category, $limit, $order_by, $order ) {
$products = self::get_products( $category, $limit, $order_by, $order );
$base_url = get_site_url();
$currencysymbol = get_woocommerce_currency_symbol();
$items = array();
foreach ( $products as $prod ) {
if ( function_exists( 'wc_get_product' ) ) {
$product = wc_get_product( $prod->get_id() );
} else {
$product = new \WC_Product( $prod->get_id() );
}

$price = floatval( $product->get_price() );
$price = number_format( floatval( $price ), 2, '.', ',' ) . html_entity_decode( $currencysymbol, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 );

$discount = 0;
// Get product price when on sale.
if ( $product->is_on_sale() ) {
// Regular price.
$regular_price = (float) $product->get_regular_price();
if ( $regular_price > 0 ) {
// Active price (the "Sale price" when on-sale).
$sale_price = (float) $product->get_price();
$saving_price = $regular_price - $sale_price;
// Discount precentage.
$discount = round( 100 - ( $sale_price / $regular_price * 100 ), 2 );
}
// Format price and add currency symbol.
$regular_price = number_format( floatval( $regular_price ), 2, '.', ',' ) . html_entity_decode( $currencysymbol, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 );
}

$url = get_permalink( $prod->get_id() );
$image = wp_get_attachment_image_src( get_post_thumbnail_id( $prod->get_id() ), 'single-post-thumbnail' );

$image = $image[0] ?? '';
$create_time = strtotime( $prod->get_date_created() );
$price_fields = '';
if ( $discount > 0 ) {
$price_fields = '
<smly:old_price>' . esc_attr( $regular_price ) . '</smly:old_price>
<smly:discount>-' . esc_attr( $discount ) . '%</smly:discount>';
}
// Parse image to form element.
$description = do_shortcode( $prod->get_description() );

$items[] = '<item>
<title><![CDATA[' . $prod->get_title() . ']]></title>
<link>' . esc_url( $url ) . '</link>
<guid isPermaLink="True">' . esc_url( $url ) . '</guid>
<pubDate>' . wp_date( 'D, d M Y H:i:s', $create_time ) . '</pubDate>
<description><![CDATA[' . $description . ']]></description>
<enclosure url="' . esc_url( $image ) . '" />
<smly:price>' . esc_attr( $price ) . '</smly:price>' . $price_fields . '
</item>
';
}
$rss = '<?xml version="1.0" encoding="utf-8"?><rss xmlns:smly="https://sendsmaily.net/schema/editor/rss.xsd" version="2.0"><channel><title>Store</title><link>' . esc_url( $base_url ) . '</link><description>Product Feed</description><lastBuildDate>' . wp_date( 'D, d M Y H:i:s' ) . '</lastBuildDate>';
$rss .= implode( ' ', $items );
$rss .= '</channel></rss>';
header( 'Content-Type: application/xml' );

// All values escaped before.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $rss;
}

/**
* Get published products from WooCommerce database.
*
Expand Down Expand Up @@ -110,8 +35,8 @@ public static function get_products( $category, $limit, $order_by, $order ) {
if ( ! empty( $category ) ) {
$product['category'] = array( $category );
}
$wprod = wc_get_products( $product );
return $wprod;

return wc_get_products( $product );
}

/**
Expand Down Expand Up @@ -176,41 +101,4 @@ public static function get_user_data( $user_id, array $options ) {

return $user_sync_data;
}

/**
* Get Product RSS Feed URL.
*
* @param string $rss_category Category slug.
* @param int $rss_limit Limit of products.
* @param string $rss_order_by Order products by.
* @param string $rss_order ASC/DESC order
* @return string
*/
public static function make_rss_feed_url( $rss_category = null, $rss_limit = null, $rss_order_by = null, $rss_order = null ) {
global $wp_rewrite;

$site_url = get_site_url( null, 'smaily-rss-feed' );
$parameters = array();

if ( isset( $rss_category ) && $rss_category !== '' ) {
$parameters['category'] = $rss_category;
}
if ( isset( $rss_limit ) ) {
$parameters['limit'] = $rss_limit;
}
if ( isset( $rss_order_by ) && $rss_order_by !== 'none' ) {
$parameters['order_by'] = $rss_order_by;
}
if ( isset( $rss_order ) && $rss_order_by !== 'none' && $rss_order_by !== 'rand' ) {
$parameters['order'] = $rss_order;
}

// Handle URL when permalinks have not been enabled.
if ( $wp_rewrite->using_permalinks() === false ) {
$site_url = get_site_url();
$parameters['smaily-rss-feed'] = 'true';
}

return add_query_arg( $parameters, $site_url );
}
}
126 changes: 126 additions & 0 deletions woocommerce/rss.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,49 @@

namespace Smaily_WC;

use Smaily_WC\Data_Handler;
use WC_Product;

/**
* Handles RSS generation for Smaily newsletter
*/
class Rss {
/**
* Get Product RSS Feed URL.
*
* @param string $rss_category Category slug.
* @param int $rss_limit Limit of products.
* @param string $rss_order_by Order products by.
* @param string $rss_order ASC/DESC order
* @return string
*/
public static function make_rss_feed_url( $rss_category = null, $rss_limit = null, $rss_order_by = null, $rss_order = null ) {
global $wp_rewrite;

$site_url = get_site_url( null, 'smaily-rss-feed' );
$parameters = array();

if ( isset( $rss_category ) && $rss_category !== '' ) {
$parameters['category'] = $rss_category;
}
if ( isset( $rss_limit ) ) {
$parameters['limit'] = $rss_limit;
}
if ( isset( $rss_order_by ) && $rss_order_by !== 'none' ) {
$parameters['order_by'] = $rss_order_by;
}
if ( isset( $rss_order ) && $rss_order_by !== 'none' && $rss_order_by !== 'rand' ) {
$parameters['order'] = $rss_order;
}

// Handle URL when permalinks have not been enabled.
if ( $wp_rewrite->using_permalinks() === false ) {
$site_url = get_site_url();
$parameters['smaily-rss-feed'] = 'true';
}

return add_query_arg( $parameters, $site_url );
}

/**
* Rewrite rule for url-handling
Expand Down Expand Up @@ -67,4 +105,92 @@ public function maybe_flush_rewrite_rules() {
delete_option( 'smaily_flush_rewrite_rules' );
}
}

/**
* List store products as RSS feed items.
*
* @param string $category
* @param int $limit
* @param string $order_by
* @param string $order
* @return array{created_at: string, current_price: string, description: string, discount: float, enclosure_url: string, regular_price: string, title: string, url: string}
*/
public static function list_rss_feed_items( $category, $limit, $order_by, $order ) {
$products = Data_Handler::get_products( $category, $limit, $order_by, $order );
$items = array();
foreach ( $products as $prod ) {
$product = function_exists( 'wc_get_product' ) ? wc_get_product( $prod->get_id() ) : new WC_Product( $prod->get_id() );

if ( $product === false || $product === null ) {
continue;
}

$current_price = $product->get_price();
$regular_price = $product->get_regular_price();
$url = get_permalink( $product->get_id() );

if ( $url === false ) {
continue;
}

$rss_feed_item = array(
'current_price' => $current_price,
'regular_price' => $product->is_on_sale() ? $regular_price : $current_price,
'discount' => self::calculate_discount( floatval( $current_price ), floatval( $regular_price ) ),
'url' => $url,
'title' => $product->get_title(),
'created_at' => $product->get_date_created()->date_i18n( 'D, d M Y H:i:s' ),
'enclosure_url' => self::get_product_image_url( $product->get_id() ),
'description' => do_shortcode( $product->get_description() ),
);

$items[] = $rss_feed_item;
}

return $items;
}

/**
* Calculates discount percentage between the current price and the regular price.
*
* @param float $current_price
* @param float $regular_price
* @return float
*/
private static function calculate_discount( $current_price, $regular_price ) {
if ( $current_price > $regular_price ) {
return 0.0;
}

if ( $regular_price > 0 ) {
return round( 100 - ( $current_price / $regular_price * 100 ), 2 );
}

return 0.0;
}

/**
* Get the thumbnail image URL for the product.
*
* @param int $product_id
* @return string
*/
private static function get_product_image_url( $product_id ) {
$thumbnail_id = get_post_thumbnail_id( $product_id );

if ( $thumbnail_id === false ) {
return '';
}

$image_data = wp_get_attachment_image_src(
$thumbnail_id,
'single-post-thumbnail'
);

if ( $image_data === false ) {
return '';
}

return $image_data[0];
}
}
Loading