Usability

In: General

22 Jan 2006

I started writing this a while back, and I figured I might as well publish it, despite being some what incomplete…

I always find that usability is a difficult topic to cover and as such, there are very limited resources that solely address this issue separate to the notion of accessibility. As the two topics are usually discussed both in context with each other, I have included and brief description of their distinctions.

Usability

Usability is a term used to denote the ease with which people can employ a particular tool or other human-made object in order to achieve a particular goal. Usability can also refer to the methods of measuring usability and the study of the principles behind an object’s perceived efficiency or elegance.

Web Accessibility

Web accessibility refers to the practice of making pages on the Internet accessible to all users, especially those with disabilities.

From experience, I have noticed a number of aspects that can aid in usability, although these may seem rather trivial, the number of headaches I have had explaining these to apparently IT literate people is an uphill struggle.

  • Selecting Options from a drop down menu
    • Always predefine a default value, to purge users to select a value.
  • Removing string literals from a text box.
    • This may seem rather silly, however from observation, some people cannot distinguish that removing an string literal they have entered into a text box is removed by holding the backspace and the delete button.

Heres some interesting analysis with labels and form alignment and why you should right align form labels?

Figure5 – Eye Movement in a Left Aligned Form. Triangular Pattern. Figure6 – Eye Movement in a Right Aligned Form.Triangular Pattern.



Lately, i’ve been reading Advanced PHP Programming, by George Schlossnagle, which I must say is an excellent book. Below is an excerpt, which I find particularly interesting on the topic of error handling.

“Production Display of Errors

How to notify users of errors is often a political issue. All the large clients I have worked for have had strict rules regarding what to do when a user incurs an error. Business rules have ranged from display of a customized or themed error page to complex logic regarding display of some sort of cached version of the content they were looking for. From a business perspective, this makes complete sense: Your Web presence is your link to your customers, and any bugs in it can color their perceptions of your whole business.

Regardless of the exact content that needs to be returned to a user in case of an unexpected error, the last thing I usually want to show them is a mess of debugging information. Depending on the amount of information in your error messages, that could be a considerable disclosure of information. “, PHP Error Handling

Along with some brief discussions with Håvard Eide on some SQL Injection Attacks, I found in a large public site, the end result was simply terminating the execution, and displaying the error. E.g.

Note: The database is using MySQL 3. MySQL supports multiple result sets, from multiple statements, although this is a flag in MySQL 4.1+. So, when upgrading we have numerous secruity exploits, that has the potential to delete or drop the entire database. Also, with more rigorous permissions on the credentials used would also help alleviate such problems.

“If $userid is passed in, unvalidated, from the end user, a malicious user could pass in this:

$userid = “10; DELETE FROM users;”;

MySQL (like many other RDBMS systems) supports multiple queries inline, if this value is passed in unchecked, you will have lost your user’s table. This is just one of a number of variations on this sort of attack. The moral of the story is that you should always validate any data in queries.”, George Schlossnagle

mysql_query(”) or die(‘MySQL Error: ‘.mysql_error());

Firstly, if there was a fatal error such as that, someone with a malicious intent can use the debugging information printed to acquire further knowledge and deduce other methods of attack. Also, from a usability point of view, why is the execution terminated? At least give something more meaningful or redirect to an errors page. Ultimately, a message such as that should never, ever, be shown on a live site.

More to the point of this post, displaying a cached version of the page is a great method, it gives the end user the content that they were looking for, and illudes the fundamental issue of deteriorating your users perception. Now, this entails a number of further questions to think about:

* What if it is a dynamic service, such as a search?
* Caching Policy and retention.

Whilst, dynamic services would be very difficult to cater for, and each case would be unique, in terms of search. Some possibilities, are to cache paged results, and apply some business logic on the retention of that cache (We don’t want to serve out-dated content), also state that the user is searching cached content and for any new terms, that haven’t been cached display an error page. This could become very convoluted, and in essence results in how critical the information is or the service!

You also don’t want to display partial pages where execution has ultimately terminated, this should be rather trivial with output buffering. However, it can be a rather gray area to associate, every possibility and as George Schlossnagle mentioned each company has different requirements.

It would be interesting to expand out and detail case examples.

I would be interested to understand better about one of the statements in the book…. “Exceptions expose the possibility of leaking memory.”, if anyone has more information on this, I would be very interested to hear further…

