My WooThemes Review

by Yang Yang on June 7, 2012

WooThemesAs WordPress becomes the de facto standard of web publishing, the need for quality WordPress themes have soared. There are lots of themes vendors across the web but I finally made up my mind to go with WooThemes and signed up a $125 membership with them a few months back. I never looked back. Here are the things I like or don’t like about them off the top of my head.

Good things

  1. Some of the themes look so fantastic and unique! Their themes are much more than just blogs.
  2. They retire old themes that went out of fashion. This means they are very picky of what they offer. When I first signed up, it’s about 125 themes in their repository but recently it has been downsized to 100.
  3. They UPDATE. I log back in my account at WooThemes.com every a few days and the version numbers of the themes change very frequently. Many old themes have been regularly updated and patched since being released more than 2 years ago.
  4. They release new themes every month. While it’s not as exactly as 2 themes per month as they promise, it’s approximately in that pace. And the latest ones are the best.
  5. They have very sophisticated yet easy to use backend configuration panels for every theme. Each of them comes with hundreds of different options regarding a variety of aspects. The administration is quite enjoyable.
    WOOTHEMES CONTROL PANEL
  6. They reply to EVERY THREAD in their member community with usable advice and sensible solutions in less than 24 hours. Sometimes it takes longer for advanced questions that would be assigned to a senior support staff. I asked a total of 16 questions at their community forum and all of them got resolved in less than 48 hours with clear, detailed and professional answers. I actually become better with WordPress (know many useful plugins that make my life easier writing for the web) since I started participating in their forums.
  7. As an intermediate web programmer myself, from viewing their code base and from the answers to my questions, I can tell they have designed their products (themes, theme framework and WooCommerce) well. It’s not a novice-work that’s pieced together and then duplicated over and over again. What they make is a well-formed ecosystem that’s capable of forgiving and extending. It’s both robustly functioning and beautifully usable.
  8. Very great value for money. With this coupon code:

    $25 OFF STANDARD MEMBERSHIP OR $40 OFF DEVELOPER MEMBERSHIP
    WOO2011
    Click To Open/Copy

    You would only need to pay $100 (with $25 OFF, or $40 OFF for developer membership) for a standard membership that instantly enables you to download ALL of their current themes for a month. And subsequent subscription is just $20 per month that you can choose to deactivate or re-activate any time you want without any additional charges.

Not so good things

  1. About 1/2 of the themes don’t look that good, especially old ones. This is probably because they have designed their website so beautiful that I have high expectations from their products and become too easy to be disappointed by their themes.
  2. I tried to think of things I don’t like about them for the last hour…….and I couldn’t think of any, except one time I asked a question about the product image resizing issue for one of their WooCommerce themes and got replies that insisted it was my problem rather than theirs. They turned out to be right. It was my problem because I was not familiar with the WooCommerce options. But still. If a user can be blamed for incompetence to use the software, it’s a developer fail without excuses.

Conclusion

To sum up, I give 4.8 / 5 to WooThemes. Go with them, if WordPress is your core publishing platform. Period. With WooCommerce and their e-Commerce themes, WordPress can be a very powerful online store as well. Being a member of them would make sure you stay on the decisive edge of web design trends for your web projects.

{ Comments on this entry are closed }

I’m having Kavoir moved to Knownhost and now that I have 2 different VPS packages with them, I’m qualified to have an VIP coupon to spread out for 10% off any hosting package purchased with it. It’s one of the best managed VPS hosts around if you search for their reviews on WHT. I’d highly recommend the VS-2 package that is their most cost-effective plan.

KnownHost.com – 75% Off First Month + 10% Off Lifetime

Anyway, if you do make up your mind to sign up with them, go ahead and use this coupon code to claim the 75% off first month and then 10% off lifetime discount:

KnownHost: 75% Off First Month + 10% Off Lifetime
KHBESTUPTIME
Click To Open/Copy

Other than this

I just moved to my new office with a bigger, compound space on 2 floors. Pictured here is the first floor which will serve as a salon for people who are interested in Internet marketing and online businesses. We will talk here about trends and potential opportunities and collaborate on some of them. The second floor will be the actual place where my employees work which is still being decorated and for now is only equipped with a 4-men working unit with 4 DELL Vostro All-in-One computers.

1

