Enable Combinations for Virtual Products in Prestashop 1.6/1.5

Prestashop 1.4 used to let you create combinations for downloadable items. It was the same file for every combinations, but we could use them, at least. This is no longer possible with Prestashop 1.5 and 1.6’s virtual products, so let’s see how to turn this feature back!

The problem: You cannot use combinations with a virtual product

If you ever tried to add attribute combinations to a virtual product, or turn into virtual a normal one that already had them, you might have run into this message. In the worst case, you just upgraded from 1.4 and discovered your products didn’t have the previous files attached anymore, if they had combinations. Despite having to re-bind all the downloadable files in the latter case, we can re-enable combinations for virtual products in Prestashop 1.5 using some overrides:

  • AdminProductsController
  • combinations.tpl (back office template file)
  • virtualproduct.tpl

We will simply disable a couple of checks Prestashop does, which turn off combinations if the product is virtual, and vice-versa.

The AdminProductsController Override

First of all, let’s disable the virtual product check in the product’s back office. Create a new file in override/controllers/admin and call it AdminProductsController.php. Add the following to begin with, inside php tags of course:

class AdminProductsController extends AdminProductsControllerCore
{
}

At this point, we need to extend and modify the method named initFormAttributes. Therefore, open up the original AdminProductsController.php (controllers/admin), copy initFormAttributes from there (about line 3628 in Prestashop 1.5.6), and paste the whole function inside the new override.

Then, locate the following:

if ($this->product_exists_in_shop)
			{
				if ($product->is_virtual)
				{
					$data->assign('product', $product);
					$this->displayWarning($this->l('A virtual product cannot have combinations.'));
				}
				else
				{
					$attribute_js = array();
					$attributes = Attribute::getAttributes($this->context->language->id, true);
					foreach ($attributes as $k => $attribute)
						$attribute_js[$attribute['id_attribute_group']][$attribute['id_attribute']] = $attribute['name'];
					$currency = $this->context->currency;
					$data->assign('attributeJs', $attribute_js);
					$data->assign('attributes_groups', AttributeGroup::getAttributesGroups($this->context->language->id));

					$data->assign('currency', $currency);

					$images = Image::getImages($this->context->language->id, $product->id);

					$data->assign('tax_exclude_option', Tax::excludeTaxeOption());
					$data->assign('ps_weight_unit', Configuration::get('PS_WEIGHT_UNIT'));

					$data->assign('ps_use_ecotax', Configuration::get('PS_USE_ECOTAX'));
					$data->assign('field_value_unity', $this->getFieldValue($product, 'unity'));

					$data->assign('reasons', $reasons = StockMvtReason::getStockMvtReasons($this->context->language->id));
					$data->assign('ps_stock_mvt_reason_default', $ps_stock_mvt_reason_default = Configuration::get('PS_STOCK_MVT_REASON_DEFAULT'));
					$data->assign('minimal_quantity', $this->getFieldValue($product, 'minimal_quantity') ? $this->getFieldValue($product, 'minimal_quantity') : 1);
					$data->assign('available_date', ($this->getFieldValue($product, 'available_date') != 0) ? stripslashes(htmlentities($this->getFieldValue($product, 'available_date'), $this->context->language->id)) : '0000-00-00');

					$i = 0;
					$data->assign('imageType', ImageType::getByNameNType('small_default', 'products'));
					$data->assign('imageWidth', (isset($image_type['width']) ? (int)($image_type['width']) : 64) + 25);
					foreach ($images as $k => $image)
					{
						$images[$k]['obj'] = new Image($image['id_image']);
						++$i;
					}
					$data->assign('images', $images);

					$data->assign($this->tpl_form_vars);
					$data->assign(array(
						'list' => $this->renderListAttributes($product, $currency),
						'product' => $product,
						'id_category' => $product->getDefaultCategory(),
						'token_generator' => Tools::getAdminTokenLite('AdminAttributeGenerator'),
						'combination_exists' => (Shop::isFeatureActive() && (Shop::getContextShopGroup()->share_stock) && count(AttributeGroup::getAttributesGroups($this->context->language->id)) > 0 && $product->hasAttributes())
					));
				}
			}
			else
				$this->displayWarning($this->l('You must save the product in this shop before adding combinations.'));	

