Jump to content

[1.6.x] Link to other products having the same feature value. Almost there...


Rulian

Recommended Posts

My products (music records) have 2 kind of features: Artist and Label.

 

For every product, may it be displayed in the product list, or on the product detail page, I'd like to add 2 links pointing to a list containing every other product with the same Artist (1st link) and the same label (2nd link).

 

Something like:

 

Live in Sausalito '73                      $6.50

Artist: Bob Marley and the Wailers  <== link to all records by Bob Marley

Label: Tuff Gong Records               <== link to all records published by Tuff Gong

 

 

Work already done:

 

1A) I created a new category "catalog" containing all the products.

1B) I enabled the layered navigation, which is the only way I found to link to products with the same feature.

 

So now, the list to all Bob Marley's records is something like:

http://mysite.com/619-catalog#/artist-bob_marley

3) I followed Nemo's tutorial to display features in the product list.

4) Found out (at least I think so) so in order to convert a feature value to its URL friendly version, I need to use

Tools::link_rewrite($filter_name)

Where I'm stuck:

 

Now, I'd like to make the displayed artist name a link.

Still, I don't know where to begin with...

Looks like the only php file calling product-list.tpl is blocklayered.php, is this where I should add a function adding a 'artist_URL' and 'label_URL' attributes to the products fetched by getProducts(...) ?

 

 

 

 

 

 

Edited by Ruliane (see edit history)
Link to comment
Share on other sites

Manufacturers and Suppliers, renamed to Artists and Labels, could be another option.

 

Thank you for this workaround.

That's the first way I tried to handle the issue, but there's actually 3 features (artist, label and "riddim") I need to link to.

 

Furthermore, since there's about 22.000 products, and thousands of artists/labels, the layered navigation just crash my server (looks like it also freeze firefox).

Now I'm looking at how the manufacturer/supplier modules are implemented in order to code something similar, leaving the memory hog layered navigation behind...

Link to comment
Share on other sites

Now I'm looking at how the manufacturer/supplier modules are implemented in order to code something similar, leaving the memory hog layered navigation behind...

A product can have only one manufacturer but more than one supplier. The consequence is of course that the code for suppliers is a lot more complicated.

Link to comment
Share on other sites

OK, I solved my problem.

I ended up coding a lot of stuff, but looks like it works in a somehow efficient way.

For the desperate guy who could have the same issue, here's what I did:

 

1) Folowwing this great tutorial: http://nemops.com/creating-new-pages-in-prestashop/#.Vd7dYJdmzuN

I coded 3 new pages (ie.3 controllers)  within a module to keep the code tidy.

Each page (one by feature: artist, label and riddim) is a list of products with the same feature_value.

 

What I'll remember from this tutorial ?

 

If you already had a look a the official Prestashop documentation about this, and consequently tried to shoot at your head, I won’t blame you.

 

2) Since we need to pass an id_feature_value as an URL parameter (SEO friendly) in order to populate my product list, I followed this thread: http://stackoverflow.com/questions/23659444/prestashop-module-seo-url-and-parameters

 

3) Now we have a page which can display products and an ID.

We need to override /classes/Product.php in order to add a function getProductsByIDfeatureValue(...) which is a copy of GetProducts, with an extra parameter: id_feature_value, and an extra JOIN on it in the SQL query retrieving the products.

 

Now it's time to take a pause, grab a beer, and do something refreshing like wondering if the prestashop B.O achievements can be linked to your steam account, or maybe load testing your webserver by trying a CSV import...

 

4) But we still need to create links to these pages in our 'classic' product list.

So I displayed the feature values following this tutorial: http://nemops.com/features-to-product-list-prestashop-16/#.Vd7hupdmzuP

and made them hyperlinks instead of simple text.

 

Since we need to access the id_feature_value in the template file, we need to override the public static function getFrontFeaturesStatic($id_lang, $id_product) in Product.php in order to add the id_feature_value as a new "column" from the result of the query.

 

I also went the extra mile and used

Tools::link_rewrite($row['value']);

in order to generate clean URLs.

 

And... that's about it.

 

To the poor desperate soul needing such a feature for his shop, I hope these few lines would give you some hope.

 

 

 

 

 

 

 

  • Like 1
Link to comment
Share on other sites

  • 1 month later...

hi  Ruliane  excelent !

you can post all your code please ?

 

No sorry, but you can download my module "Feature Linker 2015", there's a free trial version where you can only link to one feature and it's displayed with a black font on a black background.

 