Further Information:

The addslashes() Versus mysql_real_escape_string() Debate
mysql_real_escape_string() versus Prepared Statements
Security Corner: SQL Injection

Update: I tested the script in my previous post to generate domains on an alternative server and it worked flawlessly (Not quite sure why, and I need to test further). However, one of the features that are missing from Plesk is to create aliases of domains. Through the Plesk API, you can create a redirect on a domain (HTTP Redirect) or a Frameset for example…

<packet version="1.3.1.0">
  <domain>
    <add>
      <gen_setup>
        <name>alternative.ajohnstone.com</name>
        <client_id>1</client_id>
        <status>0</status>
        <ip_address>70.85.198.202</ip_address>
        <std_fwd>
          <dest_url>ajohnstone.com</dest_url>
          <ip_address>70.85.198.202</ip_address>
        </std_fwd>
        <htype>std_fwd</htype>
      </gen_setup>
      <hosting>
        <std_fwd>
          <dest_url>ajohnstone.com</dest_url>
          <ip_address>70.85.198.202</ip_address>
        </std_fwd>
      </hosting>
      <prefs>
        <www>true</www>
      </prefs>
    </add>
  </domain>
</packet>

Although thats not quite what I desired, anyway heres a quick modifcation to the PleskDomainsCommand class. There are a few limitations, the first being that it needs to write to vhost.conf file in “/home/httpd/vhosts/ajohnstone.com/conf/vhost.conf” and that open_basedir allows access to that file.

  class PleskDomainsCommand extends PleskObjectCommand {

    function PleskDomainsCommand($Data=null) { parent::PleskObjectCommand($Data);  }
    function Command() { } //Stub

    function addAliasDomain($AliasedDomain, $ActualDomain) {
      $FileName= '/home/httpd/vhosts/'.$ActualDomain.'/conf/vhost.conf';
      $ServerAlias = 'ServerAlias '.$AliasedDomain."\r\n";

      if (is_writable($FileName)) {
        if (!$Handle = fopen($FileName, 'a'))        trigger_error("Cannot open file: ({$FileName})", E_USER_ERROR);
        if (fwrite($Handle, $ServerAlias) === FALSE) trigger_error("Cannot write to file: ({$FileName})", E_USER_ERROR);
        fclose($Handle);
      } else { trigger_error("The file ({$FileName}) is not writable", E_USER_ERROR); }

      $Data = "<packet version=\"{$this->Version}\"><server><get><services_state   /></get></server></packet>";
      //restart server id = web
      $Data = "<packet version=\"{$this->Version}\"><server><get><srv_man><id>web</id><operation>restart</operation></srv_man></get></server></packet>";
      parent::PleskObjectCommand($Data);
      $this->connect();
    }
  }

  $Domains = new PleskDomainsCommand($Data);
  $Domains->setOptions('https://admin:***@localhost:8443/enterprise/control/agent.php');
  $Domains->Command();
  $Domains->addAliasDomain('alternative.test.ajohnstone.com', 'ajohnstone.com');

Today I was trying to generate domains through Plesk with the RPC API, the following actually creates the domain, despite throwing the following error. Looking at the domain in plesks admin it states “Hosting (Domain has no hosting configured)”. Once configuring the domain through the “Physical hosting setup page for domain testdomaingeneration3.waidev6.com” interface page successfully added the domain. Although I still need to find out why “<htype>vrt_hst</htype>” and the rest of the “vrt_hst” information is not being set correctly. There also seems to be very little information that I could find on actually using the API besides the schema info generated by Xml Spy, which is quite strange.

Include /home/httpd/vhosts/testdomaingeneration3.waidev6.com/conf/httpd.include

<virtualhost 192.168.1.22:80>
        ServerName testdomaingeneration3.waidev6.com:80
        ServerAlias www.testdomaingeneration3.waidev6.com
        UseCanonicalName Off
...
</virtualhost>

Error response on domain creation.

