site

Get Narrator for free via TrialPay

[TrialPay]I have added a new payment option to the Dejal Store: TrialPay.

This is an interesting concept. Instead of directly buying one of my products, you try or buy one of the many products offered via the TrialPay service, and get my product for free as part of the bargain. They have many products available, including ones you'd likely buy anyway, such as eBay, Blockbuster, Stamps.com, and hundreds of others.

In fact, with Valentine's Day coming up, now's a perfect time to use FTD.com for flowers and get Narrator as a free bonus!

How TrialPay works:

Initially I am only offering a Household license for Narrator via TrialPay. Based on my experience with how well it works, and feedback from my customers, I may extend it to all of my products. But for now, you can get Narrator for any number of people in your home for free — a $24 value!

To take advantage of this deal, click this button to begin:

Please help me evaluate this by contacting me if you have any thoughts on TrialPay - whether you choose to use it or not.

If you're a developer and want to find out how TrialPay can help you, you can find out more information on the TrialPay merchant site.

Featured blog posts of 2007

My blog posts often just cover new releases, but sometimes I post general-interest or developer-interest topics. Some highlights from 2007 included:

I hope you enjoyed these posts.

Dejal year in review: 2007

The year 2007 was a good one for Dejal, with lots of releases, nicely growing revenue, and other goodness. Here are some highlights from the year:

LeopardApple released Mac OS X 10.5 "Leopard" in October. It is an excellent upgrade, with hundreds of enhancements, both at the UI level and in terms of frameworks and tools for developers.

Simon: My flagship website and server monitoring tool had two significant updates, versions 2.2 and 2.3, with several fix updates bringing it to 2.3.5. These versions added the versatile Script service and notifier, plus several other plug-ins and lots of other improvements.

Time Out: Version 1.5 of this popular freeware break reminder app was released, with 1.5.1 as the latest release as of this writing. This version added a much improved icon, layout tweaks, and other changes.

Caboodle: This handy snippet-keeper app was updated to version 1.1, with the current release being 1.1.3. These releases added import and export functions, enhanced printing, and many other improvements.

Narrator: My fun speech synthesis app hasn't been updated for a number of years, but I've been working on a rewrite of it using Leopard technologies, which is currently in private alpha testing, and will be available publicly soon.

BlogAssist: This useful HTML markup tool was updated to version 2.1, with 2.1.2 the latest release. It added a very useful Services menu command, allowing inline replacement of text -- something I use daily. Plus other changes.

Macfilink: My affiliate link cloaking app was updated to version 1.4, with 1.4.2 the latest currently. It didn't have many changes, since it is a very simple app that does one job well, but it had some tweaks.

Dejal site: The Dejal web site also had a number of improvements in 2007. Back in January, the entire site was migrated to a new server, driven by a hybrid of custom PHP code and the Drupal CMS. It uses my custom code for the product and store pages, among others, and Drupal for the remainder -- basically any page with a navigation menu or login fields in the sidebar is a Drupal-powered page. Using Drupal allows easy blog posting, forums, FAQs, plus combined account management and commenting. I also added a Developer section, where I share some of my Cocoa code for other Mac developers. The screenshot slideshows were also improved, using JavaScript instead of reloading the page each time, and I added JavaScript to the Store pages to make them prettier. Speaking of the Store, I also implemented PayPal's Instant Payment Notification service, allowing purchases to be processed promptly and automatically.

2007 was also an interesting year in terms of Mac events. I attended Macworld for the first time (as an attendee only, not an exhibitor), which was quite interesting. I also attended my second WWDC.

What will 2008 bring? I don't want to pre-announce too much, but you can expect updates to all of my current apps, some of them major upgrades with significant new looks. One such is Time Out version 2, which I'm really excited about. Another is Narrator 2, which will be going into public beta testing very soon.

Having RSS issues

I'm having issues with my RSS feed. Sorry about that. I think I've fixed it, so this is a post to see if it's working now, as well as to apologize for the issues.