If you want to link to multiple features and choose where to hook the module, you can buy FeatureLinker 2015 Pro for only 39.99$

thMAB0L.png

 

Ah ah, just kidding !

Imagine if dev start acting like we're in some sort of competition, instead of a community, selling you code snippets originally written to fix some weirdo PS default behavior !

 

Oh wait...

 

Anyway, I'll try to write down all the stuff I did as soon as possible, stay tuned !

Edited by Ruliane (see edit history)
Link to comment
Share on other sites

I asked my client, and I'm sorry I can't give an archive of the module I coded since there's some personal info inside.
Anyway, that's how I managed to display a block of links for each product in the product list, pointing to products having the same feature_value:

Let's say you created a feature "artist" in you backoffice.
For every other feature you want to link to, you'll just have to follow the same process.

To be clear:
Feature example: "Artist"
Feature value example: "Bob Dylan"

First, when we display a category, we want each and every product object to 'know' 2 new things:
- the IDs of feature values it have.
- an URL friendly value, which is gonna be used to link to the product list of products sharing the same feature value

To achieve such a thing you have to override the Product class.

Actually, we'll override the function called to retrieve the products info: getFrontFeaturesStatic.

Create a /override/classes/Product.php file and it should look like this.

 


<?php

class Product extends ProductCore{





    public static function getFrontFeaturesStatic($id_lang, $id_product)

    {

        if (!Feature::isFeatureActive())

            return array();

        if (!array_key_exists($id_product.'-'.$id_lang, self::$_frontFeaturesCache))

        {

            $tempArray = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('

                SELECT name, value, pf.id_feature, fvl.id_feature_value

                FROM '._DB_PREFIX_.'feature_product pf

                LEFT JOIN '._DB_PREFIX_.'feature_lang fl ON (fl.id_feature = pf.id_feature AND fl.id_lang = '.(int)$id_lang.')

                LEFT JOIN '._DB_PREFIX_.'feature_value_lang fvl ON (fvl.id_feature_value = pf.id_feature_value AND fvl.id_lang = '.(int)$id_lang.')

                LEFT JOIN '._DB_PREFIX_.'feature f ON (f.id_feature = pf.id_feature AND fl.id_lang = '.(int)$id_lang.')

                '.Shop::addSqlAssociation('feature', 'f').'

                WHERE pf.id_product = '.(int)$id_product.'

                ORDER BY f.position ASC'

            );



            foreach ($tempArray as &$row)

                $row['url_friendly_value'] = Tools::link_rewrite($row['value']);



            self::$_frontFeaturesCache[$id_product.'-'.$id_lang] = $tempArray;

        }

        return self::$_frontFeaturesCache[$id_product.'-'.$id_lang];

    }

}

?>    

Now we'd like to display these new elements we got from the DB.
Let's display them right under the product name.
Go to /themes/yourtemplate/product-list.tpl
and after


    <div class="right-block">

        <h5 itemprop="name">

            {if isset($product.pack_quantity) && $product.pack_quantity}{$product.pack_quantity|intval|cat:' x '}{/if}

            <a class="product-name" href="{$product.link|escape:'html':'UTF-8'}" title="{$product.name|escape:'html':'UTF-8'}" itemprop="url" >

                {$product.name|truncate:45:'...'|escape:'html':'UTF-8'}

            </a>

        </h5>

You'll put something like this:
 


    {if isset($product.features)}

        <div class="features">

        {foreach from=$product.features item=feature}

            <div>

            {if $feature.id_feature == '10'} {*This part is to handle the 3 different features I link to*}

                {assign var="link_root" value="labels"}

            {elseif $feature.id_feature == '11'}

                {assign var="link_root" value="artist"}

            {elseif $feature.id_feature == '12'}

                {assign var="link_root" value="riddim"}                            

            {/if}

            {$feature.name}: <a href="{$base_dir}{$link_root}/{$feature.id_feature_value}_{$feature.url_friendly_value}" title="{$feature.value|escape:'html':'UTF-8'}">{$feature.value}</a>

            </div>

        {/foreach}

        </div>

    {/if}

Now we display nice links, but they lead to nothing existing (yet)!

We'll have to create new pages to link to.

Create a new empty module, using the Project Files given by Nemo on his blog:
http://nemops.com/creating-new-pages-in-prestashop/#.Vh4L3ytmzuO

