Jump to content
Latest News: (loading..)
frankl

Remove Product Schema from form on product_info.php

Recommended Posts

Currently product schema is spread throughout the product_info.php page as microdata, including in and out of the form. Using LD-JSON we can bring all the markup together as a header tag module, making it easy to update or alter.

 

I have attached a header tag module ht_product_schema.php, language file, and a revised product_info.php with microdata removed.

 

Just upload and turn on header tag module Modules -> Header Tags -> Install Module -> Product Schema meta Tags

 

(upload will overwrite existing product_info.php file!)

 

Test -> Structured Data Testing Tool

 

Thoughts?

ht_product_schema.zip


Let's make things easier for new osCommerce users http://forums.oscommerce.com/topic/402638-discussion-about-hard-coded-database-tables/?p=1718900  Getting there with osCommerce 2.4! :thumbsup:

Share this post


Link to post
Share on other sites

IIRC we were talking about something similar on a thread here or at git hub, a while back.  At that time, I got as far as testing something hard-coded into product_info, then gave up when reallife got in the way.  I should still have that hard-coded stuff somewhere, between your code and mine (and anyone else who wants to have an input, of course), perhaps we can come up with something to patch into Core.

 

Doing this would also simplify the upcoming modular product_info stuff...

 

Nice work @@frankl, thankyou for helping move osC forward all the time. 


This is a signature that appears on all my posts.  
IF YOU MAKE A POST REQUESTING HELP...please state the exact version
of osCommerce that you are using. THANKS

 
Get the latest current code (community-supported responsive 2.3.4.1BS Edge) here

 

Share this post


Link to post
Share on other sites