I think it happened as a result an update to the Drupal CMS I use.

Possible Dejal site downtime this week

The Dejal site will be moving to a new server at a new data center sometime this week, as part of a planned migration that my web host is performing. Hopefully this will result in improved reliability and performance of the server. But in the meantime, the site may be offline for a while.

They assure me that there shouldn't be any noticeable downtime, or only for a few minutes. But you never know with such operations, so I thought I'd provide warning here. Apologies in advance if you try to access the Dejal site and get an error. Please just try again later if you do.

Rest assured that I will be closely monitoring the situation (via Dejal Simon, of course!), and will do what I can to minimize any downtime.

Tweet tweet tweet

You may have heard of this little thing called Twitter on the intertubes. It's a site where people can post short (140 characters or less) updates on what they're doing, which can be viewed on the web, cellphones, etc. People can "follow" others they're interested in, and others can follow what you're up to. You know, one of those social networking thingies.

I've resisted the siren song of Twitter for quite a while, since I've been somewhat dubious about the value of such short updates, but it does seem quite popular, so I've finally given in. I've enjoyed reading about events in the lives of other Mac developers and Mac bloggers, and it's been useful to get a feel for the C4[1] conference last weekend, which I wasn't able to attend, unfortunately.

You can now see what I'm up to via my Twitter page. We'll see how it goes.

Commenting now requires login

I've resisted this move as long as possible, but commenting on Dejal blog entries, forums, etc now requires logging in. I tried using a captcha, and back-end spam detection software, and an approval queue, but while each of those helped keep the spam off the site, I've still had to wade through hundreds of spam comments.

I've noticed that most people seem happy to create an account on the site anyway, so this step shouldn't affect too many people. Creating an account is free and easy, and allows much easier access for subsequent comments, allows posting new forum topics, and allows tracking posts so you're notified when there's a reply, if you so desire.

If you don't want to create an account, you can always contact me privately instead.

Prompt PayPal payment processing, part 3

Here's the remainder of the PHP code for the recent automation of my PayPal-based store, using PayPal's Instant Payment Notification (IPN) service. See part 1 for the introduction and part 2 for the start of the code.