Can you see where it reads if ($product->is_virtual)? Get rid of this if/else, so it looks like this:

			if ($this->product_exists_in_shop)
			{
				// removed virtual product restriction
				$attribute_js = array();
				$attributes = Attribute::getAttributes($this->context->language->id, true);
				foreach ($attributes as $k => $attribute)
					$attribute_js[$attribute['id_attribute_group']][$attribute['id_attribute']] = $attribute['name'];
				$currency = $this->context->currency;
				$data->assign('attributeJs', $attribute_js);
				$data->assign('attributes_groups', AttributeGroup::getAttributesGroups($this->context->language->id));

				$data->assign('currency', $currency);

				$images = Image::getImages($this->context->language->id, $product->id);

				$data->assign('tax_exclude_option', Tax::excludeTaxeOption());
				$data->assign('ps_weight_unit', Configuration::get('PS_WEIGHT_UNIT'));

				$data->assign('ps_use_ecotax', Configuration::get('PS_USE_ECOTAX'));
				$data->assign('field_value_unity', $this->getFieldValue($product, 'unity'));

				$data->assign('reasons', $reasons = StockMvtReason::getStockMvtReasons($this->context->language->id));
				$data->assign('ps_stock_mvt_reason_default', $ps_stock_mvt_reason_default = Configuration::get('PS_STOCK_MVT_REASON_DEFAULT'));
				$data->assign('minimal_quantity', $this->getFieldValue($product, 'minimal_quantity') ? $this->getFieldValue($product, 'minimal_quantity') : 1);
				$data->assign('available_date', ($this->getFieldValue($product, 'available_date') != 0) ? stripslashes(htmlentities($this->getFieldValue($product, 'available_date'), $this->context->language->id)) : '0000-00-00');

				$i = 0;
				$type = ImageType::getByNameNType('%', 'products', 'height');
				if (isset($type['name']))
					$data->assign('imageType', $type['name']);
				else
					$data->assign('imageType', 'small_default');
				$data->assign('imageWidth', (isset($image_type['width']) ? (int)($image_type['width']) : 64) + 25);
				foreach ($images as $k => $image)
				{
					$images[$k]['obj'] = new Image($image['id_image']);
					++$i;
				}
				$data->assign('images', $images);

				$data->assign($this->tpl_form_vars);
				$data->assign(array(
					'list' => $this->renderListAttributes($product, $currency),
					'product' => $product,
					'id_category' => $product->getDefaultCategory(),
					'token_generator' => Tools::getAdminTokenLite('AdminAttributeGenerator'),
					'combination_exists' => (Shop::isFeatureActive() && (Shop::getContextShopGroup()->share_stock) && count(AttributeGroup::getAttributesGroups($this->context->language->id)) > 0 && $product->hasAttributes())
				));
				
			}
			else
				$this->displayWarning($this->l('You must save the product in this shop before adding combinations.'));	

Save the file, go to cache/ and delete class_index.php. Then access the product’s back office again, load a virtual product and click on the combinations tab. Nothing! At least, it’s better than the error message. We need to tackle another step.

Editing the attribute combinations template

Inside the same folder where we placed our override, create the following structure: templates/products/. Then, reach the admin folder themes/default/template/controllers/products. Copy combinations.tpl from here into the other folder we just created.

At this point, open the newly cloned file, and right at the beginning we can read:

{if isset($product->id) && !$product->is_virtual}

Just get rid of && !$product->is_virtual. Save and access the combinations tab again!

Finishing touches

You can create combinations for a virtual product right away. However, if you try to convert an existing one into virtual, you will get the very same annoying message you got before. We need to hardcode a small fix in another couple of files, first: admin-products.js, which can be found inside the js folder. Open it up and locate the following:

				if (has_combinations)
				{
					$('#simple_product').attr('checked', true);
					$('#warn_virtual_combinations').show();
				}
				else
				{
					$('li.tab-row a[id*="VirtualProduct"]').show().click();
					$('#is_virtual').val(1);

					tabs_manager.onLoad('VirtualProduct', function(){
						$('#is_virtual_good').attr('checked', true);
						$('#virtual_good').show();
					});

					tabs_manager.onLoad('Quantities', function(){
						$('.stockForVirtualProduct').hide();
					});

					$('li.tab-row a[id*="Shipping"]').hide();

					tabs_manager.onLoad('Informations', function(){
						$('#condition').attr('disabled', true);
						$('#condition option[value=refurbished]').removeAttr('selected');
						$('#condition option[value=used]').removeAttr('selected');
					});
				}