2)In the new module, I created 3 new pages (ie. controllers) but there must be a way to create only one for all the features, in order to avoid code duplication.
At this point, I feel kind of lazy so one feature = one page.

So, you create a new file under /modules/yournewmodule/controllers/front/artist.php and it should look like this:
 


<?php

Class yournewmoduleArtistModuleFrontController extends ModuleFrontController

{

    public function init()

    {

        $this->page_name = 'artist';

        parent::init();

    }

    

    public function initContent()

    {

        parent::initContent();

        

        $short_code = Tools::getValue('short_code');

        $arr = explode("_", $short_code);

        $id_feature_value = $arr[0];

        

        $products_partial = Product::getProductsByIDFeatureValue($this->context->language->id,

            0,

            5,

            'name',

            'asc',

            $id_feature_value);

        $products = Product::getProductsProperties($this->context->language->id, $products_partial);

    

        $this->context->smarty->assign(array(

            'products' => $products,

            'homeSize' => Image::getSize('home_default'),

            'megatitle' => $this->getFeatureValueName($id_feature_value)

        ));        

        $this->setTemplate('yournewmodule_list.tpl');

    }   

    

    public function setMedia()

    {

        parent::setMedia();

        $this->addCSS(_THEME_CSS_DIR_.'product_list.css');

    }

    

    public function getFeatureValueName($id_feature_value)

    {

        return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('SELECT value FROM '._DB_PREFIX_.'feature_value_lang FVL WHERE '.

        'FVL.id_lang = 1 and FVL.id_feature_value = '.(int)$id_feature_value);    

    }

}

?>

Damn, we need to list all the products having the same feature value, given in the URL.
Who you gonna call? No, not GhostBusters, but a new function from your overriden Product class: getProductsByIDFeatureValue

Here it is, Add it in your /override/classes/Product.php:
 


    public static function getProductsByIDFeatureValue($id_lang, $start, $limit, $order_by, $order_way, $id_feature_value, $id_category = false,

        $only_active = false, Context $context = null)

    {

        if (!$context)

            $context = Context::getContext();



        $front = true;

        if (!in_array($context->controller->controller_type, array('front', 'modulefront')))

            $front = false;



        if (!Validate::isOrderBy($order_by) || !Validate::isOrderWay($order_way))

            die (Tools::displayError());

        if ($order_by == 'id_product' || $order_by == 'price' || $order_by == 'date_add' || $order_by == 'date_upd')

            $order_by_prefix = 'p';

        elseif ($order_by == 'name')

            $order_by_prefix = 'pl';

        elseif ($order_by == 'position')

            $order_by_prefix = 'c';



        if (strpos($order_by, '.') > 0)

        {

            $order_by = explode('.', $order_by);

            $order_by_prefix = $order_by[0];

            $order_by = $order_by[1];

        }

        $sql = 'SELECT p.*, product_shop.*, pl.* , m.`name` AS manufacturer_name, s.`name` AS supplier_name

                FROM `'._DB_PREFIX_.'product` p

                '.Shop::addSqlAssociation('product', 'p').'

                LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (p.`id_product` = pl.`id_product` '.Shop::addSqlRestrictionOnLang('pl').')

                LEFT JOIN `'._DB_PREFIX_.'manufacturer` m ON (m.`id_manufacturer` = p.`id_manufacturer`)

                LEFT JOIN `'._DB_PREFIX_.'supplier` s ON (s.`id_supplier` = p.`id_supplier`)'.

                'LEFT JOIN `'._DB_PREFIX_.'feature_product` fp ON (fp.`id_product` = p.`id_product`)'.

                ($id_category ? 'LEFT JOIN `'._DB_PREFIX_.'category_product` c ON (c.`id_product` = p.`id_product`)' : '').'

                WHERE pl.`id_lang` = '.(int)$id_lang.

                    ($id_category ? ' AND c.`id_category` = '.(int)$id_category : '').

                    ($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '').

                    ($only_active ? ' AND product_shop.`active` = 1' : '').

                    ' AND fp.id_feature_value = '.(int)$id_feature_value.

                ' ORDER BY '.(isset($order_by_prefix) ? pSQL($order_by_prefix).'.' : '').'`'.pSQL($order_by).'` '.pSQL($order_way).

                ($limit > 0 ? ' LIMIT '.(int)$start.','.(int)$limit : '');

        $rq = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);

        if ($order_by == 'price')

            Tools::orderbyPrice($rq, $order_way);



        foreach ($rq as &$row)

            $row = Product::getTaxesInformations($row);



        return ($rq);

    }    