So now we've got a valid transaction, so can begin to process it. As I previously said, I use a shopping cart, so I need to loop over the cart contents. I call the getPayPalShoppingCartValue() function (provided in part 2) to get values specific to a product; shared values can be copied easily. This should all work without a shopping cart, too. I save the values into a $license associative array, which is equivalent to a Cocoa NSDictionary.

    $count = $transaction['num_cart_items'];
   
    if ($count < 1)
        $count = 1;
   
    // Process each shopping cart item as a separate license:
    for ($index = 0; $index < $count; $index++)
    {
        // Construct the license:
        $license = array();
       
        $license['Parser'] = 'PayPal';
        $license['Parameters'] = $transaction;
        $license['Name'] = $transaction['first_name'] . " " . $transaction['last_name'];
        $license['Email'] = $transaction['payer_email'];
        $license['Postal'] = $transaction['address_street'] . ", " . $transaction['address_city'] . ", ";
        $license['Postal'] .= $transaction['address_state'] . " " . $transaction['address_zip'] . ", " . $transaction['address_country'];
        $license['PaymentAmount'] = getPayPalShoppingCartValue($transaction, 'mc_gross', $index);
        $license['TotalAmount'] = $transaction['mc_gross'];
        $license['Quantity'] = getPayPalShoppingCartValue($transaction, 'quantity', $index);
        $license['ProcessorNote'] = $transaction['payment_date'];
        $license['ProcessorTransID'] = $transaction['txn_id'];
        $license['ProcessorName'] = 'PayPal';
        $license['PaymentMethod'] = 'PayPal';
       
        $ref = getPayPalShoppingCartValue($transaction, 'option_selection1', $index);
       
        if (!$ref)
            $ref = 'paypal';
       
        $license['Referrer'] = $ref;
        $license['Special'] = getPayPalShoppingCartValue($transaction, 'option_selection2', $index);
       
        if ($testMode)
            $license['Test'] = 1;
       
        $license['ServerReferrer'] = $_SERVER['HTTP_REFERER'];
        $license['ServerIP'] = $_SERVER['REMOTE_ADDR'];
        $license['ServerAgent'] = $_SERVER['HTTP_USER_AGENT'];
       
        $itemName = getPayPalShoppingCartValue($transaction, 'item_name', $index);
        $isDonation = strcontains($itemName, 'Donation', true);
       
        setLicenseProduct($license, $itemName);

The setLicenseProduct() function simply maps the shopping cart's item name to my own product identifier.

Time to actually add the license. This is done with another function to be left as an exercise for the reader, addLicense(). It takes the $license array we just constructed, adds it to the license database, and returns (by reference) any error. If the sale was a donation for a freeware product or a pending sale, though, the add is bypassed, since a donation doesn't need a license, and a pending sale (if it reaches here) shouldn't be issued one.

We then construct an email to the customer, with different wording for freeware, pending, or normal sales. I'm omitting most of that, since it's specific to your situation:

        // Add the license, unless it's a donation or pending:
        if ($isDonation || $isPending || addLicense($license, $error))
        {
            $productName = $license['ProductName'];
            $version = $license['Version'];
            $name = $license['Name'];
            $email = $license['Email'];
           
            // Donations and pending transactions will use the item name for the product name,
            // as the latter is set in updateLicense(), which isn't called for them:
            if (!$productName)
                $productName = $itemName;
           
            if ($isDonation)
                $body = "Greetings.  Thank you for the $productName!  I appreciate it.\n\n\n";
            else if ($isPending)
            {
                $body = "Greetings.  Thank you for purchasing a $productName.\n\n\n";
                $body .= "Your PayPal transaction is still pending, so you will receive your license when the payment is complete.\n\n\n";
            }
            else
            {
                $body = "Greetings.  Thank you for purchasing $productName.\n\n\n";
                $body .= "Licensed Name: $name\n";
                $body .= "Licensed Email: $email\n";
                $body .= "License Kind: " . $license['KindName'] . "\n";
                $body .= "$productName $version Serial Number: " . $license['Serial'] . "\n\n\n";
                // Instructions on adding the license omitted...
            }
           
            // Info on forums, FAQ, email contacts, etc omitted...
           
            $headers = "From: MyCompany <sales@mycompany.com>\r\n";
            $headers .= "Bcc: MyCompany <sales@mycompany.com>\r\n";
            $headers .= "X-Mailer: PHP/" . phpversion() . "\r\n";
           
            if ($isDonation)
                $subject = $productName;
            else if ($isPending)
                $subject = "$productName purchase";
            else
                $subject = "$productName license";
           
            mail("$name <$email>", $subject, $body, $headers);
        }
        else if ($error)
            exitWithError('PayPal', $error, $license);
    }
   
    header("Content-Type: text/text");
    echo("Transaction completed.");
}

And we're done! I hope this is helpful. This all seems to work fine for me, but if you spot any bugs or have any suggestions for improvements, please let me know. If you have any questions, I'd be happy to elaborate more. I'd be keen to hear from people who use this code, too.

Prompt PayPal payment processing, part 2

As discussed in part 1, I recently automated my PayPal-based store, using PayPal's Instant Payment Notification (IPN) service.

For those interested in the technical details, perhaps implementing this for your own store, here's my code. This is written in PHP, but other languages can be used too.

Firstly, a couple of utility functions. For getPayPalShoppingCartValue(), given the PayPal transaction data, a key, and the (zero-based) index, this returns the corresponding value. Tries the key with the index appended, with an underscore and the index appended, or by itself; the documentation is somewhat inconsistent on how it is applied, though I think an underscore is usually used. All this code should still work fine if you aren't using the shopping cart, too.

function getPayPalShoppingCartValue(&$transaction, $key, $index = 0)
{
    $value = $transaction[$key . ($index + 1)];
   
    if (!$value)
        $value = $transaction[$key . '_' . ($index + 1)];
   
    if (!$value)
        $value = $transaction[$key];
   
    return $value;
}