Eiffel Eames style plastic armchairs, you can find similar items on Amazon at here and here as well. I love green and black, with a modern tone.

2

Quite spacious, until a 3.9m sofa I ordered comes in. I’m planning to get a bigger library unit than the bookshelf to accommodate more books from Amazon.

3

How does the curtain go with the room theme?

4

Some books I bought on Amazon.

Here are 2 updated pictures of the office after the sofa has shipped in.

Photos taken by the built-in camera of Nokia E71. Not bad for a grandpa phone, eh..

In case you would travel to Xi’an, China, drop me a comment or message and I’ll be very glad to be your host! Winking smile

{ Comments on this entry are closed }

Menus are created and edited from in the WordPress backend and then called in frontend theme to be displayed by the wp_nav_menu() function. On some occasions, we may want to programmatically add one or two menu items to a menu because those menu items change frequently and it would be a chore to have to add / update them in the backend every time any of changes occur.

So how to programmatically add menu items to a WP menu on the fly?

For instance, what we have now in our theme as the top menu:

<ul id="main-nav" class="nav fl">
	<?php
	
	wp_nav_menu(array(
		'depth' => 6, 
		'sort_column' => 'menu_order', 
		'container' => 'ul', 
		'menu_id' => 'main-nav', 
		'menu_class' => 'nav fl', 
		'theme_location' => 'primary-menu', 
		'items_wrap' => '%3$s'
	));
	
	?>
</ul>

Firstly, we need to get the HTML returned by wp_nav_menu() by setting the parameter ‘echo’ => false. This way, we can get the menu HTML as a PHP variable and then manipulate it however we want. What I want to do is to add some sub menu items to the “Shop” item:

<ul id="main-nav" class="nav fl">
	<?php
	
	$my_wp_nav_menu = wp_nav_menu(array(
		'depth' => 6, 
		'sort_column' => 'menu_order', 
		'container' => 'ul', 
		'menu_id' => 'main-nav', 
		'menu_class' => 'nav fl', 
		'theme_location' => 'primary-menu', 
		'items_wrap' => '%3$s', 
		'echo' => false
	));
	
	$my_wp_nav_menu = str_replace(
		'>Shop</a></li>', 
		'>Shop</a><ul class="sub-menu">'.$my_sub_items_html.'</ul></li>', 
		$my_wp_nav_menu
	);
	
	echo $my_wp_nav_menu;
	
	?>
</ul>

The key is to insert a sub menu in there. In this case, I used the str_replace() function to do this. Make sure you use appropriate classes for <ul></ul> and <li></li> or they won’t be rendered as a sub menu.

$my_sub_items_html contains the sub menu items such as this:

<li class="menu-item menu-item-type-post_type menu-item-object-page"><a href="/product-category/bags/">Bags</a></li>
<li class="menu-item menu-item-type-post_type menu-item-object-page"><a href="/product-category/accessories/">Accessories</a></li>
<li class="menu-item menu-item-type-post_type menu-item-object-page"><a href="/product-category/toys/">Toys</a></li>

These can be dynamically generated from one of the taxonomies of your WordPress blog or you can specify your own.

{ Comments on this entry are closed }

The default image header and link on the login / register page of WordPress is a WordPress one, apparently, but when you are enabling registration / login to general users, you would want to use your own logo and website URL as the header rather than the default -  so people don’t get confused and freak out!

It’s amazing how WordPress has made such simple customizations even simpler by the introduction of hooks. Just insert the following snippet in your theme’s functions.php:

add_action('login_head', 'my_custom_login_logo');
function my_custom_login_logo() {
    echo '<style type="text/css">
        h1 a { background-image:url('.dirname(get_bloginfo('stylesheet_url')).'/my_custom_login_logo.png) !important; }
    </style>';
}

add_filter('login_headerurl', 'my_custom_login_link');
function my_custom_login_logo(){
    return ('/'); // put here your website URL such as http://www.example.com/ or simply /
}

Just provide your own logo and upload it to your theme directory as my_custom_login_logo.png. You can also use get_bloginfo(‘template_directory’) for theme path but that would return the parent theme path if you have a child theme. Sensibly, we would put all customizations in the child theme, so dirname(get_bloginfo(‘stylesheet_url’)) is probably a better approach.

Without editing the core files, this solution is upgrade-proof and you don’t have to install any plugin.

{ Comments on this entry are closed }

