+4

Bật tính năng SEO friendly trong Opencart

"Happy new year -- cung hỷ phát tài vạn sự như ý". Chào các bạn, là fan của viblo đã lâu nhưng vẫn chưa có chia sẽ gì cả sẵn tiện đầu năm mình quyết định viết một bài để tạo bắt đầu một hành trình mới cho năm mới. Bài viết này tóm gọn cách thức bật tính năng "SEO friendly Url trong Opencart".

1. Tóm gọn SEO Friendly URLs là gì?

Trước hết ta cần hiểu khái niệm "Seo Friendly Urls" là gì?. "Seo friendly url" hay "Slug Url" là một khái niệm dùng để chỉ cấu trúc đường dẫn liên kết đặt sau tên miền, cấu trúc này kết hợp với tên miền và tại thành một liên kết đến tài nguyên của website ( một bài viết, một trang nguồn, hình ảnh, video ...). "Seo friendly url" có 2 tính năng đặc biệt sau:

  • Là một chuỗi được kết hợp từ những ký tự a-Z và số 0-9 cùng dấu phân cách "-" đại diện cho khoảng trắng hoặc các ký tự đặc biệt "%*~!".
  • Chuỗi ký tự Slug Url luôn rõ nghĩa,dễ nhớ và là duy nhất trong toàn bộ các liên kết đang có trong một website.

Và SEO Friendly là một trong những yêu cầu quan trọng khi triển khai một plan SEO thành công, do đó việc website hỗ trợ tính năng này là điều bắt buộc trong thời đại nhà nhà làm "digital marketing và SEO". Nội dung dưới đây hi vọng sẽ giúp các bạn - những người mới làm quen với Opencart sẽ có khởi đầu suôn sẽ hơn trong tương lai.

2. Bật tính năng SEO Friendly URLs trong Opencart.

Không như Wordpress hay Pretashop, mặc định tính năng làm đẹp liên kết này không được bật trong Opencart mà phải thực hiện một cách thủ công qua 2 bước như sau:

2.1 Bước 1: Bật tính năng SEO Urls trong admin

  1. Login vào admin panel (bạn cần có quyền hệ thống thì mới thấy được các setting tiếp theo).
  2. Di chuyển đến mục System > Settings -> Server (Hệ thống > Cấu Hình Hệ Thống > Máy Chủ).
  3. Check vào tính năng USE SEO Urls trong admin như hình dưới. Nhớ xóa cache hệ thống nếu có cài đặt plugin cache nhé.

2.2 Bước 2: Thay đổi file .htaccess

Sau khi làm xong bước 1 thì về cơ bản website của bạn đã được kích hoạt tính năng SEO, mỗi khi thêm bài viết mới hay một sản phẩm mới thì trong admin phần nhập liệu sẽ có mục để bạn tùy chỉnh liên kết của tài nguyên. Tuy nhiên, để người dùng có thể truy cập được liên kết đó thì bạn cần phải edit lại file htaccess (hiện tại trên nginx thì mình vẫn chưa biết cái file cấu hình chuẩn là gì, a/c/e nào biết share mình với nhé) như sau: Cấu trúc file .htaccess