Next, the exitWithError() function emails me the details of an error, for diagnostic purposes, then exits the script. You can optionally pass an array and it will be included in the email. It also outputs the error (normally wouldn't be seen). It calls another existing function of mine (not provided here) that returns the array as an ASCII property list; there are other ways to output it too. Of course, you should replace the mycompany.com email addresses with your own.

function exitWithError($parser, $error = '', $array = null)
{
    $body = "An error occurred with the $parser parser:\n\n$error\n\n";
   
    if ($array)
        $body .= arrayToASCIIPropertyList($array);
   
    $headers = "From: MyCompany <info@mycompany.com>\r\n";
    $headers .= "X-Mailer: PHP/" . phpversion() . "\r\n";
   
    mail("info@mycompany.com", "Store error: $error", $body, $headers);
   
    header("Content-Type: text/text");
    echo("$parser error: $error");
   
    exit();
}

On to the main code. I actually have this in a function in my code, as it is just one processor function among others, but if you only have one, it can be at the top level of the script.

function handlePayPal()
{
    // Copy to a local variable for convenience, and since the post back to PayPal might wipe it:
    $transaction = $_POST;
   
    // Test mode is activated by passing test=1 to my PayPal Store.  It then uses PayPal's sandbox site instead:
    $testMode = $transaction['test_ipn'] == 1;
   
    if ($testMode)
    {
        mail("MyCompany <sales@mycompany.com>", "PayPal IPN starting", arrayToASCIIPropertyList($transaction), "From: MyCompany <sales@mycompany.com>\r\n");
       
        $paypalDomain = 'www.sandbox.paypal.com';
        $receiverEmail = 'paypal_sandbox_biz@mycompany.com';
    }
    else
    {
        $paypalDomain = 'www.paypal.com';
        $receiverEmail = 'paypal@mycompany.com';
    }

The above sets up things based on whether the purchase was via the PayPal sandbox or your live store. The sandbox is a great way to create fake customer and seller accounts for testing without spending real money. You can initiate this test mode by changing the action on your form from <https://www.paypal.com/cgi-bin/webscr> to <https://www.sandbox.paypal.com/cgi-bin/webscr>. You also need to change the form's "business" field to the sandbox receiver email address.

Next up, we post the received data back to PayPal, so they can confirm that they actually sent it. We construct and post urlencoded form data to their server, then fetch the response. If it's VERIFIED, we're good:

    // Read the post from PayPal system and add 'cmd':
    $req = 'cmd=_notify-validate';
   
    foreach ($_POST as $key => $value)
    {
        $value = urlencode(stripslashes($value));
        $req .= "&$key=$value";
    }
   
    // Post back to PayPal system to validate:
    $header .= "POST /cgi-bin/webscr HTTP/1.0\r\n";
    $header .= "Host: $paypalDomain:80\r\n";
    $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
    $header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
   
    $fp = fsockopen($paypalDomain, 80, $errno, $errstr, 30);
   
    if (!$fp)
        exitWithError('PayPal', 'Unable to connect to the PayPal server.', $transaction);
   
    fputs($fp, $header . $req);
   
    $verified = false;
   
    // Read the response:
    while (!feof($fp))
    {
        $line = fgets($fp, 1024);
       
        if (strcmp($line, "VERIFIED") == 0)
            $verified = true;
    }
   
    fclose($fp);
   
    // Ensure PayPal verified the post:
    if (!$verified)
        exitWithError('PayPal', 'Received a transaction that PayPal did not verify as valid.', $transaction);

We then do some further checks, to ensure the payment went to the correct address, and the transaction has an acceptable status. I wrote this before realizing that pending transactions probably don't reach this point anyway (I think PayPal holds off notifying till they are completed), but it doesn't hurt to leave the pending logic in here:

    // Ensure the payment went to me:
    if ($transaction['receiver_email'] != $receiverEmail)
        exitWithError('PayPal', 'The receiver email was not correct.', $transaction);
   
    $transStatus = $transaction['payment_status'];
    $isPending = ($transStatus == 'Pending');
   
    if (!$isPending && $transStatus != 'Completed')
        exitWithError('PayPal', "The transaction status was $transStatus.", $transaction);

So now we've got a valid transaction, so can begin to process it. Continue to part 3 with the remainder of the code!

Prompt PayPal payment processing, part 1

I use two payment processors for Dejal: Kagi and PayPal. The Dejal Store powered by Kagi has been fully automated for years, so when a customer buys one of my products, Kagi queries my server, which generates a serial number and passes it back to Kagi's server, and the customer receives it automatically as part of Kagi's "Thanks for your purchase" message. This is very convenient for both me and the customer. (My products also include a Kagi-based store right within the app, which avoids the need to even enter the serial number.)

For the Dejal Store powered by PayPal, on the other hand, it has been a manual process. When someone purchased via PayPal, I received a notification of the payment in my email, which I copied to my home-grown license management app, which parsed it to generate the license, including creating an email message in Mail ready to send. This wasn't much work, but required manual processing on my part, which of course meant the customer had to wait for me before they got their license. I was usually very prompt, but if they bought while I was asleep or otherwise away from my machine, they had to wait. That's just not great service.

So this week I've spent the time to automate the PayPal processing. Now, if you buy from my PayPal store, PayPal will send my server a notification with the shopping cart contents. My server will then automatically add licenses and generate and send out email messages for each product purchased. So now purchasing from either store (or within the app) will give you your license details virtually immediately, with no need for waiting for me. Which of course means less work for me, too, so I can spend more time answering support questions or writing code.

Getting technical

Implementing the PayPal automation wasn't too difficult, though it took some research to find the best solutions. For the standard PayPal business accounts, they offer two automated payment notification services: Payment Data Transfer (PDT) and Instant Payment Notification (IPN). My initial research showed people using PDT, for example that's what the AquaticPrime framework uses. However, further research showed that this might not be the best choice. The way it works is to send the transaction information to your server when the customer is redirected back to your website after the purchase. But if they close their browser window or click away before they return to your site, your server doesn't get the transaction info. Also, you have to deal with pending transactions (e.g. eChecks), form reloads, and other issues.

So then I switched to using IPN. This is a more robust mechanism, but still quite simple to implement. In this case, the payment notification occurs in the background, whether or not the customer actually returns to your site. I believe that pending transactions aren't received until they clear, too. So this is more reliable and efficient.

Both services include fraud protections, in the form of posting the received data back to PayPal and getting a response. If they originated the data, they'll reply saying that it was confirmed, otherwise it's suspicious.

To make things more interesting, my store pages offer multiple products for sale, so I need a shopping cart. Most existing examples assumed a single buy button, but fortunately the PayPal data copes with shopping cart transactions in a fairly simple way, appending digits to the variables.

Continue reading part 2, with a discussion of the PHP code to implement this.

A store highlight

I've just rolled out some minor changes to the Dejal Store, to enhance the item selection. Having recently bought a new MacBook Pro, I admired how the Apple Store makes it easy to choose the options, and highlights the selected items. (If you're familiar with the way web browsers work, you'll know that clicking on text next to a radio button doesn't normally work; you have to click the actual button.)

Anyway, I wanted to improve the Dejal Store in a similar way, since it has sets of radio buttons to select the license levels for each product. It now looks the same as before, but when you point to a license for a product, the line is highlighted in grey, and you can now click anywhere in that highlighted space to select that license. When you do, the line is nicely highlighted with a blue background. The blue highlight works for the pop-up menus and receipt checkbox, too.

A challenge was that the page allows yellow product highlighting via a page argument - e.g. as used when clicking on a Buy Now link on the product pages. That is still supported:

This was all done via CSS for the styles, JavaScript for the click handlers, and PHP to generate the page with no code duplication. Each license line on the page is constructed via a PHP function call - every page on my site has at least a little PHP magic driving it. A JavaScript onclick handler on the license text simulates a click on the corresponding radio button, and JavaScript functions alter the class of the line based on whether it is selected, highlighted, or neither. The various highlights are done in CSS via classes, and the grey highlight is done purely in CSS via the :hover meta attribute.

The JavaScript code probably isn't all that elegant, since I'm not as familiar with that language as PHP etc, but it works. I am quite impressed with the JavaScript DOM (Document Object Model); it seems quite flexible. I've only had occasion to mess with JavaScript a few times (most recently redoing the screenshot pages, and for a Dashboard widget a while back), but I expect to do a lot more JavaScript stuff in the future, both on my site and in app contexts.

All this was created in Panic's excellent Coda application; my preferred web environment now.

Dejal Developer pages: free Cocoa code

I've been developing for the Mac since about 1988, initially in Pascal, and since 2002 in ObjC/Cocoa. I've benefited from advice and comments on mailing lists like CocoaDev, and like to give back to the community. In the past I released some of my Classic Pascal code, and now I'm doing the same for some of my Cocoa code.

I am a chronic generic code writer, which I know many people frown on, but with six applications to maintain, shared code is very useful and efficient. An important part of this shared code is my Cocoa categories, that extend Apple's classes with convenience methods and new functionality.

These categories are now available to other Cocoa developers to use in your own products, if desired. The code was written over the last several years, so some of it could be replaced with more modern techniques, but hopefully a lot of it will remain useful. They are certainly used a lot in my products.

The main Developer page is at www.dejal.com/developer. The categories are organized by Foundation and AppKit, plus some utilities. You can view the code online and copy select snippets if you wish, or download .zip archives for each, or a single archive with all.

This code is generously licensed; you are welcome to use it in your own products, including commercial; all I ask is a mention in your credits or website, and that you tell me you're using it. Use as little or as much as you wish.

If you find any bugs or have some suggestions, or want to roll in improvements, please let me know. The code should all be pretty bug-free, though, being in active use. And depending on feedback, I'll probably add more code over time; I have several subclasses and new classes that are shared by all my apps that (with a little tidying up) could also be made available in the future.

I hope it's helpful for you!

More site improvements

I've made a number of further website improvements over the last week:

  • I've replaced the front page with a custom one. It now shows a large feature graphic (like Apple's site does), followed by the latest blog entry, and a link to the blog page. There's also a sidebar listing the recent blog posts for quick access.
  • Improved the page layout by eliminating the rounded corners on the boxes. Rounded corners look a little nicer, but the :before and :after metatags I was using didn't work reliably, and other approaches are too complex. I think the square corners has a certain elegance.
  • Improved the icons for the Blog and Forums header buttons.
  • Header button selection highlighting is now working for Drupal-based pages.
  • The Navigation menu (for site members) now uses DHTML to expand and collapse sub-menus without having to reload the page.
  • The Navigation menu now isn't available for guests, since all of the relevant functions are available via the header (though the menu does make accessing individual forums etc faster - a benefit of becoming a free site member!).
  • Added a Blog tags page and sidebar block (that only appears when viewing the blog) that lists blog tags, allowing filtering the blog for just those topics.
  • Added graphic captcha for guest comments, instead of the math question - it seems tidier and more reliable.
  • Changed the product screenshot slideshows to use JavaScript, again to avoid untidy page reloads.
  • Added new Developer pages, providing source code to other Cocoa developers (more on this tomorrow).