Our page also uses a new template "yournewmodule_list.tpl", so here it is.

Let's put it under /modules/yournewmodule/views/templates/front/yournewmodule_list.tpl
That's basically a call to the default product list, but this way you can easily customize it.
 


<h1 class="page-heading product-listing">{$megatitle|escape:'html':'UTF-8'}</h1>

{include file="$tpl_dir./product-list.tpl" products=$products class='blockbestsellers tab-pane' id='blockbestsellers'}

Finally, we need to give prestashop some rewrite rules to keep our new page SEO friendly since we're smarty pants.
At the roots of your new module, you should have a mynewmodule.php file

Modify the install function in order to make it look like this:


    public function install()

    {

        if (!parent::install()

            || !$this->registerHook('moduleRoutes')) 

            return false;

            

        return true;

    }

And the related function:
 


    public function hookmoduleRoutes($params) {

        $routes = array();

    

        //One block like this by kind of feature you want to link to

        $routes['module-mynewmodule-artist'] = array(

            'controller'=>'artist',

            'rule'=>'artist{/:code}',

            'keywords'=>array(

                'code'=>array(

                    'regexp'=>'[\w_-]+',

                    'param'=>'short_code'

                )

            ),

            'params'=>array(

                'fc'=>'module',

                'module'=>'yournewmodule',

                'controller'=>'artist'

            )

        );

    }

I think (hope) that's all you need.
Feel free to ask if you want some more explanations!

Start of rant:
I took one hour to write this down, to share with the community.
So if you're reading this, please (if you can) try to do the same when you code some basic stuff for a client, that could be reused later by others devs.
I understand you might want to make a living out of your code, but come on, now the PS ecosystem looks like the windows95 shareware era.
More code sharing = better dev community = more solid framework/CMS to work with = less of a "cheap and shiny modules outlet" feeling.

End of rant.








 

Edited by Ruliane (see edit history)
  • Like 1
  • Thanks 1
Link to comment
Share on other sites

  • 1 month later...

first of all, I admire your attitude towards community dev and for taking the time to post this here.

yes I am one of these desperate souls that would need exactly that sort of functionality on my site.

 

I have carefully followed all your steps and got to the point where my product list shows all artists inclusive the hyperlinks. however upon clicking the links, I am presented with the dreadful "THIS PAGE IS NOT AVAILABLE

WE'RE SORRY, BUT THE WEB ADDRESS YOU'VE ENTERED IS NO LONGER AVAILABLE." page.
 
So something with the routes must be wrong but I can't seem to figure out what (yes, I spent a couple of hours looking at the code and I am new to PS coding and also a very poor php dev.)
 
I named my module "artistmodule" and in the artistmodule.php I have added the code
    public function install()
    {
        Configuration::updateValue('ARTISTMODULE_LIVE_MODE', true);
        return parent::install() &&
            $this->registerHook('header') &&
            $this->registerHook('backOfficeHeader') &&
	    $this->registerHook('moduleRoutes') &&
            $this->registerHook('displayHome');
    }

public function hookmoduleRoutes($params) {
        $routes = array();
    
        //One block like this by kind of feature you want to link to
        $routes['module-artistmodule-artist'] = array(
            'controller'=>'artist',
            'rule'=>'artist{/:code}',
            'keywords'=>array(
                'code'=>array(
                    'regexp'=>'[\w_-]+',
                    'param'=>'short_code'
                )
            ),
            'params'=>array(
                'fc'=>'module',
                'module'=>'artistmodule',
                'controller'=>'artist'
            )
        );
    }	

my (non-working) URL looks like this http://mywebsite/artist/401_dj-rum

 

This is on a PS 1.6.1.3

 

Any help would be appreciated - and yes I would actually buy your module for $40...got exited when I saw your fake product box :)

 

Thanks

 

Link to comment
Share on other sites

  • 9 months later...
  • 2 years later...

Hi there Is there a way to call the feature of level two by id ? when I used for example

{if $feature.id_feature == '24'} -> it's working

{if $feature.id_feature == '123'} -> it's not working

(this two have the same $product->id_category_default)

And do you know how to remove a feature_id list of product ? I'm new to this language so I don't have the grammar :)

For example {if $product->id_category_default && [exclude ??] $feature.id_feature == '24'} ??

Thanks a lot,

Clément

Link to comment
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
×
×
  • Create New...