# SEO URL Settings
RewriteEngine On
# If your opencart installation does not run on the main web folder make sure you folder it does run in ie. / becomes /shop/RewriteBase /
RewriteRule ^sitemap.xml$ index.php?route=feed/google_sitemap [L]
RewriteRule ^googlebase.xml$ index.php?route=feed/google_base [L]
RewriteRule ^download/(.*) /index.php?route=error/not_found [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !.*\.(ico|gif|jpg|jpeg|png|js|css)
RewriteRule ^([^?]*) index.php?_route_=$1 [L,QSA]

Để có thể thêm code 301 trong file .htaccess, bạn thêm phía trên dòng cuối trong đoạn code này.

3. Thêm tính năng SEO Friendly Urls cho module bạn tự tạo.

Mặc định thì các chuỗi Slug Urls này được lưu trong một table, mỗi khi người dùng truy cập vào liên kết thì Core của Opencart sẽ đọc một bản ghi trong table này và điều hướng đến controller tương ứng. Tuy nhiên, tính năng này thì không tự detech mỗi khi chúng ta thêm một controller mới hay một module mới do đó cần phải kích hoạt bằng cách khác theo 2 hướng sau:

3.1 Trường hợp 1: Thêm 1 controller với các methods là điểm cuối của view

Một ví dụ cho trường hợp này là chúng ta muốn thêm 1 trang liên hệ cho website để lấy thông tin khách hàng, hoặc 1 trang landing page để chạy quảng cáo. Mindset cho trường hợp này thì ta chỉ cần tạo 1 controller với 1 method để hiển thị view có form liên hệ kết hợp với xử lý lưu trữ nội dung này. Giả sử chúng ta tạo 1 controller có tên là "contact" với method "index" với cấu trúc thư mục sau:

Lúc này ta truy cập trang contact bằng liên kết có mẫu như sau: root-domain/index.php?route=folder/controller/method Như ví dụ trên thì sẽ là root-domain/index.php?route=contact-folder/contact/index hoặc root-domain/index.php?route=contact-folder/contact với method index là mặc định nếu không cung cấp. Để chuyển đoạn url khó nhớ trên thành 1 dang đại loại như : root-domain/lien-he-voi-chung-toi thì bạn vào trong admin tìm kiếm mục: Design > SEO URL hoặc Thiết Kế > SEO URL để thêm 1 record vào bản quản lý các liên kết SEO URL.

Lưu ý:

  • Query sẽ bằng đoạn contact-folder/contact/index
  • Keyword sẽ bằng lien-he-voi-chung-toi

3.2 Trường hợp 2: Thêm 1 module tự tạo với dữ liệu render view phụ thuộc vào model id

Như ví dụ 3.1 thì dữ liệu render của view phụ thuộc vào code tĩnh được thêm trong file contact.twig trong bộ mã code. Vậy nếu chúng ta muốn dữ liệu render được lấy từ trong 1 model được ánh xạ từ một hay nhiều table trong database thì phải làm thế nào. Một ví dụ trực quan nhất cho trường hợp này là module đăng tin tuyển dụng trên website. Module đơn giản này có các tính năng cơ bản như sau:

  1. Một trang list các tin đăng
  2. Mỗi tin tuyển dụng ở trang tin đăng thì sẽ render ra một trang chi tiết nội dung tin tuyển dụng đã được nhập trước trong trang admin.

Trang list ta có thể tạo 1 controller và 1 method như ở ví dụ 3.1. Còn trang chi tiết thì cần phải lấy dữ liệu động từ method get trên url với param đại loại tuyendung=id trong url được tạo.

Để làm được điều này ta cần thêm 1 số code vào trong file:

  • Opencart 3 + 4: catalog/controller/startup/seo_url.php
  • Opencart 2: catalog/controller/common/seo_url.php
  • Opencart 4: cập nhật sau (mình chưa đụng tới em nó)
<?php
class ControllerStartupSeoUrl extends Controller {
	public function index() {
		// Add rewrite to url class
		if ($this->config->get('config_seo_url')) {
			$this->url->addRewrite($this);
		}

		// Decode URL
		if (isset($this->request->get['_route_'])) {
			$parts = explode('/', $this->request->get['_route_']);

			// remove any empty arrays from trailing
			if (utf8_strlen(end($parts)) == 0) {
				array_pop($parts);
			}

			foreach ($parts as $part) {

				$partFormat = str_replace(".html", "", $part);

				$query = $this->db->query("SELECT * FROM " . DB_PREFIX . "seo_url WHERE keyword = '" . $this->db->escape($partFormat) . "' AND store_id = '" . (int)$this->config->get('config_store_id') . "'");

				if ($query->num_rows) {
					$url = explode('=', $query->row['query']);

					if ($url[0] == 'product_id') {
						$this->request->get['product_id'] = $url[1];
					}

					if ($url[0] == 'category_id') {
						if (!isset($this->request->get['path'])) {
							$this->request->get['path'] = $url[1];
						} else {
							$this->request->get['path'] .= '_' . $url[1];
						}
					}

					if ($url[0] == 'manufacturer_id') {
						$this->request->get['manufacturer_id'] = $url[1];
					}

					if ($url[0] == 'information_id') {
						$this->request->get['information_id'] = $url[1];
					}

					if ($url[0] == 'blog_id') {
						$this->request->get['blog_id'] = $url[1];
					}

					if ($url[0] == 'blog_category_id') {
						$this->request->get['blog_category_id'] = $url[1];
					}

					if ($url[0] == 'video_id') {
						$this->request->get['video_id'] = $url[1];
					}

					if ($url[0] == 'video_category_id') {
						$this->request->get['video_category_id'] = $url[1];
					}

                    if ($url[0] == 'landing_id') {
                        $this->request->get['landing_id'] = $url[1];
                    }

                    if ($url[0] == 'branch_id') {
                        $this->request->get['branch_id'] = $url[1];
                    }

					if ($query->row['query'] && 
						$url[0] != 'video_id' && 
						$url[0] != 'branch_id' && 
						$url[0] != 'video_category_id' && 
						$url[0] != 'landing_id' &&
						$url[0] != 'blog_id' &&
						$url[0] != 'blog_category_id' && 
						$url[0] != 'information_id' && 
						$url[0] != 'manufacturer_id' && 
						$url[0] != 'category_id' && 
						$url[0] != 'product_id') {
						$this->request->get['route'] = $query->row['query'];
					}
				} else {
					$this->request->get['route'] = 'error/not_found';

					break;
				}
			}

			if (!isset($this->request->get['route'])) {
				if (isset($this->request->get['product_id'])) {
					$this->request->get['route'] = 'product/product';
				} elseif (isset($this->request->get['path'])) {
					$this->request->get['route'] = 'product/category';
				} elseif (isset($this->request->get['manufacturer_id'])) {
					$this->request->get['route'] = 'product/manufacturer/info';
				} elseif (isset($this->request->get['information_id'])) {
					$this->request->get['route'] = 'information/information';
				}elseif (isset($this->request->get['blog_id'])) {
					$this->request->get['route'] = 'web/blog';
				}elseif (isset($this->request->get['blog_category_id'])) {
					$this->request->get['route'] = 'web/category';
				}elseif (isset($this->request->get['video_category_id'])) {
					$this->request->get['route'] = 'web/video_category';
				}elseif (isset($this->request->get['video_id'])) {
					$this->request->get['route'] = 'web/video';
				}elseif (isset($this->request->get['landing_id'])) {
                    $this->request->get['route'] = 'web/landing';
                }elseif (isset($this->request->get['branch_id'])) {
                    $this->request->get['route'] = 'web/branch';
                }
			}
		}
	}

	public function rewrite($link) {
		$url_info = parse_url(str_replace('&amp;', '&', $link));

		$url = '';

		$data = array();

		parse_str($url_info['query'], $data);

		foreach ($data as $key => $value) {
			if (isset($data['route'])) {
				if (($data['route'] == 'product/product' && $key == 'product_id') || 
					(($data['route'] == 'product/manufacturer/info' || $data['route'] == 'product/product') && $key == 'manufacturer_id') || ($data['route'] == 'information/information' && $key == 'information_id')
						|| ($data['route'] == 'web/blog' && $key == 'blog_id')
						|| ($data['route'] == 'web/category' && $key == 'blog_category_id')
						|| ($data['route'] == 'web/video_category' && $key == 'video_category_id')
						|| ($data['route'] == 'web/video' && $key == 'video_id')
						|| ($data['route'] == 'web/landing' && $key == 'landing_id')
						|| ($data['route'] == 'web/branch' && $key == 'branch_id')
					) {
					$query = $this->db->query("SELECT * FROM " . DB_PREFIX . "seo_url WHERE `query` = '" . $this->db->escape($key . '=' . (int)$value) . "' AND store_id = '" . (int)$this->config->get('config_store_id') . "' AND language_id = '" . (int)$this->config->get('config_language_id') . "'");

					if ($query->num_rows && $query->row['keyword']) {
						// $url .= '/' . $query->row['keyword']
						$url .= '/' . $query->row['keyword'] . '.html';

						unset($data[$key]);
					}
				} elseif ($key == 'path') {
					$categories = explode('_', $value);

					foreach ($categories as $category) {
						$query = $this->db->query("SELECT * FROM " . DB_PREFIX . "seo_url WHERE `query` = 'category_id=" . (int)$category . "' AND store_id = '" . (int)$this->config->get('config_store_id') . "' AND language_id = '" . (int)$this->config->get('config_language_id') . "'");

						if ($query->num_rows && $query->row['keyword']) {
							$url .= '/' . $query->row['keyword'];
						} else {
							$url = '';

							break;
						}
					}

					$url .= '.html';

					unset($data[$key]);
				}
				elseif( 
                    ($data['route'] == 'web/gallery' ||
					$data['route'] == 'web/contact' ||
					$data['route'] == 'web/contact/create' ||
					$data['route'] == 'product/special' ||
					$data['route'] == 'product/newest' ||
					$data['route'] == 'product/bestseller' ||
					$data['route'] == 'product/search' ||
					$data['route'] == 'web/category' ||
					$data['route'] == 'web/store' ||
					$data['route'] == 'common/home' ||
					$data['route'] == 'web/prize' ||
					$data['route'] == 'checkout/cart' ||
					$data['route'] == 'checkout/recurring' ||
					$data['route'] == 'checkout/checkout' ||
					$data['route'] == 'checkout/failure' ||
					$data['route'] == 'checkout/success' ||
					$data['route'] == 'account/account' ||
					$data['route'] == 'account/edit' ||
					$data['route'] == 'account/password' ||
					$data['route'] == 'account/address' ||
					$data['route'] == 'account/wishlist' ||
					$data['route'] == 'account/order' ||
					$data['route'] == 'account/download' ||
					$data['route'] == 'account/reward' ||
					$data['route'] == 'account/return' ||
					$data['route'] == 'account/transaction' ||
					$data['route'] == 'account/recurring' ||
					$data['route'] == 'account/newsletter' ||
					$data['route'] == 'account/logout' ||
					$data['route'] == 'account/login' ||
					$data['route'] == 'account/register' ||
					$data['route'] == 'web/store/search' ||
					$data['route'] == 'web/video_category') and $key == 'route'
				 ) {
					//my custom
					 $query = $this->db->query("SELECT * FROM " . DB_PREFIX . "seo_url WHERE `query` = '" . $data['route'] . "'");
 
	                if ($query->num_rows && $query->row['keyword']) {
	                    // $url .= '/' . $query->row['keyword'];
	                    if( $data['route'] == 'web/category' && isset($data['blog_category_id']) || $data['route'] == 'web/video_category' && isset($data['video_category_id']) ) {
							$url .= '/' . $query->row['keyword'];
	                    }else {
	                    	$url .= '/' . $query->row['keyword'] . '.html';
	                    }
	                } else {
	                    $url = '';
	                     
	                    break;
	                }
				}
			}
		}

		if ($url) {
			unset($data['route']);

			$query = '';

			if ($data) {
				foreach ($data as $key => $value) {
					$query .= '&' . rawurlencode((string)$key) . '=' . rawurlencode((is_array($value) ? http_build_query($value) : (string)$value));
				}

				if ($query) {
					$query = '?' . str_replace('&', '&amp;', trim($query, '&'));
				}
			}

			return $url_info['scheme'] . '://' . $url_info['host'] . (isset($url_info['port']) ? ':' . $url_info['port'] : '') . str_replace('/index.php', '', $url_info['path']) . $url . $query;
		} else {
			return $link;
		}
	}
}

Mình gửi 1 file seo_url.php sau khi đã tùy chỉnh và thêm 1 số liên kết SEO. Hy vọng bài viết sẽ giúp được các bạn.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí