Post by bbcentral » Tue Apr 03, 2012 11:53 am

Hi there,

We're using OpenCart for a whole range of client stores, and we're very happy with the way it's been going.

However there's quite a few places in the code where I'd really like the opportunity to clean things up, make everything more robust and flexible. The actual structure of the code is very clean and well laid out, however some of the content of the various functions is very difficult to follow and work with (especially the SQL, which doesn't seem to follow any particular coding standards). There's also quite a few needless queries which can easily be optimized and condensed down. The database structure itself I have no problem with, it's definitely one of the best I've used.

What would be involved in contributing these various changes back to the project? I'm not talking about adding any functionality, that's something I'd continue to do via vQMod. Part of my job as a PHP/MySQL developer involves rewriting code to keep things optimized and efficient, when you're running stores with 15,000+ products you can't afford to have slow SQL queries or duplicate code cluttering things up. We're quite serious about sticking with OpenCart and would love to share our work back with the project directly (leaving the main developers free to work on more interesting things like building new features!).

If you'd like, please send me a random filename from OpenCart and I'll be happy to rewrite it and show you what I'm talking about (with comments to explain what I'm trying to achieve at each stage). :)

File: /catalog/controller/product/all.php

Before:

Code: Select all

<?php
if (isset($this->request->get['sort'])) {
	$sort = $this->request->get['sort'];
} else {
	$sort = 'pd.name';
}

if (isset($this->request->get['order'])) {
	$order = $this->request->get['order'];
} else {
	$order = 'ASC';
}
	 
if (isset($this->request->get['page'])) {
	$page = $this->request->get['page'];
} else {
	$page = 1;
}

if (isset($this->request->get['limit'])) {
	$limit = $this->request->get['limit'];
} else {
	$limit = $this->config->get('config_catalog_limit');
}
?>
After:

Code: Select all

<?php
$sort 	= isset($this->request->get['sort']) 	? $this->request->get['sort'] 	: 'pd.name';
$order 	= isset($this->request->get['order']) 	? $this->request->get['order'] 	: 'ASC';
$page 	= isset($this->request->get['page']) 	? $this->request->get['page'] 	: 1;
$limit 	= isset($this->request->get['limit']) 	? $this->request->get['limit'] 	: $this->config->get('config_catalog_limit');
?>

OpenCart enthusiast
PHP/MySQL/jQuery Developer


User avatar
Newbie

Posts

Joined
Wed Dec 07, 2011 9:26 am

Post by MrTech » Tue Apr 03, 2012 12:50 pm

Offers like this is what makes a community work.

I would suggest contacting Daniel directly to make your offer:
http://forum.opencart.com/memberlist.ph ... ile&u=3414

~
Install Extensions OR OpenCart Fast Service! PayPal Accepted
I will professionally install and configure any free or purchased theme, module or extension.

Visit http://www.mrtech.ca if you need an OpenCart webmaster
~


User avatar
Active Member

Posts

Joined
Mon Jan 09, 2012 2:39 pm
Location - Canada, Eh!

Post by bbcentral » Tue Apr 03, 2012 1:27 pm

MrTech wrote:Offers like this is what makes a community work.

I would suggest contacting Daniel directly to make your offer:
http://forum.opencart.com/memberlist.ph ... ile&u=3414
I always prefer not to direct message people who have giant inboxes full of mail ("Help me! My site doesn't work!"), I prefer to keep these things on the forum where possible. If I don't get a response in a week or two I'll think about messaging then. Believe me, I understand what it's like when you get pestered with every little question :P

A few things I'd love to do:
  • Move inline escape/cleanup functions outside of SQL statements to improve readability
  • Reduce duplication of code
  • Potentially add SQL replacement variables & parsing directly to the database engine to simplify SQL queries (DB_PREFIX etc)
  • Remove the need for running SQL queries twice to count search results (through enhancements to the models)
  • Rewrite many of the subqueries as joins (this can be a major performance boost)
  • Make sure any SELECT/UPDATE SQL queries that only select or update one row have an appropriate LIMIT 1

OpenCart enthusiast
PHP/MySQL/jQuery Developer


User avatar
Newbie

Posts

Joined
Wed Dec 07, 2011 9:26 am

Post by MrTech » Tue Apr 03, 2012 9:37 pm

Don't hold your breath then, my understanding is this is a real tight group and they don't look kindly to strangers trying to tell them the way they operate is less than desirable. Bad code, not standards, non-existent and out of date documentation, snipy remarks, non-tolerance and the best one yet:

ZERO contact after making a donation! Yup, no thank you note, no extra help on the forums, as a matter of fact, after making my donation all I get is short abrupt answers from the person in question. All I can say is I'm glad my cart is 97% done because navigating this forum has not been a pleasant experience overall. I've been less frustrated watching white trash on Jerry Springer than reading some of the replies offered on here by the veterans...

~
Install Extensions OR OpenCart Fast Service! PayPal Accepted
I will professionally install and configure any free or purchased theme, module or extension.

Visit http://www.mrtech.ca if you need an OpenCart webmaster
~


User avatar
Active Member

Posts

Joined
Mon Jan 09, 2012 2:39 pm
Location - Canada, Eh!

Post by rph » Wed Apr 04, 2012 5:14 am

Development in OpenCart is pretty closed (which has its bad points and good points) but you can always lobby for specific changes.

Your tertiary assignment example seems more a matter of style than anything. I know they're already used in array value assignments which is a good fit but that's about it.

One thing I would disagree with you on is moving input sanitation outside SQL queries. Having it in the queries keeps you from having to search all over the place to make sure everything is cleaned up.

Definitely agree with standardization on the SQL statements (backtick use seems to be random outside required areas).

As much as I'd love to not have to type DB_PREFIX all the time I'm not sure how you would move it without making things more complicated and potentially difficult for developers who add their own tables.

-Ryan


rph
Expert Member

Posts

Joined
Fri Jan 08, 2010 5:05 am
Location - Lincoln, Nebraska

Post by rph » Wed Apr 04, 2012 6:07 am

MrTech wrote:Don't hold your breath then, my understanding is this is a real tight group and they don't look kindly to strangers trying to tell them the way they operate is less than desirable. Bad code, not standards, non-existent and out of date documentation, snipy remarks, non-tolerance
Documentation is "light", to put it politely. I have no idea what you're basing your "bad code, not standards" statement on. I went with OpenCart as a developer because it easily had the best code out of all the carts out there. Certain members have been snippy in the past but bringing in Jerry Springer comparisons certainly isn't going to improve the situation.

As far as the donation thing goes, I agree you should have received a thank you but this is a community support forum. The people helping here are all volunteers who are donating also in the form of time and expertise. Anyone looking to trade cash for support would likely be much better off hiring a developer from the partners page.

-Ryan


rph
Expert Member

Posts

Joined
Fri Jan 08, 2010 5:05 am
Location - Lincoln, Nebraska

Post by bbcentral » Wed Apr 04, 2012 11:00 am

MrTech wrote:Don't hold your breath then, my understanding is this is a real tight group and they don't look kindly to strangers trying to tell them the way they operate is less than desirable. Bad code, not standards, non-existent and out of date documentation...
Well the code is a little messy and inefficient in places, but that's understandable given the complexity of the queries. The OpenCart GetProducts() query could be cleaned up, but it's still dramatically better than the equivalent in VirtueMart (shop_browse_queries.php for anyone who's familiar with it). Now THAT'S bad code, several hundred lines of code just to generate a single SQL statement. We've been working with VirtueMart for a few years now, and compared to that OpenCart is an absolute dream. My web designer colleagues are able to install, configure, skin and modify an OpenCart store with almost no help from me (and even then my help is only needed when adding a custom vQMod or new payment method). The Extensions community is far better than anything VirtueMart has, at least everything is version tagged (so you're not scrolling through an endless forum topic to find out if an update was released).

Documentation is something that can be very hard to keep up to date when you're busy developing software, adding new features, fixing bugs etc. This is a problem even with much bigger software, IP.Board being a good example (commercial company with a good size staff, great at fixing bugs and adding functionality but the documentation always lags behind).
rph wrote:Development in OpenCart is pretty closed (which has its bad points and good points) but you can always lobby for specific changes.
I'm happy to post suggestions, chunks of code etc to the forums. I just worry about submitting something and then it not being seen until the code has already been changed by the developers. I'm not asking for direct SVN access, but a way to make sure I can help these guys out would be great. Even if they want to "outsource" some work to me (they write the functionality & rough code, I go through and tidy it up for them...the boring stuff :P)
rph wrote:Your tertiary assignment example seems more a matter of style than anything. I know they're already used in array value assignments which is a good fit but that's about it.
That's exactly right, and yes it's definitely a personal choice of individual developers. But I noticed both styles being used in OpenCart, and I feel like it would make vQMods much easier to work with if the code was condensed.
The best example I can think of is the SQL query in /catalog/models/catalog/product.php, inside the GetProduct() function. It's one LOOONG SQL query! When you want to modify the code in vQMod to add a single new field, it's a real pain to see exactly what this query is doing (especially because there are so many subqueries to pull out reward points, discount prices etc).

Code: Select all

$query = $this->db->query("SELECT DISTINCT *, pd.name AS name, p.image, m.name AS manufacturer, (SELECT price FROM " . DB_PREFIX . "product_discount pd2 WHERE pd2.product_id = p.product_id AND pd2.customer_group_id = '" . (int)$customer_group_id . "' AND pd2.quantity = '1' AND ((pd2.date_start = '0000-00-00' OR pd2.date_start < NOW()) AND (pd2.date_end = '0000-00-00' OR pd2.date_end > NOW())) ORDER BY pd2.priority ASC, pd2.price ASC LIMIT 1) AS discount, (SELECT price FROM " . DB_PREFIX . "product_special ps WHERE ps.product_id = p.product_id AND ps.customer_group_id = '" . (int)$customer_group_id . "' AND ((ps.date_start = '0000-00-00' OR ps.date_start < NOW()) AND (ps.date_end = '0000-00-00' OR ps.date_end > NOW())) ORDER BY ps.priority ASC, ps.price ASC LIMIT 1) AS special, (SELECT points FROM " . DB_PREFIX . "product_reward pr WHERE pr.product_id = p.product_id AND customer_group_id = '" . (int)$customer_group_id . "') AS reward, (SELECT ss.name FROM " . DB_PREFIX . "stock_status ss WHERE ss.stock_status_id = p.stock_status_id AND ss.language_id = '" . (int)$this->config->get('config_language_id') . "') AS stock_status, (SELECT wcd.unit FROM " . DB_PREFIX . "weight_class_description wcd WHERE p.weight_class_id = wcd.weight_class_id AND wcd.language_id = '" . (int)$this->config->get('config_language_id') . "') AS weight_class, (SELECT lcd.unit FROM " . DB_PREFIX . "length_class_description lcd WHERE p.length_class_id = lcd.length_class_id AND lcd.language_id = '" . (int)$this->config->get('config_language_id') . "') AS length_class, (SELECT AVG(rating) AS total FROM " . DB_PREFIX . "review r1 WHERE r1.product_id = p.product_id AND r1.status = '1' GROUP BY r1.product_id) AS rating, (SELECT COUNT(*) AS total FROM " . DB_PREFIX . "review r2 WHERE r2.product_id = p.product_id AND r2.status = '1' GROUP BY r2.product_id) AS reviews, p.sort_order FROM " . DB_PREFIX . "product p LEFT JOIN " . DB_PREFIX . "product_description pd ON (p.product_id = pd.product_id) LEFT JOIN " . DB_PREFIX . "product_to_store p2s ON (p.product_id = p2s.product_id) LEFT JOIN " . DB_PREFIX . "manufacturer m ON (p.manufacturer_id = m.manufacturer_id) WHERE p.product_id = '" . (int)$product_id . "' AND pd.language_id = '" . (int)$this->config->get('config_language_id') . "' AND p.status = '1' AND p.date_available <= NOW() AND p2s.store_id = '" . (int)$this->config->get('config_store_id') . "'");
If you open the file, you'll notice it's all on a single 2408 character line! Makes me want to cry :P
rph wrote:One thing I would disagree with you on is moving input sanitation outside SQL queries. Having it in the queries keeps you from having to search all over the place to make sure everything is cleaned up.
I guess this is the Joomla developers' preferences rubbing off on me. Instead of calling values directly from $_REQUEST, $_POST, $_GET; they use custom functions to sanitize the values before being used.
For example:

Code: Select all

$product_id = JRequest::getInt('pid',false);
if(!$product_id) {
return false;
}
$sql  = "SELECT * FROM product WHERE product_id = ".$product_id." LIMIT 1";
(I'm writing this from memory, I may have it wrong).

So it's basically saying that $product_id must be an integer, the getInt() function cleans up the variable, and if it's not set it returns false (or a default value of your choice).
CodeIgniter does something similar, to prevent XSS attacks. Of course if the SQL is laid out nicely in a readable fashion then this isn't as important, it's just when you've got:

Code: Select all

...WHERE lcd.language_id = '" . (int)$this->config->get('config_language_id') . "' AND ...
that it can be hard to follow. And in the above getProduct() query, config_language_id is used several times in the same query, so if we're typecasting it as an integer it would make sense (at least to me) to assign it to a variable before using it in 3 different subqueries:

Code: Select all

$language_id = (int)$this->config->get('config_language_id');
$sql = "...WHERE lcd.language_id = ".$language_id." AND ...";
There's even a few variables in the product search query which are passed through 2-3 different inline functions in the query:

Code: Select all

$sql = "...WHERE product_name LIKE '%".dbclean(escape(utf8_clean($keyword)))."%' ";
(Again, typing this one from memory, the function names aren't real but you get the idea).
rph wrote:Definitely agree with standardization on the SQL statements (backtick use seems to be random outside required areas).
I'm not sure which way I lean with MySQL backticks. I initially only used them for escaping, then I used them all the time in every single query...but now I'm thinking I was right the first time and the SQL is easier to read without them. I think the developers should pick one style and then I can clean up all the code to fit their chosen standard :)
rph wrote:As much as I'd love to not have to type DB_PREFIX all the time I'm not sure how you would move it without making things more complicated and potentially difficult for developers who add their own tables.
How about the Joomla method? For security reasons they use random database prefixes (it used to be jos_tablename but now it's a random string).
When you write a Joomla SQL query, it looks like this:

Code: Select all

$sql = "SELECT * FROM #__users WHERE id = ".$id." LIMIT 1";
The Database class automatically replaces the #__ with the correct prefix in the configuration file ("jos_users" in older versions, "kjov3_users" in newer versions using random prefixes).
VirtueMart had its own custom prefix, so queries would look like this:

Code: Select all

$sql = "SELECT * FROM #__{vm}_products WHERE product_id = ".$pid." LIMIT 1";
It would be converted automatically to:

Code: Select all

$sql = "SELECT * FROM jos_vm_products WHERE product_id = ".$pid." LIMIT 1";
It definitely shortens the length and complexity of the queries, but I'm open to suggestions too.

I'm making this offer to try to make everyone's lives easier, including that of the main developers. They've done an absolutely brilliant job, I'd like to help ease the strain a bit for them so they can continue working on the "fun stuff" rather than the tedious boring aspects of development :)

OpenCart enthusiast
PHP/MySQL/jQuery Developer


User avatar
Newbie

Posts

Joined
Wed Dec 07, 2011 9:26 am
Who is online

Users browsing this forum: No registered users and 3 guests