<?xml version="1.0" encoding="UTF-8"?>
<packet version="1.3.1.0">
  <domain>
    <add>
      <result>
        <status>error</status>
        <errcode>2307</errcode>
        <errtext>Domain adding was failed. Error: Can`t resolve ID for IP ()</errtext>
      </result>
    </add>
  </domain>
</packet>

Anyway, its still very much incomplete as I was running out of time to complete it.

<?php
  define('DEBUG',1);

  class PleskSocketRequest {

    var $Configuration = array(
      'HOST'=>null,
      'PORT'=>null,
      'PATH'=>null,
      'USER'=>null,
      'PASS'=>null
    );

    var $Version   = null;
    var $URIScheme = null;
    var $CR        = null; //Curl Resource

    function buildHeaders() {
      $Headers = array(
        'HTTP_AUTH_LOGIN: '  . $this->get('USER'),
        'HTTP_AUTH_PASSWD: ' . $this->get('PASS'),
        'HTTP_PRETTY_PRINT: TRUE',
        'Content-Type: text/xml',
      );
      return $Headers;
    }

    function setOpt() {
      curl_setopt($this->CR, CURLOPT_SSL_VERIFYHOST, 0);
      curl_setopt($this->CR, CURLOPT_SSL_VERIFYPEER, FALSE);
      curl_setopt($this->CR, CURLOPT_HTTPHEADER, $this->buildHeaders() );
      curl_setopt($this->CR, CURLOPT_URL, $this->URIScheme);
      curl_setopt($this->CR, CURLOPT_VERBOSE, 1);

      curl_setopt($this->CR, CURLOPT_WRITEFUNCTION, array(&$this,CallBack));
      curl_setopt($this->CR, CURLOPT_POSTFIELDS, $this->Data);
    }

    function CallBack($CR, $Data) {
      echo '<pre>'.htmlentities($Data).'</pre>';
      return strlen($Data);
    }

    function connect() {
      $this->URIScheme =  $this->get('SCHEME') . '://' . $this->get('HOST').':'. $this->get('PORT'). $this->get('PATH');
      $this->CR = curl_init();

      $this->setOpt();

      curl_setopt($this->CR, CURLOPT_POSTFIELDS, $this->Data);

      $result = curl_exec($this->CR);

      if ($result == CURL_OK) {
        if (DEBUG) { print '<pre>';print_r(curl_getinfo($this->CR));print '</pre>'; }
      } else {
        if (DEBUG) { print '<pre>';print_r(curl_getinfo($this->CR));echo "nn-------------------------n" ."cURL error number:" .curl_errno($this->CR);echo "nncURL error:" . curl_error($this->CR);print '</pre>'; }
      }
      curl_close($this->CR);
      return;
    }

    function get($Key) {
      return $this->Configuration[$Key];
    }
  }
  class PleskObjectCommand  extends PleskSocketRequest {

    var $Data=null;

    function PleskObjectCommand($Data=null) { $this->Data=$Data; }
    function Command()                      { die('Not Implemented');  }

    function setOptions($Option) {
      $Options = (is_array($Option))? $Option : parse_url($Option);
      foreach($Options AS $Key=>$Value) $this->Configuration[strtoupper($Key)]=$Value;
      //print_r($this->Configuration);
    }
  }

  class PleskDomainsCommand extends PleskObjectCommand {

    function PleskDomainsCommand($Data=null) { parent::PleskObjectCommand($Data);  }

    function Command() {
      $this->connect();
      die('exec');
    }

  }
  class PleskClientCommand   extends PleskObjectCommand {}
  class PleskDNSCommands     extends PleskObjectCommand {}
  class PleskIPCommands      extends PleskObjectCommand {}
  class PleskeventsCommands  extends PleskObjectCommand {}

$Data =<<<EOF
<packet version="1.3.1.0">
  <domain>
    <add>
      <gen_setup>
        <name>testdomaingeneration5.example.com</name>
        <client_id>1</client_id>
        <status>0</status>
        <ip_address>192.168.1.22</ip_address>
        <vrt_hst>
          <ftp_login>catalina0</ftp_login>
          <ftp_password>057177a</ftp_password>
          <ftp_quota>0</ftp_quota>
          <fp>false</fp>
          <fp_ssl>false</fp_ssl>
          <fp_auth>false</fp_auth>
          <fp_admin_login>miaumiaul</fp_admin_login>
          <fp_admin_password>lalalalallaa</fp_admin_password>
          <ssl>false</ssl>
          <shell>/bin/false</shell>
          <php>true</php>
          <ssi>false</ssi>
          <cgi>false</cgi>
          <mod_perl>false</mod_perl>
          <mod_python>false</mod_python>
          <asp>false</asp>
          <asp_dot_net>false</asp_dot_net>
          <coldfusion>false</coldfusion>
          <webstat>awstats</webstat>
          <errdocs>false</errdocs>
          <at_domains>true</at_domains>
        </vrt_hst>
        <htype>vrt_hst</htype>
      </gen_setup>
      <hosting>
        <vrt_hst>
          <ftp_login>catalina0</ftp_login>
          <ftp_password>057177a</ftp_password>
          <ftp_quota>0</ftp_quota>
          <fp>false</fp>
          <fp_ssl>false</fp_ssl>
          <fp_auth>false</fp_auth>
          <fp_admin_login>miaumiaul</fp_admin_login>
          <fp_admin_password>lalalalallaa</fp_admin_password>
          <ssl>false</ssl>
          <shell>/bin/false</shell>
          <php>true</php>
          <ssi>false</ssi>
          <cgi>false</cgi>
          <mod_perl>false</mod_perl>
          <mod_python>false</mod_python>
          <asp>false</asp>
          <asp_dot_net>false</asp_dot_net>
          <coldfusion>false</coldfusion>
          <webstat>awstats</webstat>
          <errdocs>false</errdocs>
          <at_domains>true</at_domains>
        </vrt_hst>
        <htype>vrt_hst</htype>
      </hosting>
      <prefs>
        <www>true</www>
      </prefs>
    </add>
  </domain>
</packet>
EOF;

  $Domains = new PleskDomainsCommand($Data);
  $Domains->setOptions('https://root:***@localhost:8443/enterprise/control/agent.php');
  $Domains->Command();

?>

I was quite surprised to see my computers memory usage creep up to 811,316K with FireFox (1.07) open with two tabs, minimal graphics and no pages containing applets. I realise Firefox has many problems with memory usage and multiple tabs, (I’m not sure if thats still the case) and also with a large number of graphics. However i’m a little confused as to why it would be so high… I’ll give Browser.cache.memory.capacity a try and see if that helps, any one else had Firefox try to kill your computer?

http://kb.mozillazine.org/Memory_Leak

Just came across an Internet Explorer Developer Toolbar. I was hoping it would have a network protocol analyzer built in, but it looks very handy any how. Regardless you can still use NETCAP, which is included in the Windows XP Service Pack 2 Support Tools, or microsoft network monitor with windows server editions, or Ethereal, or Fiddler.

Currently it is not possible to manage multiple Exchange Accounts with a single profile and it looks like it won’t be possible in Outlook 12 either. Looks like i’ll have to keep on tapping my fingers on the desk while outlook shuts down and restarts another profile. The main problem with this is that Exchange hi-jack your rules and alerts (Which often has limits), so you have to have Exchange on its own profile. Anyway it’s always good to receive fairly prompt response. Thanks.

My Question

I have a very quick question with regard to Outlook 12’s implementation of multiple profiles and accounts. Currently I have numerous accounts that I have from previous companies that i’ve worked for that have simple POP3/IMAP accounts, Hotmail, and numerous other accounts I have on my own dedicated servers. All of which I find very frustrating to have to shutdown Outlook to switch profiles just because I have a few exchange accounts that have a seperate profile.

Will there in current/later versions be a more managable solution to this type of scenario? I find it rather tedious and expensive to shut Outlook down for such a trivial task.

Microsoft:

Thanks so much for getting in touch with me on this. It’s always good to hear feedback from our customers.

I understand the frustration around some of the limitations of our profile system. Originally, profiles were invented at a time when people only had one email account, but multiple people wanted to use the same computer. Since people didn’t need to “log on” to Windows at that point, we used profiles to let multiple people run Outlook at different times when they all use the same computer.

At this point, lots of people have multiple email accounts, and everyone logs on to his or her own account with Windows. So now people use profiles to manage different email accounts. As you point out, you can only have one Exchange account per profile.

We are certainly looking at ways to solve these problems in future versions of Outlook. There are a number of solutions we’re investigating. Unfortunately, Outlook12 doesn’t solve any of these problems in a way that will make things easier for you. I do hope that you’ll find lots of other features in Outlook12 that make you happy, though. I’d love to hear your impressions when you get the product.

Thanks again for the feedback. Staying in touch with our customers really helps us make the right decisions for our customers.

– William Kennedy
General Manager, Microsoft Outlook

Just came across this whilst creating an XML HTTP object and creating a socket to a page that doesn’t exist. I couldn’t understand what was going on when I saw that.

<!--
- Unfortunately, Microsoft has added a clever new
- "feature" to Internet Explorer. If the text of
- an error's message is "too small", specifically
- less than 512 bytes, Internet Explorer returns
- its own error message. You can turn that off,
- but it's pretty tricky to find switch called
- "smart error messages". That means, of course,
- that short error messages are censored by default.
- IIS always returns error messages that are long
- enough to make Internet Explorer happy. The
- workaround is pretty simple: pad the error
- message with a big comment like this to push it
- over the five hundred and twelve bytes minimum.
- Of course, that's exactly what you're reading
- right now.
-->

A Couple Tidbits

In: Linux

24 Dec 2005

I can never seem to remember, how to remember how to reconfigure a subdomain with plesk without looking it up, so for quick reference.

<directory /home/httpd/vhosts/ajohnstone.com>
  php_admin_value open_basedir none
</directory>

/usr/local/psa/admin/sbin/websrvmng -a

OR

/usr/local/psa/admin/sbin/websrvmng -u --vhost-name=domain.com

Also, today I learnt that you cannot attach the onmouseover event to an option element. I found this rather odd, despite never having attempted this before. I’m sure you could write a hack to attach an event.

MySQL Install multiple instances.

Create a folder called Conf with Instance.1.ini, Instance.2.ini, and Instance.3.ini.
The Port each are listening on should all differ, as well as having a different data directory.

I named these

C:\Program Files\MySQL\MySQL Server 5.0\MySQLData\Instance1


mysqld --install "Mysql-Instance-1" --defaults-file="C:\Program Files\MySQL\MySQL Server 5.0\Conf\Instance.1.ini"
mysqld --install "Mysql-Instance-2" --defaults-file="C:\Program Files\MySQL\MySQL Server 5.0\Conf\Instance.2.ini"
mysqld --install "Mysql-Instance-3" --defaults-file="C:\Program Files\MySQL\MySQL Server 5.0\Conf\Instance.3.ini"

in the ini file the commands to set these are:

  • port=3306
  • datadir=”C:\Program Files\MySQL\MySQL Server 5.0\MySQLData\Instance1″
  • port=3307
  • datadir=”C:\Program Files\MySQL\MySQL Server 5.0\MySQLData\Instance2″
  • port=3308
  • datadir=”C:\Program Files\MySQL\MySQL Server 5.0\MySQLData\Instance3″

After creating the folders, settings and executing the command lines to install mysql as a service. I found that all the services successfully started up and then terminated itself and in the folder “.\MySQLData\Instance1” etc, you should notice the following files, ib_logfile0, ib_logfile1, ibdata1 and %SystemName%.err, which displays the following error on each instance.

051220 22:16:28 [ERROR] Fatal error: Can't open and lock privilege tables: Table 'mysql.host' doesn't exist

Which simply means it cannot find the system tables. A quick resolve is to copy the entire data directory into each instance folder (Ensure that the standard instance is disabled if you wish to leave this service alone). Then the service should hopefully startup for all 3 services.

xcopy data "MySQLData/Instance1"
xcopy data "MySQLData/Instance2"
xcopy data "MySQLData/Instance3"

net start "Mysql-Instance-1"
net start "Mysql-Instance-2"
net start "Mysql-Instance-3"

PS. To remove the instances enter the following commands.

mysqld --remove "Mysql-Instance-1"
mysqld --remove "Mysql-Instance-2"
mysqld --remove "Mysql-Instance-3"

Now we can start playing with MySQL Slaves, and replication on a single server and now all thats left that I can think of is loadbalancing (On Windows 2003 Server its easy enough, as it is in Administrative Tools), Let me know if you have a solution for Windows XP.;)

For more on configuring windows see the following…
Parallel Instances of PHP on Windows
Apache & IIS Multiple Address on Port 80

About this blog

I have been a developer for roughly 10 years and have worked with an extensive range of technologies. Whilst working for relatively small companies, I have worked with all aspects of the development life cycle, which has given me a broad and in-depth experience.