Ok, that took some hunting down, here is as far as I got (I've cleaned it up a little bit).

 

 

$schema_product = array("@context"                       =>  "http://schema.org",
                        "@type"                          => "Product",
                        "name"                           => "product name",
                        "image"                          => "product image url",
                        "description"                    => "product description",
                        "mpn"                            => "product model",
                        "gtin13"                         => "product gtin");
                        
$schema_product['offers'] = array("@type"                => "Offer",
                                  "priceCurrency"        => "CUR CODE",
                                  "price"                => "xx.yy",
                                  "priceValidUntil"      => "date",
                                  "availability"         => "date");
$schema_product['offers']['seller'] = array("@type"      => "Organization",
                                            "name"       => "store name");
                                                                              
$schema_product['manufacturer'] = array("@type"          => "Organization",
                                        "name"           => "product manufacturer");
                                        
$schema_product['aggregateRating'] = array("@type"       => "AggregateRating",
                                           "ratingValue" => "x",
                                           "reviewCount" => "y");

 

Putting the data into arrays like this, make it easier to add in (or remove) data programmatically as needed...


This is a signature that appears on all my posts.  
IF YOU MAKE A POST REQUESTING HELP...please state the exact version
of osCommerce that you are using. THANKS

 
Get the latest current code (community-supported responsive 2.3.4.1BS Edge) here

 

Share this post


Link to post
Share on other sites

OK...this UNTESTED. Anyone with mad-skillz feel free to hack at it.

This is attempt v1.0, and is up for testing, tweaking and most importantly comments of relevance.

 

Using Franks module. Replace the execute function as so:

 

    function execute() {
      global $PHP_SELF, $oscTemplate, $product_check, $languages_id, $currency, $currencies;

      if ($product_check['total'] > 0) {        
        $product_info_query = tep_db_query("select p.products_id, pd.products_name, pd.products_description, p.products_model, p.manufacturers_id, p.products_image, p.products_price, p.products_quantity, p.products_tax_class_id, p.products_date_available, p.products_gtin from products p, products_description pd where p.products_id = '" . (int)$_GET['products_id'] . "' and p.products_status = '1' and p.products_id = pd.products_id and pd.language_id = '" . (int)$languages_id . "'");

        if ( tep_db_num_rows($product_info_query) ) {
          $product_info = tep_db_fetch_array($product_info_query);  
          
          $products_image = $product_info['products_image'];
          $pi_query = tep_db_query("select image from products_images where products_id = '" . (int)$product_info['products_id'] . "' order by sort_order limit 1");
          if ( tep_db_num_rows($pi_query) ) {
            $pi = tep_db_fetch_array($pi_query);
            $products_image = $pi['image'];
          }
          
          $schema_product = array("@context"                         => "http://schema.org",
                                  "@type"                            => "Product",
                                  "name"                             => tep_db_output($product_info['products_name']),
                                  "image"                            => tep_href_link('images/' . $products_image, '', 'NONSSL', false, false),
                                  "description"                      => substr(trim(preg_replace('/\s\s+/', ' ', strip_tags($product_info['products_description']))), 0, 197) . '...');
                                  
          if (tep_not_null($product_info['products_model'])) {
            $schema_product['mpn']                                   = tep_db_output($product_info['products_model']);
          }
          
          if (tep_not_null($product_info['products_gtin'])) {
            $schema_product['gtin' .  MODULE_CONTENT_PRODUCT_INFO_GTIN_LENGTH] = tep_db_output(substr($product_info['products_gtin'], 0-MODULE_CONTENT_PRODUCT_INFO_GTIN_LENGTH));
          }
          
          $schema_product['offers'] = array("@type"                  => "Offer",
                                            "priceCurrency"          => $currency);
                                            
          if ($new_price = tep_get_products_special_price($product_info['products_id'])) {
            $products_price = $currencies->display_raw($new_price, tep_get_tax_rate($product_info['products_tax_class_id']));
          } else {
            $products_price = $currencies->display_raw($product_info['products_price'], tep_get_tax_rate($product_info['products_tax_class_id']));
          }          
          
          $schema_product['offers']['price'] = $products_price;
          
          $specials_expiry_query = tep_db_query("select expires_date from specials where products_id = '" . (int)$product_info['products_id'] . "' and status = 1");
if ( tep_db_num_rows($specials_expiry_query) ) {
            $specials_expiry = tep_db_fetch_array($specials_expiry_query); 
            
            $schema_product['offers']['priceValidUntil'] = $specials_expiry['expires_date'];
          }
          
          $availability = ( $product_info['products_quantity'] > 0 ) ? MODULE_HEADER_TAGS_PRODUCT_SCHEMA_TEXT_IN_STOCK : MODULE_HEADER_TAGS_PRODUCT_SCHEMA_TEXT_OUT_OF_STOCK;
          $schema_product['offers']['availability'] = $availability;          
                                            
          $schema_product['offers']['seller'] = array("@type"        => "Organization",
                                                      "name"         => STORE_NAME);
                                                                              
          $manufacturers_name_query = tep_db_query("select manufacturers_name from manufacturers where manufacturers_id='" . (int)$product_info['manufacturers_id'] . "'");
     if ( tep_db_num_rows($manufacturers_name_query) ) {
            $manufacturers_name = tep_db_fetch_array($manufacturers_name_query);
            $schema_product['manufacturer'] = array("@type"          => "Organization",
                                                    "name"           => tep_db_output($manufacturers_name['manufacturers_name']));
          }
                                        
          $average_query = tep_db_query("select AVG(r.reviews_rating) as average, COUNT(r.reviews_rating) as count from reviews r where r.products_id = '" . (int)$product_info['products_id'] . "' and r.reviews_status = 1");
     $average = tep_db_fetch_array($average_query);
     if ( $average['count'] > 0 ) {
            $schema_product['aggregateRating'] = array("@type"       => "AggregateRating",
                                                       "ratingValue" => (int)$average['average'],
                                                       "reviewCount" => (int)$average['count']);
          }
          
          // place a HOOK here
          // allows to update $schema_product array 
          // with no core changes...
          // untested, uncoded
          
          $data = json_encode($schema_product);

          $oscTemplate->addBlock('<script type="application/ld+json">' . $data . '</script>', $this->group);
        }
      }
    }
Why so much complicated code? It creates an array of data ready to be used.

It is so incredibly simple to manipulate arrays of data...

 

Imagine:

 

// place a HOOK here
A hook there which allows a user to simple upload a file into a directory. This new file adds in extra data to the array, let's say that in your shop you use the Core SEO which has the ability to have a different SEO name for a product. 1 hook file writing that data as https://schema.org/alternateName and upload...it's working. Set and forget.

 

PLEASE NOTE:

My code is UNTESTED and is subject to change or filed under B for Bin.

This is what I was working on months back, plugged into Franks Module.

Edited by burt
added global, updated sql for gtin

This is a signature that appears on all my posts.  
IF YOU MAKE A POST REQUESTING HELP...please state the exact version
of osCommerce that you are using. THANKS

 
Get the latest current code (community-supported responsive 2.3.4.1BS Edge) here

 

Share this post


Link to post
Share on other sites

It's also possible to use the javascript array values to add to the html page (via javascript). This is how the breadcrumb links are shown on the Live Sites website, source at:

 

https://github.com/haraldpdl/website_sites/blob/master/osCommerce/OM/Custom/Site/Sites/Module/Template/Widget/breadcrumb_nav/Controller.php

 

This can save multiple queries or loops.


:heart:, osCommerce

Share this post


Link to post
Share on other sites

I have the product info as json-ld in a header tag, but it uses a product(info) class.

I have tried this way to safe on queries.

Must say, I like the added flexibility that the integrated hook brings.


KEEP CALM AND CARRY ON

I do not use the responsive bootstrap version since i coded my responsive version earlier, but i have bought every 28d of code package to support burts effort and keep this forum alive (albeit more like on life support).

So if you are still here ? What are you waiting for ?!

 

Find the most frequent unique errors to fix:

grep "PHP" php_error_log.txt | sed "s/^.* PHP/PHP/g" |grep "line" |sort | uniq -c | sort -r > counterrors.txt

Share this post


Link to post
Share on other sites

OK...this UNTESTED. Anyone with mad-skillz feel free to hack at it.

This is attempt v1.0, and is up for testing, tweaking and most importantly comments of relevance.

 

Using Franks module. Replace the execute function as so:

 

    function execute() {
      global $PHP_SELF, $oscTemplate, $product_check, $languages_id, $currency, $currencies;

      if ($product_check['total'] > 0) {        
        $product_info_query = tep_db_query("select p.products_id, pd.products_name, pd.products_description, p.products_model, p.manufacturers_id, p.products_image, p.products_price, p.products_quantity, p.products_tax_class_id, p.products_date_available, p.products_gtin from products p, products_description pd where p.products_id = '" . (int)$_GET['products_id'] . "' and p.products_status = '1' and p.products_id = pd.products_id and pd.language_id = '" . (int)$languages_id . "'");

        if ( tep_db_num_rows($product_info_query) ) {
          $product_info = tep_db_fetch_array($product_info_query);  
          
          $products_image = $product_info['products_image'];
          $pi_query = tep_db_query("select image from products_images where products_id = '" . (int)$product_info['products_id'] . "' order by sort_order limit 1");
          if ( tep_db_num_rows($pi_query) ) {
            $pi = tep_db_fetch_array($pi_query);
            $products_image = $pi['image'];
          }
          
          $schema_product = array("@context"                         => "http://schema.org",
                                  "@type"                            => "Product",
                                  "name"                             => tep_db_output($product_info['products_name']),
                                  "image"                            => tep_href_link('images/' . $products_image, '', 'NONSSL', false, false),
                                  "description"                      => substr(trim(preg_replace('/\s\s+/', ' ', strip_tags($product_info['products_description']))), 0, 197) . '...');
                                  
          if (tep_not_null($product_info['products_model'])) {
            $schema_product['mpn']                                   = tep_db_output($product_info['products_model']);
          }
          
          if (tep_not_null($gtin['products_gtin'])) {
            $schema_product['gtin' .  MODULE_CONTENT_PRODUCT_INFO_GTIN_LENGTH] = tep_db_output(substr($product_info['products_gtin'], 0-MODULE_CONTENT_PRODUCT_INFO_GTIN_LENGTH));
          }
          
          $schema_product['offers'] = array("@type"                  => "Offer",
                                            "priceCurrency"          => $currency);
                                            
          if ($new_price = tep_get_products_special_price($product_info['products_id'])) {
            $products_price = $currencies->display_raw($new_price, tep_get_tax_rate($product_info['products_tax_class_id']));
          } else {
            $products_price = $currencies->display_raw($product_info['products_price'], tep_get_tax_rate($product_info['products_tax_class_id']));
          }          
          
          $schema_product['offers']['price'] = $products_price;
          
          $specials_expiry_query = tep_db_query("select expires_date from specials where products_id = '" . (int)$product_info['products_id'] . "' and status = 1");
if ( tep_db_num_rows($specials_expiry_query) ) {
            $specials_expiry = tep_db_fetch_array($specials_expiry_query); 
            
            $schema_product['offers']['priceValidUntil'] = $specials_expiry['expires_date'];
          }
          
          $availability = ( $product_info['products_quantity'] > 0 ) ? MODULE_HEADER_TAGS_PRODUCT_SCHEMA_TEXT_IN_STOCK : MODULE_HEADER_TAGS_PRODUCT_SCHEMA_TEXT_OUT_OF_STOCK;
          $schema_product['offers']['availability'] = $availability;          
                                            
          $schema_product['offers']['seller'] = array("@type"        => "Organization",
                                                      "name"         => STORE_NAME);
                                                                              
          $manufacturers_name_query = tep_db_query("select manufacturers_name from manufacturers where manufacturers_id='" . (int)$product_info['manufacturers_id'] . "'");
     if ( tep_db_num_rows($manufacturers_name_query) ) {
            $manufacturers_name = tep_db_fetch_array($manufacturers_name_query);
            $schema_product['manufacturer'] = array("@type"          => "Organization",
                                                    "name"           => tep_db_output($manufacturers_name['manufacturers_name']));
          }
                                        
          $average_query = tep_db_query("select AVG(r.reviews_rating) as average, COUNT(r.reviews_rating) as count from reviews r where r.products_id = '" . (int)$product_info['products_id'] . "' and r.reviews_status = 1");
     $average = tep_db_fetch_array($average_query);
     if ( $average['count'] > 0 ) {
            $schema_product['aggregateRating'] = array("@type"       => "AggregateRating",
                                                       "ratingValue" => (int)$average['average'],
                                                       "reviewCount" => (int)$average['count']);
          }
          
          // place a HOOK here
          // allows to update $schema_product array 
          // with no core changes...
          // untested, uncoded
          
          $data = json_encode($schema_product);

          $oscTemplate->addBlock('<script type="application/ld+json">' . $data . '</script>', $this->group);
        }
      }
    }
Why so much complicated code? It creates an array of data ready to be used.

It is so incredibly simple to manipulate arrays of data...

 

Imagine:

 

// place a HOOK here
A hook there which allows a user to simple upload a file into a directory. This new file adds in extra data to the array, let's say that in your shop you use the Core SEO which has the ability to have a different SEO name for a product. 1 hook file writing that data as https://schema.org/alternateName and upload...it's working. Set and forget.

 

PLEASE NOTE:

My code is UNTESTED and is subject to change or filed under B for Bin.

This is what I was working on months back, plugged into Franks Module.

 

 

 

A much better approach than my sloppy attempt! This code tested fine Gary, apart from the line

if (tep_not_null($gtin['products_gtin'])) {

which should be

if (tep_not_null($product_info['products_gtin'])) {

See results at Structured Data Testing Tool - Out of stock item, Product on special with expiry date, Product with GTIN, Product with reviews

 

This is what it looks like in the source code, I wasn't sure if it would work all run together like this, but it vaidates correctly :)

<script type="application/ld+json">{"@context":"http:\/\/schema.org","@type":"Product","name":"Matrox G200 MMS","image":"https:\/\/www.tonerpak.com.au\/edge3\/images\/matrox\/mg200mms.gif","description":"Reinforcing its position as a multi-monitor trailblazer, Matrox Graphics Inc. has once again developed the most flexible and highly advanced solution in the industry. Introducing the new Matrox G20...","mpn":"MG200MMS","gtin13":"2345678910111","offers":{"@type":"Offer","priceCurrency":"USD","price":"299.99","availability":"http:\/\/schema.org\/OutOfStock","seller":{"@type":"Organization","name":"TestEdge2"}},"manufacturer":{"@type":"Organization","name":"Matrox"}}</script>

Let's make things easier for new osCommerce users http://forums.oscommerce.com/topic/402638-discussion-about-hard-coded-database-tables/?p=1718900  Getting there with osCommerce 2.4! :thumbsup:

Share this post


Link to post
Share on other sites

This could be the perfect opportunity to finally get a product class integrated into core...

This module could be the first use of it.

 

Thoughts?


This is a signature that appears on all my posts.  
IF YOU MAKE A POST REQUESTING HELP...please state the exact version
of osCommerce that you are using. THANKS

 
Get the latest current code (community-supported responsive 2.3.4.1BS Edge) here

 

Share this post


Link to post
Share on other sites

I'll liaise with the team to find out what plans are in store for 2.4.

Would be good to have the same class in both 2.4 and Community 234r


This is a signature that appears on all my posts.  
IF YOU MAKE A POST REQUESTING HELP...please state the exact version
of osCommerce that you are using. THANKS

 
Get the latest current code (community-supported responsive 2.3.4.1BS Edge) here

 

Share this post


Link to post
Share on other sites

@@burt

 

 

Yes :) - Could be of great use. I think @@wHiTeHaT allready backported V3.0 product class ... Can not find it right now ...

 

@@wHiTeHaT - wondering if you can remember doing this??


This is a signature that appears on all my posts.  
IF YOU MAKE A POST REQUESTING HELP...please state the exact version
of osCommerce that you are using. THANKS

 
Get the latest current code (community-supported responsive 2.3.4.1BS Edge) here

 

Share this post


Link to post
Share on other sites

I don't think we ever came to a consensus regarding a product_info class that is workable and simple...

I'll take a closer look at 2.4 and see what is being done there regarding a product class and report back.


This is a signature that appears on all my posts.  
IF YOU MAKE A POST REQUESTING HELP...please state the exact version
of osCommerce that you are using. THANKS

 
Get the latest current code (community-supported responsive 2.3.4.1BS Edge) here

 

Share this post


Link to post
Share on other sites

This is a signature that appears on all my posts.  
IF YOU MAKE A POST REQUESTING HELP...please state the exact version
of osCommerce that you are using. THANKS

 
Get the latest current code (community-supported responsive 2.3.4.1BS Edge) here

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×