Change it to


					$('li.tab-row a[id*="VirtualProduct"]').show().click();
					$('#is_virtual').val(1);

					tabs_manager.onLoad('VirtualProduct', function(){
						$('#is_virtual_good').attr('checked', true);
						$('#virtual_good').show();
					});

					tabs_manager.onLoad('Quantities', function(){
						$('.stockForVirtualProduct').hide();
					});

					$('li.tab-row a[id*="Shipping"]').hide();

					tabs_manager.onLoad('Informations', function(){
						$('#condition').attr('disabled', true);
						$('#condition option[value=refurbished]').removeAttr('selected');
						$('#condition option[value=used]').removeAttr('selected');
					});
				

Then, once more, reach the themes/default/template/controllers/products folder. This time, copy virtualproduct.tpl to the same folder where we added combinations.tpl. Open it up and inspect the following snippet:

			{* Don't display file form if the product has combinations *}
			{if empty($product->cache_default_attribute)}
				{if $product->productDownload->id}
					<input type="hidden" id="virtual_product_id" name="virtual_product_id" value="{$product->productDownload->id}" />
				{/if}
				<table cellpadding="5" style="float: left; margin-left: 10px;">
					<tr id="upload_input" {if $is_file}style="display:none"{/if}>
						<td class="col-left">
							<label id="virtual_product_file_label" for="virtual_product_file" class="t">{l s='Upload a file'}</label>
						</td>
						<td class="col-right">
							<input type="file" id="virtual_product_file" name="virtual_product_file" onchange="uploadFile();" maxlength="{$upload_max_filesize}" />
							<p class="preference_description">{l s='Your server\'s maximum file-upload size is'}:&nbsp;{$upload_max_filesize} {l s='MB'}</p>
						</td>
					</tr>
					<tr id="upload-error" style="display:none">
						<td colspan=2></td>
					</tr>
					<tr id="upload-confirmation" style="display:none">
						<td colspan=2>
							{if $up_filename}
								<input type="hidden" id="virtual_product_filename" name="virtual_product_filename" value="{$up_filename}" />
							{/if}
							<div class="conf">
							<script>
								delete_this_file = '{l s='Delete this file'}';
							</script>
								<a class="delete_virtual_product" id="delete_downloadable_product" href="{$currentIndex}&deleteVirtualProduct=true&token={$token}&id_product={$product->id}" class="red">
									<img src="../img/admin/delete.gif" alt="{l s='Delete this file'}"/>
								</a>
							</div>
						</td>
					</tr>
					{if $is_file}
						<tr>
							<td class="col-left">
								<input type="hidden" id="virtual_product_filename" name="virtual_product_filename" value="{$product->productDownload->filename}" />
								<label class="t">{l s='Link to the file:'}</label>
							</td>
							 <td class="col-right">
								{$product->productDownload->getHtmlLink(false, true)}
								<a href="{$currentIndex}&deleteVirtualProduct=true&token={$token}&id_product={$product->id}" class="red delete_virtual_product">
									<img src="../img/admin/delete.gif" alt="{l s='Delete this file'}"/>
								</a>
							</td>
						</tr>
					{/if}
					<tr>
						<td class="col-left">
							<label for="virtual_product_name" class="t">{l s='Filename'}</label>
						</td>
						<td class="col-right">
							<input type="text" id="virtual_product_name" name="virtual_product_name" style="width:200px" value="{$product->productDownload->display_filename|escape:'htmlall':'UTF-8'}" />
							<p class="preference_description" name="help_box">{l s='The full filename with its extension (e.g. Book.pdf)'}</p>
						</td>
					</tr>
					<tr>
						<td class="col-left">
							<label for="virtual_product_nb_downloable" class="t">{l s='Number of allowed downloads'}</label>
						</td>
						<td class="col-right">
							<input type="text" id="virtual_product_nb_downloable" name="virtual_product_nb_downloable" value="{$product->productDownload->nb_downloadable|htmlentities}" class="" size="6" />
							<p class="preference_description">{l s='Number of downloads allowed per customer. (Set to 0 for unlimited downloads)'}</p>
						</td>
					</tr>
					<tr>
						<td class="col-left">
							<label for="virtual_product_expiration_date" class="t">{l s='Expiration date'}</label>
						</td>
						<td class="col-right">
							<input class="datepicker" type="text" id="virtual_product_expiration_date" name="virtual_product_expiration_date" value="{$product->productDownload->date_expiration}" size="11" maxlength="10" autocomplete="off" /> {l s='Format: YYYY-MM-DD'}
							<p class="preference_description">{l s='If set, the file will not be downloadable after this date. Leave blank if you do not wish to attach an expiration date.'}</p>
						</td>
					</tr>
						<td class="col-left">
							<label for="virtual_product_nb_days" class="t">{l s='Number of days'}</label>
						</td>
						<td class="col-right">
							<input type="text" id="virtual_product_nb_days" name="virtual_product_nb_days" value="{$product->productDownload->nb_days_accessible|htmlentities}" class="" size="4" /><sup> *</sup>
							<p class="preference_description">{l s='Number of days this file can be accessed by customers'} - <em>({l s='Set to zero for unlimited access.'})</em></p>
						</td>
					</tr>
					{* Feature not implemented *}
					{*<tr>*}
						{*<td class="col-left">*}
							{*<label for="virtual_product_is_shareable" class="t">{l s='is shareable'}</label>*}
						{*</td>*}
						{*<td class="col-right">*}
							{*<input type="checkbox" id="virtual_product_is_shareable" name="virtual_product_is_shareable" value="1" {if $product->productDownload->is_shareable}checked="checked"{/if} />*}
							{*<span class="hint" name="help_box" style="display:none">{l s='Please specify if the file can be shared.'}</span>*}
						{*</td>*}
					{*</tr>*}
				{else}
					<div class="hint clear" style="display: block;width: 70%;">{l s='You cannot edit your file here because you used combinations. Please edit this file in the Combinations tab.'}</div>
					<br />
					{if isset($error_product_download)}{$error_product_download}{/if}
				{/if}

Change it to:


			{if $product->productDownload->id}
				<input type="hidden" id="virtual_product_id" name="virtual_product_id" value="{$product->productDownload->id}" />
			{/if}
			<table cellpadding="5" style="float: left; margin-left: 10px;">
				<tr id="upload_input" {if $is_file}style="display:none"{/if}>
					<td class="col-left">
						<label id="virtual_product_file_label" for="virtual_product_file" class="t">{l s='Upload a file'}</label>
					</td>
					<td class="col-right">
						<input type="file" id="virtual_product_file" name="virtual_product_file" onchange="uploadFile();" maxlength="{$upload_max_filesize}" />
						<p class="preference_description">{l s='Your server\'s maximum file-upload size is'}:&nbsp;{$upload_max_filesize} {l s='MB'}</p>
					</td>
				</tr>
				<tr id="upload-error" style="display:none">
					<td colspan=2></td>
				</tr>
				<tr id="upload-confirmation" style="display:none">
					<td colspan=2>
						{if $up_filename}
							<input type="hidden" id="virtual_product_filename" name="virtual_product_filename" value="{$up_filename}" />
						{/if}
						<div class="conf">
						<script>
							delete_this_file = '{l s='Delete this file'}';
						</script>
							<a class="delete_virtual_product" id="delete_downloadable_product" href="{$currentIndex}&deleteVirtualProduct=true&token={$token}&id_product={$product->id}" class="red">
								<img src="../img/admin/delete.gif" alt="{l s='Delete this file'}"/>
							</a>
						</div>
					</td>
				</tr>
				{if $is_file}
					<tr>
						<td class="col-left">
							<input type="hidden" id="virtual_product_filename" name="virtual_product_filename" value="{$product->productDownload->filename}" />
							<label class="t">{l s='Link to the file:'}</label>
						</td>
						 <td class="col-right">
							{$product->productDownload->getHtmlLink(false, true)}
							<a href="{$currentIndex}&deleteVirtualProduct=true&token={$token}&id_product={$product->id}" class="red delete_virtual_product">
								<img src="../img/admin/delete.gif" alt="{l s='Delete this file'}"/>
							</a>
						</td>
					</tr>
				{/if}
				<tr>
					<td class="col-left">
						<label for="virtual_product_name" class="t">{l s='Filename'}</label>
					</td>
					<td class="col-right">
						<input type="text" id="virtual_product_name" name="virtual_product_name" style="width:200px" value="{$product->productDownload->display_filename|escape:'htmlall':'UTF-8'}" />
						<p class="preference_description" name="help_box">{l s='The full filename with its extension (e.g. Book.pdf)'}</p>
					</td>
				</tr>
				<tr>
					<td class="col-left">
						<label for="virtual_product_nb_downloable" class="t">{l s='Number of allowed downloads'}</label>
					</td>
					<td class="col-right">
						<input type="text" id="virtual_product_nb_downloable" name="virtual_product_nb_downloable" value="{$product->productDownload->nb_downloadable|htmlentities}" class="" size="6" />
						<p class="preference_description">{l s='Number of downloads allowed per customer. (Set to 0 for unlimited downloads)'}</p>
					</td>
				</tr>
				<tr>
					<td class="col-left">
						<label for="virtual_product_expiration_date" class="t">{l s='Expiration date'}</label>
					</td>
					<td class="col-right">
						<input class="datepicker" type="text" id="virtual_product_expiration_date" name="virtual_product_expiration_date" value="{$product->productDownload->date_expiration}" size="11" maxlength="10" autocomplete="off" /> {l s='Format: YYYY-MM-DD'}
						<p class="preference_description">{l s='If set, the file will not be downloadable after this date. Leave blank if you do not wish to attach an expiration date.'}</p>
					</td>
				</tr>
					<td class="col-left">
						<label for="virtual_product_nb_days" class="t">{l s='Number of days'}</label>
					</td>
					<td class="col-right">
						<input type="text" id="virtual_product_nb_days" name="virtual_product_nb_days" value="{$product->productDownload->nb_days_accessible|htmlentities}" class="" size="4" /><sup> *</sup>
						<p class="preference_description">{l s='Number of days this file can be accessed by customers'} - <em>({l s='Set to zero for unlimited access.'})</em></p>
					</td>
				</tr>
				{* Feature not implemented *}
				{*<tr>*}
					{*<td class="col-left">*}
						{*<label for="virtual_product_is_shareable" class="t">{l s='is shareable'}</label>*}
					{*</td>*}
					{*<td class="col-right">*}
						{*<input type="checkbox" id="virtual_product_is_shareable" name="virtual_product_is_shareable" value="1" {if $product->productDownload->is_shareable}checked="checked"{/if} />*}
						{*<span class="hint" name="help_box" style="display:none">{l s='Please specify if the file can be shared.'}</span>*}
					{*</td>*}
				{*</tr>*}

Explanation: we basically got rid of the if/else statement which was triggering the message, instead of displaying the virtual product form.

Save, and we are done!

A note about prestashop 1.6

Iclavijos, user of the Prestashop forums, found out an additional step is needed in order to make virtual products work with combinations in Prestashop 1.6, here follows his advice

You need to override form.tpl (controllers/admin/templates/products/helpers/form) and there, comment out line 116. Basically, this is the original code:

else if (product_type == product_type_virtual)

                {

                    $('a[id*="Pack"]').hide();

                    $('a[id*="Shipping"]').hide();

                    $('a[id*="Combinations"]').hide();

                }[

and this should be the final one:

else if (product_type == product_type_virtual)

                {

                    $('a[id*="Pack"]').hide();

                    $('a[id*="Shipping"]').hide();

                    //$('a[id*="Combinations"]').hide();

                }

Thanks for your contribution!

Final note

Although we used overrides to ensure changes are preserved after an upgrade, remember to re-apply the admin-products.js fix, as we had to hardcode that one!.

You like the tuts and want to say "thank you"? Well, you can always feel free to donate:

  • Gab

    Thanks! I tried this on Thirty Bees and technically it works.
    Is there any way I can associate different combinations with the appropriate files though?

    • NemoPS

      That’s more complex, you need a new table in the database, as well as a column for the virtual file, basically working out a separate, new, system that handles it

  • Alex Kami

    Hey, i’m also interested in this for ps 1.7.2 any ideas on how can i do it? Or simply edit the checkout process in order to erase shipping?

    Cheers!

  • Ros Lin

    Hey, :)

    Is there any chance you might make a similar tutorial for PrestaShop 1.7? The structure seems to be largely different from the files described here.

  • adminmsm

    Hello,

    I thought I had got this to work, but unfortunately not. I get a pop message saying the following:

    (500 Internal Server Error)
    A server error occurred while loading the tabs: some tabs could not be loaded.
    Please try again by refreshing the page.
    If you are still encountering this problem, please check your server logs or contact your hosting provider for assistance.

    I clicked OK and kept going. I can set up the combinations, but I cannot save them as the save buttons just keep turning. Any ideas on both issues! Thanks Kathleen

    • NemoPS

      Hi Kathleen! Thanks for trying out the tut!
      When something like that happens, you can try to turn on debug mode in config/defines.inc.php, then inspect the network tab of the console, look for the red entry, and read the response to know more about the error. It’s explained better here: http://nemops.com/spinning-save-button-prestashop-1-6/#.WT7NCWh96Uk

      • adminmsm

        Hello Nemo,
        I turned on debug mode then logged on to my back office only to find the problem was gone. No 500 error message, and no spinning save buttons, everything works perfectly. This was all done on a WAMP server, now I am going to try it on my live site. Let’s hope there are no problems. Thanks Kathleen

  • Majory Cubizolles Webdesigner

    Perfect ! Thanks

  • AnarchyJim

    All my products disappear after changing adminProductsController (PS 1.6.0.13). This is somewhat panic inducing but removing it from the Override folder gets everything back. Any idea what the issue is?

    I’m guessing if you really know you’re way around PHP you can get this to work with 1.6. For those of us that don’t, this is not going to work.

  • Jeremy

    Hi Nemo,

    I trying test this on version 1.6.13

    After first steps I have in Back office/product all blank page.

    Could you help me, please?

    What I did wrong?

  • Ich Bin Künstler

    Hey, i made this changes in PS 1.6.0.11 – for testing purposes without
    overrides – so far virtual products could have combinations but only
    with the same associated file wich does not make much sense … anyone
    any idea ? THX a lot in advance !

  • Naoto

    Thank you Nemo! This works with my site with version 1.6.0.9. I was getting errors with “A server error occurred while loading the tabs”, too ;-) And I found a hint as “PHP Fatal error” in web server’s log, and I was able to fix. And now I can add combinations with virtual products.

    If you have time, please correct file path in the last section which describe about 1.6 setup. I think it should be :

    admin/themes/default/template/controllers/products/helpers/form/form.tpl

    Thank!

  • Anthony Garcia

    I made these modifications to a new 1.6 install with no success :( – Do we (you) have any other information to complete this feature or is an upcoming version of prestashop rolling back this feature to allow attributes for a virtual product?

    • NemoPS

      Weird, as it should work with the latest addition by the forum user. I have no idea if they will take it back, but you can try a pull request on github!

  • alfonso

    For version 1.6.0.8 dont work, dont upload a virtual product whe i save it.

  • ben

    it doesn’T WORK on 1.6.0.8 is there any workaround?

  • ManoMano

    Hi Nemo. Awesome tutorial. However, after these changes and in product page, I get a half blank page and an error message which asks me if I want to retry some tab loading which was unsuccessful. I followed this
    tutorial step by step and did it all twice from the start, but this
    mentioned error persists. I’m on PrestaShop™ 1.5.6.2

  • rake

    Hi, I don’t use option “Virtual Product (services, booking or downloadable products)” in Global information of Prestashop 1.5.6.2, how can I rid it off ?

  • NemoPS

    On 1.5 it works fine as it is, for me :) I’m using it flawlessly on my store

  • PrestaEdit

    It’s not only theses files you have to change. You need some SQL queries, and others classes to work good… Really.

  • Altomarketing.com

    Does it work on 1.6 version ?

  • Kon Rad

    Hi&Thanks. Works fine at 1.5.6.2 but after product Save i see only white blank page. But how to add a different file that is match to the atributes variants? Without that this solution is partial because customer can choose options but the file is always the same :(

  • NemoPS

    Yes they are. It’s in the admin folder, themes, default, controllers, product ;)

  • Kruti

    Hi Nemo,

    I’m trying the above tutorial but i’m unable to find combinations.tpl file at the prescribed location in PS 1.5.6.2 installation. The same goes with virtualproducts.tpl as i can’t find the file in the installation. Could you please check and see if the steps are still applicable for PS 1.5.6.2 install

    thanks

    Kruti

Store Top Sales

You like the tuts and want to say "thank you"? Well, you can always feel free to donate: