Sometimes you just want to add a new data dimension to a product without having to use x-cart’s extra fields module. The extra fields modules is great but very limited when it comes to adding grouped data and multiple grouped fields.

So I have documented how to do this using the example scenario of adding attachments to a product like PDFs and other file formats.

Create a database table

CREATE TABLE `xcart_media` (
	`id` INT(11) NOT NULL AUTO_INCREMENT,
	`productid` INT(11) NOT NULL,
	`type` VARCHAR(50) NOT NULL DEFAULT '' COLLATE 'utf8_general_ci',
	`title` VARCHAR(255) NOT NULL DEFAULT '' COLLATE 'utf8_general_ci',
	`description` TEXT(65535) NOT NULL COLLATE 'utf8_general_ci',
	`url` VARCHAR(255) NOT NULL DEFAULT '' COLLATE 'utf8_general_ci',
	`pos` INT(11) NOT NULL DEFAULT '0',
	PRIMARY KEY (`id`) USING BTREE
)
COLLATE='utf8_general_ci'
ENGINE=MyISAM
;

The SQL above will create a table xcart_media which will allow me to store multiple media attachments per product and order them by position.

Create module folder

In the folder /modules/ create a new folder called Product_Media

So you should have /modules/Product_Media/

Create config.php

In this new folder create a file called config.php which contains the following code

<?php
/**
 * Configuration file for product_media module
 *
 * @category   X-Cart
 * @package    X-Cart
 * @subpackage Modules
 * @author     Pinakin Patel <pinakin@zone1creative.co.uk>
 * @copyright  Copyright (c) 2001-present e-commerce expert ltd <pinakin@zone1creative.co.uk>
 * @version    0b0633768559e57d1124488556a9dbf71d865723, v19 (xcart_4_7_7), 2017-01-23 20:12:10, config.php, aim
 * @link       http://pinakin.me.uk/
 * @see        ____file_see____
 */

if ( !defined('XCART_START') ) { header('Location: ../../'); die('Access denied'); }

$css_files['Product_Media'][] = array();
$addons['Product_Media'] = true;

// Add module specific tables
$sql_tbl['media']		= XC_TBL_PREFIX . 'media';

$_module_dir  = $xcart_dir . XC_DS . 'modules' . XC_DS . 'Product_Media';
/*
 Load module functions
*/
if (!empty($include_func))
    require_once $_module_dir . XC_DS . 'func.php';
?>

Create func.php

This file will allow you to create functions that are loaded when this module is activated.

<?php
/**
 * Configuration file for product_media module
 *
 * @category   X-Cart
 * @package    X-Cart
 * @subpackage Modules
 * @author     Pinakin Patel <pinakin@zone1creative.co.uk>
 * @copyright  Copyright (c) 2001-present e-commerce expert ltd <pinakin@zone1creative.co.uk>
 * @version    0b0633768559e57d1124488556a9dbf71d865723, v19 (xcart_4_7_7), 2017-01-23 20:12:10, config.php, aim
 * @link       http://pinakin.me.uk/
 * @see        ____file_see____
 */

if (!defined('XCART_SESSION_START')) { header("Location: ../../"); die("Access denied"); }

?>

SQL to create module

INSERT INTO `xcart_modules` (`module_name`, `module_descr`, `active`, `init_orderby`, `author`, `module_url`, `tags`) VALUES ('Product_Media', 'This module allows you to attach files to a product', 'N', 0, 'ecommerce expert ltd', '', 'products');
INSERT INTO `xcart_languages` (`code`, `name`, `value`, `topic`) VALUES ('en', 'module_descr_Product_Media', 'This module allows you to attach files to a product', 'Modules');
INSERT INTO `xcart_languages` (`code`, `name`, `value`, `topic`) VALUES ('en', 'module_name_Product_Media', 'Product Media', 'Modules');

If all goes well in the backend you can goto settings > modules and you should see a deactivated Product Media module. You can then active this and check the store front end still loads. If it does then it has all worked correctly. You may want to perform a Maintenance cleanup now.

Modify include/product_modify.php

When can now edit the include/product_modify.php script which handles the product modify code logic backend. In this file find and add below.

find

/**
 * Define the current section
 */
if (
    empty($section)
    || !in_array($section, $avail_sections)
) {
    $section = 'main';
}

add directly above it
  
if (!empty($active_modules['Product_Media'])) {
    $avail_sections[] = 'product_media';
}

Add the following SQL for label language variable

INSERT INTO `xcart_languages` (`code`, `name`, `value`, `topic`) VALUES ('en', 'lbl_product_media', 'Media Links', 'Labels');

Now when you goto edit a product you will see a Media Links option in the right hand in this section tab. If you click on it you will get an empty screen of product information. So next we need to add code to handle this request. This code will allow you to see assigned media files, edit and delete them too.

Create template file for editing the page content

Create a file in /skin/common_files/modules/Product_Media/product_media_modify.tpl

{*
762433e303212cec36cdce87e30762d8b1dc1ad3, v2 (xcart_4_4_0), 2010-07-16 06:30:56, product_images_modify.tpl, igoryan
vim: set ts=2 sw=2 sts=2 et:
*}
{if $active_modules.Detailed_Product_Images ne ""}

{$lng.txt_get_media_top_text}

<br /><br />

{capture name=dialog}

<form action="product_modify.php" method="post" name="uploadform">

<input type="hidden" name="mode" value="product_media" />
<input type="hidden" name="productid" value="{$product.productid}" />
<input type="hidden" name="geid" value="{$geid}" />

{if $product_media}
{include file="main/check_all_row.tpl" style="line-height: 170%;" form="uploadform" prefix="iids"}
{/if}

<table cellspacing="0" cellpadding="3" width="100%">

<tr class="TableHead">
{*if $geid ne ''}<td width="15" class="TableSubHead">&nbsp;</td>{/if*}
<td width="15" class="DataTable">&nbsp;</td>
<td width="65" class="DataTable">{$lng.lbl_preview}</td>
<td width="5%" class="DataTable">{$lng.lbl_pos}</td>
<td width="15%" class="DataTable">{$lng.lbl_type}</td>
<td width="15%" class="DataTable">{$lng.lbl_url}</td>
<td width="10%" nowrap="nowrap" class="DataTable">{$lng.lbl_title}</td>
<td width="40%" nowrap="nowrap">{$lng.lbl_description}</td>
</tr>

{if $product_media}

{section name=media loop=$product_media}

<tr{cycle values=", class='TableSubHead'"}>
{*if $geid ne ''}<td width="15" class="TableSubHead"><input type="checkbox" value="Y" name="fields[d_image][{$product_media[media].id}]" /></td>{/if*}
  <td width="15" class="DataTable"><input type="checkbox" value="Y" name="iids[{$product_media[media].id}]" /></td>
  <td align="center" class="DataTable">
    {if $product_media[media].type eq "STP"}
	<a href="{$product_media[media].url}" target="_blank">
		<img src="/skin/chk_light_responsive/images/custom/stp-icon.png" alt="" />
	</a>
    {elseif $product_media[media].type eq "PDF"}
	<a href="{$product_media[media].url}" target="_blank">
		<img src="/skin/chk_light_responsive/images/custom/pdf.png" alt="" />
	</a>
    {elseif $product_media[media].type eq "3DM"}
	<a href="{$product_media[media].url}" target="_blank">
		<img src="/skin/chk_light_responsive/images/custom/icon-3d.png" alt="" />
	</a>
	{else}
	<a href="{$product_media[media].url}" target="_blank">{$product_media[media].url}</a>
	{/if}
  </td>
  <td class="DataTable">
	<input type="text" size="5" maxlength="5" name="media[{$product_media[media].id}][pos]" value="{$product_media[media].pos|escape}" style="width: 100%;" />
  </td>
  <td class="DataTable">
<select name="media[{$product_media[media].id}][type]" style="width:100%">
  <option value=""{if $product_media[media].type eq ""} selected="selected"{/if}>Unset</option>
  <option value="PDF"{if $product_media[media].type eq "PDF"} selected="selected"{/if}>PDF</option>
  <option value="STP"{if $product_media[media].type eq "STP"} selected="selected"{/if}>STP</option>
  <option value="3DM"{if $product_media[media].type eq "3DM"} selected="selected"{/if}>3D Model</option>
</select>
  </td>
  <td class="DataTable">
	<input type="text" size="32" name="media[{$product_media[media].id}][url]" value="{$product_media[media].url|escape}" style="width:100%" />
  </td>
<td width="30%" class="DataTable">
	<input type="text" size="32" name="media[{$product_media[media].id}][title]" value="{$product_media[media].title|escape}" style="width:100%" />
</td>
<td width="30%" class="DataTable">
	<input type="text" size="32" name="media[{$product_media[media].id}][description]" value="{$product_media[media].description|escape}" style="width:100%" />
</td>
</tr>
{/section}

<tr>
  <td>&nbsp;</td>
</tr>

<tr>
  {*if $geid ne ''}<td width="15" class="TableSubHead">&nbsp;</td>{/if*}
  <td colspan="3" class="main-button">
    <input type="button" class="big-main-button" value="{$lng.lbl_apply_changes|strip_tags:false|escape}" onclick="document.uploadform.mode.value='update_media_availability';document.uploadform.submit();" />&nbsp;&nbsp;&nbsp;
  </td>
  <td colspan="3" align="right">
    <input type="button" value="{$lng.lbl_delete_selected|strip_tags:false|escape}" onclick="javascript: if (checkMarks(this.form, new RegExp('iids', 'ig'))) {ldelim} document.uploadform.mode.value='product_media_delete'; document.uploadform.submit();{rdelim}" />
  </td>
</tr>

{else}

<tr>
{*if $geid ne ''}<td width="15" class="TableSubHead">&nbsp;</td>{/if*}
<td colspan="6" align="center">{$lng.txt_no_images}</td>
</tr>

{/if}
<tr>
{*if $geid ne ''}<td width="15" class="TableSubHead">&nbsp;</td>{/if*}
<td colspan="6">
<br /><br />

{include file="main/subheader.tpl" title=$lng.txt_add_new_detail_image}

</td>
</tr>
<tr>
{*if $geid ne ''}<td width="15" class="TableSubHead"><input type="checkbox" value="Y" name="fields[new_d_image]" /></td>{/if*}
<td colspan="6">
<table cellpadding="4" cellspacing="0">

<tr>
<td nowrap="nowrap">{$lng.lbl_pos}</td>
<td><input type="text" size="45" name="new_media[pos]" value="0" /></td>
</tr>

<tr>
<td nowrap="nowrap">{$lng.lbl_type}</td>
<td>
<select name="new_media[type]">
  <option value="">Unset</option>
  <option value="PDF">PDF</option>
  <option value="STP">STP</option>
  <option value="3DM">3D Model</option>
</select>
</td>
</tr>


<tr>
<td nowrap="nowrap">{$lng.lbl_url}</td>
<td><input type="text" size="45" name="new_media[url]" value="" /></td>
</tr>


<tr>
<td nowrap="nowrap">{$lng.lbl_title}</td>
<td><input type="text" size="45" name="new_media[title]" value="" /></td>
</tr>

<tr>
<td nowrap="nowrap">{$lng.lbl_description}</td>
<td><input type="text" size="45" name="new_media[description]" value="" /></td>
</tr>

<tr>
  <td colspan="2" class="main-button">
    <br />
    <input type="submit" value="{$lng.lbl_upload|strip_tags:false|escape}" />
  </td>
</tr>

</table>

</td>
</tr>
</table>
</form>
{/capture}
{include file="dialog.tpl" title=$lng.lbl_jewm_media content=$smarty.capture.dialog extra='width="100%"'}
{/if}

Add an include to the product_modify.tpl file

Edit skin/common_files/main/product_modify.tpl and add the following

find

{if $active_modules.Detailed_Product_Images and $section eq "images"}
<a name="section_images"></a>
{include file="modules/Detailed_Product_Images/product_images_modify.tpl"}
<br />
{/if}

add after

{if $active_modules.Product_Media and $section eq "product_media"}
<a name="section_images"></a>
{include file="modules/Product_Media/product_media_modify.tpl"}
<br />
{/if}

Create code that will add, edit, delete and select for admin screens

Create a file /modules/Product_Media/product_media_modify.php and add the following into it

<?php
/**
 * Configuration file for product_media module
 *
 * @category   X-Cart
 * @package    X-Cart
 * @subpackage Modules
 * @author     Pinakin Patel <pinakin@zone1creative.co.uk>
 * @copyright  Copyright (c) 2001-present e-commerce expert ltd <pinakin@zone1creative.co.uk>
 * @version    0b0633768559e57d1124488556a9dbf71d865723, v19 (xcart_4_7_7), 2017-01-23 20:12:10, config.php, aim
 * @link       http://pinakin.me.uk/
 * @see        ____file_see____
 */
 
if ( !defined('XCART_SESSION_START') ) { header("Location: ../../"); die("Access denied"); }

x_load('backoffice','product');