Note: due to the CSS changes, if the page header looks weird, try reloading the page to fix it.

A few more site improvements

I've just tweaked the Dejal site a little more. Improvements include:

  • Subscriptions: You can elect to be notified via email when someone (e.g. me) replies to your forum posts, or subscribe to the blog. This is in addition to the RSS feeds.
  • Improved URLs: All posts to the site now automatically have a fairly intuitive URL, instead of "node/number" as before.
  • Code Formatting: You can now type <code>...</code> tags in posts and comments, and they'll be formatted correctly.
  • Automatic translation: You can now get blog postings, forum discussions, FAQs, and some other pages on the site automatically translated into one of several languages: see the Change Language box in the sidebar. Doesn't work with the product pages.

Fun with domain names

I just added a bunch more domain names for my website.

Firstly, some app-specific variations, which all point to their respective product pages. I may use these in various places in the future, though they will probably always point to the same pages. They may help people find my apps when guessing the URL, too. Feel free to use these when linking to the products, if you want a shorter URL:

I also added some misspellings of Dejal, to help people guessing the spelling find the site:

It's a little surprising that such short domain names are still available... but they are unusual names.

Added Simon web badges

[Badge2]

These are a couple of web badges for Simon that I've just added. You are welcome to add one of these to websites that you monitor with Simon, to reassure your readers that you're watching your site for failures or comments, and help spread the good word about Simon.

