Telegraph

Form-to-mail scripts are ubiquitous. They're trivially easy to build, and they often help developers avoid building custom applications for websites that just need some basic forms.

At Boston University, hundreds of departments on campus offer thousands of forms on their websites, most doing nothing more than communicating information to the site's administrators.

To replace an aging Perl application that supported these forms, I created the powerful >Telegraph application. It's what I call a "form-to-action" script, where a configuration file tells Telegraph what actions to take with the submitted form data.

Telegraph can send mail, sure, but it also supports over three dozen other behaviors (with the ability to easily add more). Write to a CSV spreadsheet, modify records in a MySQL database, validate the user's input, apply the reCAPTCHA human-verification test, create trouble tickets for our Help Center, calculate USPS shipping costs, subscribe the visitor to a mailing list, and even collect credit card payments using Boston University's proprietary credit card system.

And this is all done with just a plain HTML form and a plain XML configuration file.

Configuration

HTML forms just include a hidden field naming an XML configuration file. Since the file is read from disk (and can easily be made private so Apache won't serve it), the configuration is private and immutable (from the browser's perspective).

Here's part of an actual (but anonymized) configuration file. Perhaps it's clear how it works already?

<telegraph binding="late">
  <directive action="require" field="first_name" />
  <directive action="require" field="last_name" />
  <directive action="require" field="email" format="email" />

  <directive action="file" filename="output/shared.csv" format="csv" fields="last_name,first_name,email" />

  <!-- Send E-Mail -->
  <directive action="email" to="example@example.com" subject="EXAMPLE" from="sender@example.com" template="email-[payment_method].txt" \

  <!-- Now that we know the tuition, how much of it are we paying by CARD (either ALL or NONE) -->
  <directive action="field" name="credit_card_amount" value="[tuition]" />
  <directive action="conditional-field" source_field="payment_method" source_value="check" target_field="credit_card_amount" target_value="0" />

  <directive special="cashier" is_test="N" is_office="N" amount="[credit_card_amount]" cashier_app_code="ABCDEF" cashier_conf_code="123456" />

  <directive action="output" thanks="regthankyou.html" />
</telegraph>

Extensible Design

While Telegraph can already do just about anything, "just about" isn't good enough. The application is a sleek object-oriented framework, making it very easy to add new directives Here's one that validates the presence of a certain field from the form, and then takes some action:

class FakeDirectiveFactory extends DirectiveFactory {
    var $name = 'fake';
    function getInstance(/* map */ $config) {
        return new FakeDirective($config);
    }
}

class FakeDirective extends Directive {
    function FakeDirective(/* map */ $config) {
        Directive::Directive($config);
    }

    function duringValidation() {
        $data =& Telegraph::getSubmittedData(); // This is the user's input
        if (!$this->hasConfig('field') || !$data[$this->getConfig('field')])
            return $this->error('This is the default error message, which forms can override in the configuration file.');
    }

    function duringOutput() {
        // Do stuff.
    }
}

Telegraph processes forms in phases, giving each form a chance to take action before validation, during validation, after validation, during output, etc. This lets us ensure the input is fully validated before we start sending confirmation e-mails, even if our valdiation actions are part of a dozen directives at different points in the configuration file.