How would I go about fixing the issue where two categories/products have the same URL in Opencart? Or if their is a module which already does this?
E.g: categories
I have resolved this issue i have made a vqmode xml file
see opencart give the same result for below urls
http://mycart/mp3-players/iPod-Classic
http://mycart/mp3-players/some-child/iPod-Classic
http://mycart/some-parent/mp3-players/iPod-Classic
http://mycart/iPod-Classic
it will difficult to do seo for that product “iPod-Classic”
so i write a “vqmod” script with this script you can redirect user to a specific and correct page
even a user type a wrong url
i.e.
http://mycart/mp3-players/ffffdfsdgf/sdf/sdf/iPod-Classic
it will automatically redirect it to
http://mycart/mp3-players/iPod-Classic
you just need to download ‘controller_common_seo_url.xml’ from “box” or
<modification>
<id>public function index() add more functions</id>
<version>1.0</version>
<vqmver>2.1.5</vqmver>
<author>http://www.bhardwajabhi.wordpress.com</author>
<file name=”catalog/controller/common/seo_url.php”>
<operation>
<search position=”before”><![CDATA[ public function index() { ]]></search>
<add><![CDATA[
public function get_seo_title($id, $type)
{
$query = $this->db->query("SELECT keyword FROM " . DB_PREFIX . "url_alias WHERE query = '". $type ."=" . (int)$id . "'");
if($query->row)
return $query->row['keyword'];
else
return $id;
}
public function get_path_level($id)
{
$query = $this->db->query(“select `level` from ” . DB_PREFIX . “category_path where `category_id` = ‘”. $id. “‘ order by level desc LIMIT 1″);
return $query->row['level'];
}
public function get_product_relative($parts, $product_id)
{
if (isset($this->request->server['HTTPS']) && (($this->request->server['HTTPS'] == ‘on’) || ($this->request->server['HTTPS'] == ’1′))) {
$this->data['base'] = $this->config->get(‘config_ssl’);
} else {
$this->data['base'] = $this->config->get(‘config_url’);
}
$path = $this->get_seo_title($product_id, ‘product_id’);
$query = $this->db->query(“SELECT category_id FROM ” . DB_PREFIX . “product_to_category WHERE product_id = ‘” . (int)$product_id . “‘”);
$i =0;
foreach($query->rows as $pro_cat):
$i++;
$sub_query = $this->db->query(“select `path_id` from ” . DB_PREFIX . “category_path where `category_id` = ‘”. $pro_cat['category_id']. “‘ order by level desc”);
foreach($sub_query->rows as $pro_sub_cat):
$path = $this->get_seo_title($pro_sub_cat['path_id'], ‘category_id’) . “/” . $path;
endforeach;
$path1 = ‘/’. $path;
$array1 = explode(‘/’, $path1);
$array2 = explode(‘/’, $this->request->get['_route_']);
$result = array_diff($array1, $array2);
$new_path[$i]['path'] = $path;
$new_path[$i]['count'] = count($result);
$path = $this->get_seo_title($product_id, ‘product_id’);
endforeach;
$min = PHP_INT_MAX;
$max = 0;
foreach ($new_path as $i) {
$min = min($min, $i['count']);
}
foreach($new_path as $value):
if($value['count']==$min):
$final_path = $value['path'];
break;
endif;
endforeach;
similar_text($this->request->get['_route_'], $final_path, $percent);
if($percent<>100)
header (‘location:’ . $this->data['base'] .”. $final_path);
}
]]></add>
</operation>
</file>
<file name=”catalog/controller/common/seo_url.php”>
<operation info=”After ABC, add 123″>
<search position=”after”><![CDATA[
$this->request->get['product_id'] = $url[1];
]]></search>
<add><![CDATA[
/*Start url redirection*/
$this->get_product_relative($parts, $url[1]);
/*End Url Redirection*/
]]></add>
</operation>
</file>
</modification>
that’s it so now when some one hit on any product the cart will show page like
http://www.yourSiteUrl/parent-category/child-category/product
note: you must give seo title to each and every category ,child category and there product because script search for the seo title and if seo title is not available then it give it id so suppose we have not give seo-title for child category and vew its product then the url will be like
http://www.yourSiteurl/parent-category/56/product
you can get more detail about it on here
I know this is a late answer, but this is a deep rooted problem with OpenCarts architecture.
Personally I am not a fan of vQmod, so here is a (what some would call a hack) solution to the problem without using one.
I have seen many alterations to catalog/controller/common/seo_url.php
that add custom seo urls. This fix is compatible with such modifications.
I would also like to add that is by no means the most sophisticated solution in the world but it will guarantee that sub categories with duplicate seo url entries will work as they should.
Find in catalog/controller/common/seo_url.php
if ($url[0] == 'category_id') {
if (!isset($this->request->get['path'])) {
$this->request->get['path'] = $url[1];
} else {
$this->request->get['path'] .= '_' . $url[1];
}
}
Replace with the following
if ($url[0] == 'category_id') {
$categories[$i] = $this->model_catalog_category->getCategory($url[1]);
if (!isset($this->request->get['path'])) {
$this->request->get['path'] = $categories[$i]['category_id'];
} else {
foreach ($query->rows as $row) {
$url = explode('=', $row['query']);
$category_id = $url[1];
$category = $this->model_catalog_category->getCategory($category_id);
if ($category['parent_id'] == $categories[$i - 1]['category_id']) {
$this->request->get['path'] .= '_' . $category['category_id'];
}
}
}
}
Add the following line to the top of the method index()
$this->load->model('catalog/category');
Find the line...
foreach ($parts as $part) {
$query = $this->db->query("SELECT * FROM " . DB_PREFIX . "url_alias WHERE keyword = '" . $this->db->escape($part) . "'");
And replace with the following
$categories = array();
for ($i = 0; $i < count($parts); $i++) {
$query = $this->db->query("SELECT * FROM " . DB_PREFIX . "url_alias WHERE keyword = '" . $this->db->escape($parts[$i]) . "'");
In addition to @Ian Brindley you also should edit another 2 files so you can enter the keywords in the admin panel:
Admin/Controller/Catalog/category.php
Find these lines of code in the method validateForm
if (utf8_strlen($this->request->post['keyword']) > 0) {
$this->load->model('catalog/url_alias');
$url_alias_info = $this->model_catalog_url_alias->getUrlAlias($this->request->post['keyword']);
if ($url_alias_info && isset($this->request->get['category_id']) && $url_alias_info['query'] != 'category_id=' . $this->request->get['category_id']) {
$this->error['keyword'] = sprintf($this->language->get('error_keyword'));
}
if ($url_alias_info && !isset($this->request->get['category_id'])) {
$this->error['keyword'] = sprintf($this->language->get('error_keyword'));
}
if ($this->error && !isset($this->error['warning'])) {
$this->error['warning'] = $this->language->get('error_warning');
}
}
and change them into:
if (utf8_strlen($this->request->post['keyword']) > 0) {
$this->load->model('catalog/url_alias');
$url_alias_info = $this->model_catalog_url_alias->getUrlAlias($this->request->post['keyword']);
if ($url_alias_info && !isset($this->request->get['category_id'])) {
$this->error['keyword'] = sprintf($this->language->get('error_keyword'));
}
if ($this->error && !isset($this->error['warning'])) {
$this->error['warning'] = $this->language->get('error_warning');
}
}
This will get rid of the check if the seo keyword is a duplicate. This way you can enter the seo keywords within the adminpanel instead of phpmyadmin
if ($url[0] == 'category_id') {
$categories[$i] = $this->model_catalog_category->getCategory($url[1]);
if (!isset($this->request->get['path'])) {
$this->request->get['path'] = $categories[$i]['category_id'];
} else {
foreach ($query->rows as $row) {
$url = explode('=', $row['query']);
$category_id = $url[1];
$category = $this->model_catalog_category->getCategory($category_id);
if ($category['parent_id'] == $categories[$i - 1]['category_id']) {
$this->request->get['path'] .= '_' . $category['category_id'];
}
}
}
}
this will not work if its a 3 level category. I hope someone will have a better solution.
I developed this vqmod for that exact scenario