[Badge1]

To use these, drag the desired image to your Desktop to download it, then upload to a suitable location on your website (please don't just link to the images on my site). Then copy the HTML code below to the part of your page where you want the badge to appear, and change the image name and/or path if necessary:

<a href="http://www.dejal.com/simon/"><img src="simon-monitor2.png" title="Learn about Dejal Simon, the essential website and server monitor for Mac OS X" /></a>

If anyone wants to create more badges, or has any suggestions for other designs, please get in touch.

Caboodle product page updated

I just rewrote the Caboodle product page to be more like the Simon one, with better descriptions of the various features and benefits of Caboodle. I hope it all makes sense - feedback welcome in the comments, Caboodle forum, or the feedback form.

Anyway, I now have a bunch of space in the sidebar, a perfect place for customer testimonials (again, like on the Simon page). Of course, I could go through my mail archives, or pull some from VersionTracker or MacUpdate, but I'd since I'm about to release version 1.1 of Caboodle, I though some tasty fresh quotes would be better.

So, if you use Caboodle and want to encourage others to give it a try, how about sending me a brief quote that I can use, along with your name and such. The feedback form has fields in the sidebar with the info needed. You can say whatever you like; I don't edit quotes, other than to make it short enough for the space. What do you like about Caboodle? What do you use it for? How has it helped you?

I'll select a bunch from the quotes I receive and post them on the site.

Thanks!

Creating forum topics now requires an account

In the ongoing saga of fighting spam, the captcha (math question) for comments seems to protect the site's comments from spammers, but it doesn't work for forum posts. So until it does, I have reluctantly disabled anonymous forum posting.

So, if you don't have an account, you can still comment on an existing forum topic, but you can only create a new topic if you first log in or create an account. Which is really easy to do!

Anonymous comments changed again

Fighting spam in site comments and forum postings is an ongoing battle. I could eliminate it all simply by limiting posting to only people with accounts on the site, but I don't want to do that if I can avoid it, since I recognize that some people prefer to remain anonymous, or don't want to take the few seconds to create an account.

So I have now added a simple captcha feature for anonymous site users (i.e. everyone who isn't logged in via an account). You'll be asked to answer a simple math question as proof that you're a real human, instead of a spam robot.

I've also allowed anonymous comment posting without moderation again, since the captcha should prevent the spam comments. Unfortunately this feature doesn't seem to work with forum posts, so I'll have to continue manually deleting the spam posts that keep appearing there.

If you have an account (which is free and easy to create!), you won't be asked for this extra validation. I recommend that everyone create an account; it'll let you post followup messages and comments much more easily, and allow you to track answers to your posts, etc.

Anonymous comments now require approval

After getting lots of spam postings of late, I've turned off automatic approval of anonymous comments. Guest users can still post comments, but they will require approval before appearing. Guests can still post anonymous forum topics, but that may change in the future if the spam continues.

You can, of course, avoid this by creating an account - which is really easy, and lets you post future forum topics or comments more easily, plus lets you track discussions and optionally get emailed when followups are posted, etc.

Syndicate content