Firefox has come with a tremendously useful feature that is the spell checking. However the problem is, by default, it only checks the spelling of text in <textarea></textarea> the block text input. How to make it also check the spelling of <input type=”text” /> so that we can be worry-free of misspelled words in single-line input text boxes as well such as when you are entering article titles, product names, etc.?

There is a very simple way to do this. Just fire up your Firefox, type in the address bar and enter:

about:config

And then filter by:

layout.spellcheckDefault

So that you would see a configuration line like this:

firefox spell checker settings

Double click on the Value and change it from 1 to 2. That’s it.

You don’t even have to restart Firefox and it will start inspecting all single-line input text boxes for wrong spellings from now on.

{ Comments on this entry are closed }

Cannot use ctrl-c. How to stop tail -f, etc?

by Yang Yang on May 7, 2012

When I ssh into my Debian Squeeze server and start up tail -f to watch a log file or anything else which uses ctrl-c to exit/stop, ctrl-c does not work. It prints the ^C character on screen and just keeps right on going.

Is there a setting somewhere that can be tweaked or maybe a different key combo that needs pressed?

Search results suggest that it’s a pretty common problem to Debian and that tinkering with the getty settings in /etc/inittab can fix it, but I’m hesitant to mess around in there too much. I may just take a snapshot and then see what happens.

Definitely an inittab thing, in case anyone else runs into this.
This line used to be at the top of the getty stuff

Code:

8:2345:respawn:/sbin/getty 38400 hvc0

I just moved it to the bottom and now ctrl-c works when connected using ssh.

Code:

 1:2345:respawn:/sbin/getty 38400 tty1
 2:23:respawn:/sbin/getty 38400 tty2
 3:23:respawn:/sbin/getty 38400 tty3
 4:23:respawn:/sbin/getty 38400 tty4
 5:23:respawn:/sbin/getty 38400 tty5
 6:23:respawn:/sbin/getty 38400 tty6
 8:2345:respawn:/sbin/getty 38400 hvc0

{ Comments on this entry are closed }

Sometimes the text files are very large and you don’t want to load it in memory before searching through it because it’s inefficient. You just want to get the last few bytes of the file because the latest additions / updates are there.

This would help you:

$fp = fopen('somefile.txt', 'r');
fseek($fp, -50, SEEK_END); // It needs to be negative
$data = fgets($fp, 51);

Which gets the last 50 bytes of the file somefile.txt in $data. Thanks to niels for the solution.

Make sure the 2nd parameter for fgets() is 1 more than the absolute value of the 2nd parameter for fseek().

{ Comments on this entry are closed }

Apache is a marvelous web server that offers .htpasswd and .htgroup for controlling restricted access to your website. By help of .htaccess, they work as a member login & user management system that is so simple and easy to deploy. You can even define user groups / roles with it.

Basically,

  • .htpasswd defines pairs of username & password that are user accounts.
  • .htgroup defines groups / roles of user accounts that can be access-controlled as a whole.
  • .htaccess then applies .htpasswd and .htgroup to the current directory, and specifies which groups in .htgroup has access to the current directory.

For example, we have

/home/myuser/.htpasswd

user1:{SHA}kGPaD671VNU0OU5lqLiN/h6Q6ac=
user2:{SHA}npMqPEX3kPQTo+x/+ZckHDrIcQI=
user3:{SHA}q1Fh2LTUjjkncp11m0M9WUH5Zrw=

/home/myuser/.htgroup

admin: user2
editor: user1 user3
writer: user3

/home/myuser/public_html/example.com/member/.htaccess

AuthName "Members Area"
AuthType Basic
AuthUserFile /home/myuser/.htpasswd
AuthGroupFile /home/myuser/.htgroup
<Limit GET POST>
require group admin
require group writer
</Limit>

What they do is only let users in the admin and writer group, that is user2 and user3, to access example.com/member. When someone tries to access example.com/member, Apache would prompt him or her for user name and password, and he or she must be either user2 or user3 to access it – they must enter the correct password set out in .htpasswd for user2 or user3.

user1 isn’t allowed to access example.com/member even if the password is correct. You get the idea.

You can place .htaccess anywhere in your website, and it will control access to the directory it’s in by the defined rules (which groups / roles are allowed to access). Just make sure it is pointing to the right .htpasswd and .htgroup by AuthUserFile and AuthGroupFile.