// Upload additional product image
if ($mode == 'product_media' && !empty($new_media)) {
	
	$insert_array = array(
		'productid' 	=> $productid,
		'pos' 			=> intval($new_media['pos']),
		'type' 			=> $new_media['type'],
		'value' 		=> $new_media['value'],
		'title' 		=> $new_media['title'],
		'description' 	=> $new_media['description'],
	);
	
	func_array2insert('media', $insert_array);
	
	$top_message['content'] = func_get_langvar_by_name('msg_adm_product_media_added');
    $top_message['type'] = 'I';
	
    func_refresh('product_media');

// Update product image
} elseif ($mode == 'update_media_availability' && !empty($media)) {
	
	foreach ($media as $key => $value) {
        func_array2update('media', $value, "id = '$key'");
    }
    $top_message['content'] = func_get_langvar_by_name('msg_adm_product_media_updated');
    $top_message['type'] = 'I';
	func_refresh('product_media');

// Delete product image
} elseif ($mode == 'product_media_delete') {
    if (!empty($iids)) {
	
        foreach($iids as $mediaid => $tmp) {
			
			db_query(
				"DELETE FROM $sql_tbl[media]"
				. " WHERE id = '$mediaid' AND productid = '$productid'"
			);
        }

        $top_message['content'] = func_get_langvar_by_name('msg_adm_product_media_deleted');
        $top_message['type'] = 'I';
    }
    func_refresh('product_media');
}

/**
 * Collect product images
 */
$product_media = func_query("SELECT * FROM $sql_tbl[media] WHERE productid = '$productid' ORDER BY pos, id");

if (is_array($product_media) && !empty($product_media)) {

    $smarty->assign('product_media', $product_media);
}

?>

Modify include/product_modify.php again

You will need to add an include for the new file just added like so

find

/**
 * Detailed_Product_Images module
 */
if (!empty($active_modules['Detailed_Product_Images'])) {
    include $xcart_dir.'/modules/Detailed_Product_Images/product_images_modify.php';
}

add after

/**
 * Product_Media module
 */
if (!empty($active_modules['Product_Media'])) {
    include $xcart_dir.'/modules/Product_Media/product_media_modify.php';
}

Display content on the Product Page

Create a file called /modules/Product_Media/product_media.php . This file will be used to fetch the media records on the xcart_media table linked to the product being viewed.

<?php
/**
 * Configuration file for product_media module
 *
 * @category   X-Cart
 * @package    X-Cart
 * @subpackage Modules
 * @author     Pinakin Patel <pinakin@zone1creative.co.uk>
 * @copyright  Copyright (c) 2001-present e-commerce expert ltd <pinakin@zone1creative.co.uk>
 * @version    0b0633768559e57d1124488556a9dbf71d865723, v19 (xcart_4_7_7), 2017-01-23 20:12:10, config.php, aim
 * @link       http://pinakin.me.uk/
 * @see        ____file_see____
 */
 
if ( !defined('XCART_SESSION_START') ) { header("Location: ../../"); die("Access denied"); }

global $smarty;

if (!empty($productid) && in_array(AREA_TYPE, array('C', 'B'))) {
	assert('!empty($product_info)');
	if ($productid > 0) {
			$product_media = func_query("SELECT * FROM $sql_tbl[media] WHERE $sql_tbl[media].productid = '$productid' ORDER BY $sql_tbl[media].pos", TRUE);
			if (!empty($product_media)) $product_info['product_media'] = $product_media;
    }
}
?>

Add an include to use this new file

In /product.php find and add the following

find

if(!empty($active_modules['Feature_Comparison']))
        include $xcart_dir . '/modules/Feature_Comparison/product.php';

add after

if(!empty($active_modules['Product_Media'])) {
  include $xcart_dir . '/modules/Product_Media/product_media.php';
}

Edit front end template files

Create a file in /skin/common_files/Product_Media/product_media.tpl

{if $product.product_media}
<div class="chk-descr-downloads">
	<h3>Downloads</h3>
	{foreach from=$product.product_media item=media name=product_media}
	{if $media.type eq 'PDF'}
	<div class="pdf">
		<a href="{$media.url}" target="_blank"><img style="" src="/skin/chk_light_responsive/images/custom/pdf.png"><span style="">{$media.title}</span></a>
	</div>
	{/if}
	{if $media.type eq 'STP'}
	<div class="pdf">
		<a href="{$media.url}" target="_blank"><img style="" src="/skin/chk_light_responsive/images/custom/stp-icon.png"><span style="">{$media.title}</span></a>
	</div>
	{/if}
	{if $media.type eq '3DM'}
	<div class="pdf">
		<a href="{$media.url}" target="_blank"><img style="" src="/skin/chk_light_responsive/images/custom/icon-3d.png"><span style="">{$media.title}</span></a>
	</div>
	{/if}
	{if $media.type eq ''}
	<div class="link">
		<a href="{$media.url}" target="_blank">{if $media.title}{$media.title}{else}{$media.url}{/if}</a>
	</div>
	{/if}
	{/foreach}
</div>
{/if}

Add this smarty include into you /skin/your_template/customer/main/product.tpl

{if $active_modules.Product_Media}
	{include file="modules/Product_Media/product_media.tpl"}
{/if}

ScreenShots

Admin

Frontend