And you can have multiple .htaccess in different directories of your website, using the same .htpasswd and .htgroup.

This is so simple yet so very handy in creating & managing different users and user roles (.htpasswd, .htgroup) and giving them permissions (.htaccess) in accessing different website assets.

PHP Class

Now that you are familiar with the basic authentication and native user management system in Apache, you can use this two simple PHP classes to automate tasks such as user creation, user deletion, adding user to group, and removing user from group.

class Htpasswd

class Htpasswd {
	
	private $file = '';
	private $salt = 'AynlJ2H.74VEfI^BZElc-Vb6G0ezE9a55-Wj';
	
	private function write($pairs = array()) {
		$str = '';
		foreach ($pairs as $username => $password) {
			$str .= "$username:{SHA}$password\n";
		}
		file_put_contents($this -> file, $str);
	}
	
	private function read() {
		$pairs = array();
		$fh = fopen($this -> file, 'r');
		while (!feof($fh)) {
			$pair_str = str_replace("\n", '', fgets($fh));
			$pair_array = explode(':{SHA}', $pair_str);
			if (count($pair_array) == 2) {
				$pairs[$pair_array[0]] = $pair_array[1];
			}
		}
		return $pairs;
	}
	
	private function getHash($clear_password = '') {
		if (!empty($clear_password)) {
			return base64_encode(sha1($clear_password, true));
		} else {
			return false;
		}
	}
	
	public function __construct($file) {
		if (file_exists($file)) {
			$this -> file = $file;
		} else {
			die($file." doesn't exist.");
			return false;
		}
	}

	public function getUsers() {
		return $this -> read();
	}
	
	public function addUser($username = '', $clear_password = '') {
		if (!empty($username) && !empty($clear_password)) {
			$all = $this -> read();
			if (!array_key_exists($username, $all)) {
				$all[$username] = $this -> getHash($clear_password);
				$this -> write($all);
			}
		} else {
			return false;
		}
	}
	
	public function deleteUser($username = '') {
		$all = $this -> read();
		if (array_key_exists($username, $all)) {
			unset($all[$username]);
			$this -> write($all);
		} else {
			return false;
		}
	}
	
	public function doesUserExist($username = '') {
		$all = $this -> read();
		if (array_key_exists($username, $all)) {
			return true;
		} else {
			return false;
		}
	}
	
	public function getClearPassword($username) {
		return strtolower(substr(sha1($username.$this -> salt), 4, 12));
	}
	
}

class Htgroup

class Htgroup {
	
	private $file = '';
	
	private function write($groups = array()) {
		$str = '';
		foreach ($groups as $group => $users) {
			$users_str = '';
			foreach ($users as $user) {
				if (!empty($users_str)) {
					$users_str .= ' ';
				}
				$users_str .= $user;
			}
			$str .= "$group: $users_str\n";
		}
		file_put_contents($this -> file, $str);
	}
	
	private function read() {
		$groups = array();
		$groups_str = file($this -> file, FILE_IGNORE_NEW_LINES);
		foreach ($groups_str as $group_str) {
			if (!empty($group_str)) {
				$group_str_array = explode(': ', $group_str);
				if (count($group_str_array) == 2) {
					$users_array = explode(' ', $group_str_array[1]);
					$groups[$group_str_array[0]] = $users_array;
				}
			}
		}
		return $groups;
	}
	
	public function __construct($file) {
		if (file_exists($file)) {
			$this -> file = $file;
		} else {
			die($file." doesn't exist.");
			return false;
		}
	}

	public function getGroups() {
		return $this -> read();
	}
	
	public function addUserToGroup($username = '', $group = '') {
		if (!empty($username) && !empty($group)) {
			$all = $this -> read();
			if (isset($all[$group])) {
				if (!in_array($username, $all[$group])) {
					$all[$group][] = $username;
				}
			} else {
				$all[$group][] = $username;
			}
			$this -> write($all);
		} else {
			return false;
		}
	}
	
	public function deleteUserFromGroup($username = '', $group = '') {
		$all = $this -> read();
		if (array_key_exists($group, $all)) {
			$user_index = array_search($username, $all[$group]);
			if ($user_index !== false) {
				unset($all[$group][$user_index]);
				if (count($all[$group]) == 0) {
					unset($all[$group]);
				}
				$this -> write($all);
			}
		} else {
			return false;
		}
	}

	public function getGroupsByUser($username = '') {
		$all = $this -> read();
		$user_groups = array();
		foreach ($all as $group => $users) {
			if (in_array($username, $users)) {
				$user_groups[] = $group;
			}
		}
		return $user_groups;
	}

}

Usage

First, you should use your own $salt. Change the value of $salt in the Htpasswd class to something else for your own application.

To instantiate the classes:

$passwdHandler = new Htpasswd('/home/myuser/.htpasswd');
$groupHandler = new Htgroup('/home/myuser/.htgroup');

To create and delete a user:

// Add a user with name 'user1' and password 'I prefer to use passphrase rather than password.' if it doesn't exist in .htpasswd.
$passwdHandler -> addUser('user1', 'I prefer to use passphrase rather than password.');
// Delete the user 'user1' if it exists in .htpasswd.
$passwdHandler -> deleteUser('user1');

To check if a particular user exists in .htpasswd:

// Check if user 'user1' exists in .htpasswd.
if ($passwdHandler -> doesUserExist('user1')) {
	// User 'user1' exists.
}

To get the clear text password for a particular user (Apache stores passwords in .htpasswd as encoded strings):

// Get the clear password for user 'user1'.
echo $passwdHandler -> getClearPassword('user1');

To add a user to a group:

// Add user 'user1' to group 'admin' in .htgroup. Group will be automatically created if it doesn't exist.
$groupHandler -> addUserToGroup('user1', 'admin');

To delete a user from a group (user still exists in .htpasswd, just not associated with the group any more):

// Delete user 'user1' from group 'admin' in .htgroup. Group will be automatically removed if it doesn't contain any users.
$groupHandler -> deleteUserFromGroup('user1', 'admin');

To get a list of groups a particular user is assigned to:

/* Get an array of groups that 'user1' is a member of. */
$user_groups = $groupHandler -> getGroupsByUser('user1');

Conclusion

This ain’t concluded. It’s just an END notice. Feel free to let me know your thoughts and how my classes work for you.

{ Comments on this entry are closed }

I have previously reviewed Moredays, the cool productivity tool that is also fun, right after its Beta launch in October last year.

Since then, the digital organizer and scrapbook in one has continued improving, adding in for example the ability to sync with Google Apps, that now makes it pleasure to use. They’ve stayed true to focus on photos and graphics, which alone would warrant a second try, but if you were still hesitant, the soon-to-be-released native iOS apps should make you change your mind.

moredays screens blog on iphone

Moredays focuses on bringing the best user experience for each device. Their iPhone app makes the most of the small screen, letting you easily check what’s on your agenda for any given day or add items on the fly.

The iPad version, on the other hand, seems like a great tool for someone eager to take notes in a meeting or get an overview of the whole week ahead.

moredays

Both of these apps are in final stages of development and testing and will be released in the next few weeks. Will you be checking them out?

{ Comments on this entry are closed }

Avalanche Coupon Code (Magento Theme)

by Yang Yang on February 29, 2012

Avalanche Magento themeJake (fastdivision.com) was kind enough to offer me a coupon code so that my readers can enjoy the Avalanche Magento theme at 15% discount. In case you haven’t read my review of the Avalanche theme, it is the current sensation going on in the Magento-sphere. In the shortest words, I think it’s the best Magento theme because of 3 reasons:

  1. It is built by someone who’s insanely good at the website stuff (HTML, CSS, PHP, design, user experience, etc.). and who pursues perfection.
  2. It has unparalleled support – read my review and you will know. Better yet, join the club to experience yourself!
  3. The theme is very very good (best is a pretty strong word and I’ll leave it to you) in all aspects I can think of AND it’s being pushed to be better every day. Read Jake’s blog. He’s just finished working on and released a mobile version of Avalanche.

Coupon Code for Avalanche

OK here’s the coupon part. If you have made up your mind to purchase Avalanche, use this coupon code when checking out:

KAVOIR
Click To Open/Copy

Just enter it in the “Enter coupon code” text box at the end of the checkout page:

Avalanche coupon code for 15% off

And you will have the 15% discount off the normal pricing of $199 and $399 which would then be $169.15 and $339.15 respectively. Always nice to save a few bucks.

Enjoy!

{ Comments on this entry are closed }