Category: PHP

  • Block-based Forms

    Old-style WordPress forms packages do a lot of things, but then again, they really don’t do a lot of things you’d think would be really obvious. One thing you can’t really do these days is build forms directly using ordinary WordPress blocks.

    Now, there are a good half-dozen prominent forms packages in the WordPress community and some of them are quite impressive and innovative. And free, to some extent: It’s not at all unusual to find that the free version of forms packages will let you collect info from users about just about anything you can imagine, because you can label your fields and input gizmos however you like and interpret the data they collect accordingly.

    But this only works because the forms don’t really do anything with the data beyond emailing it to you.

    And then, blam!

    So, here’s the thing. I’m in the process of putting a forms package into the WordPress.org repo, and it, too, doesn’t do anything with the data beyond mailing it to you.

    There’s a twist, though, for those with a little programming know-how. You give each form a unique name. If you also create a PHP file with that form’s name and put the file in a folder that’s reserved for such things, then this file will execute before the email is sent (if, in fact, you’re having the email sent. That’s optional).

    If you use it this way, then essentially all it’s doing is helping you easily put together a conventional form using normal HTML and the regular submit process. Strangely, this is harder to do than you might think. Most of the other form packages have moved over to REST or Ajax calls to submit their forms. There are some good reasons to handle things that way, but it makes it much harder, if not impossible, to step in and take over the processing once the user clicks ‘Submit’.

    Surprisingly handy

    I was surprised to see that this, all by itself, could be pretty darned handy. My surprise stemmed, I think, from coming at the whole thing sort of backwards. I’d started by focusing on having a custom database that forms automagically wrote to on submit. The forms were just the front end for a system, which was where my real focus lay.

    But just throwing the form together quickly and then not having to deal with the baggage that the form package provider has tacked on (a different add-on for each integration, and so on) turns out to be handy, at least for me. My hope is that it’s handy for at least a few folks out there as well.

    There’s a premium product in the pipeline as well, which I guess is no surprise. It adds in the back-end stuff I mentioned above. And then things get really interesting, but that’s a topic for another post.

  • More Cursor Programming in WordPress

    I just spent about thirty minutes creating a couple of functions. I can’t really say I wrote them; it was almost entirely done by AI. I think it’s a nice example of ways in which Cursor and AI can really shine, so I thought I’d spend a couple minutes walking through how it went.

    PeakZebra (the system, not the company) has a SQL table that stores data about each of the other SQL tables that PeakZebra creates and uses. The format (for better or worse, and I suspect it’s probably worse) is that the string you’d hand off to the dbDelta function is stored in its entirety.

    The table of tables table

    While initially building PeakZebra, this made it easy for me to quickly change the fields in any of the (now rather numerous) tables I use. I’d just jump into a PHP file where each of these strings was added to the pz_table_str table. An example looks like this;

    // interaction table
    $table_name = $wpdb->prefix . "pz_interaction";
    
    // interaction table
    $item = array();
    $item['id'] = null;
    $item['table_name'] = 'pz_interaction';
    $item['field_string'] = "CREATE TABLE $table_name (
      id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
      per_id bigint(20) NOT NULL DEFAULT 1,
      summary varchar(255) NOT NULL DEFAULT '',
      details varchar(800) NOT NULL DEFAULT '',
      created varchar(12) NOT NULL DEFAULT '',
      PRIMARY KEY  (id)
    ) $charset;";
    
    handle_def_record($item);
    dbDelta($item['field_string']);

    the handle_def_record($item) call writes the string to the definition table. Then dbDelta actually creates the file.

    If you’re not familiar with dbDelta, it’s pretty handy. It creates the file you’ve described if it doesn’t exist. But if it does exist, it examines it and adds or deletes fields as needed based on comparing the existing file to your CREATE TABLE string.

    I wanted to add a way for users to add fields to tables (and perhaps to create new tables), so I wanted to grab an existing string from the table with the defining strings in it, parse it into the individual fields, and then present a form where new fields could be added (deciding to worry about deleting fields later).

    Toward that end, I opened the PHP file of a “table” block I’d recently created and gave Cursor this prompt:

    let's add a function called read_table_def that reads the record with the table_name field equal to 'pz_person' and returns the field_string value for that row. 

    Cursor gave me this:

    // ... existing code ...
    
    function read_table_def() {
        global $wpdb;
        $table_name = 'pz_person';
        
        $result = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT field_string FROM {$wpdb->prefix}pz_table_def WHERE table_name = %s",
                $table_name
            )
        );
        
        return $result;
    }

    Some things of interest here:

    1. Although it could confer that I was working within WordPress from the file I’d opened, I didn’t explicitly say so.
    2. It figured out and knew enough to know that it would need the $wpdb global.
    3. It wisely uses $wpdb->prepare(), something I’ll admit I’m often to lazy to bother with.

    Now, the code it supplied was correct, but I’d screwed up and told it the wrong file name, insofar as I’d forgotten the WordPress database prefix.

    Debugging and debugging the debugging

    As a result, I asked it to add a bunch of debugging code, which it did, leading me to realize, within ten minutes or so, that I’d screwed up.

    At that point, I asked it to add the prefix, and it did:

    function read_table_def($table_name) {
        global $wpdb;
        
        // Debug: Print the actual query
        $query = $wpdb->prepare(
            "SELECT field_string FROM wp_pz_table_def WHERE table_name = %s",
            $table_name
        );
        echo "Query: " . $query . "<br>";
        
        // Debug: Print the table contents
        $all_records = $wpdb->get_results("SELECT * FROM wp_pz_table_def");
        echo "All records: <pre>";
        var_dump($all_records);
        echo "</pre><br>";
        
        $result = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT field_string FROM wp_pz_table_def WHERE table_name = %s",
                $table_name
            )
        );
        
        // Debug: Print the result
        echo "Result: ";
        var_dump($result);
        echo "<br>";
        
        return $result;
    }

    Of course, I didn’t want the prefix hardcoded. An easy fix. I asked Cursor to remove the debug code, which it did. Then I moved on to a function that would take the string and parse it into an array of individual field definitions.

    I’ll skip over the details, but once again, I’m fascinated by the things it “figures out” on its own, for example, telling me that it’s making the assumption that the fields are comma delimited, but it’s an easy fix if it’s not right about that.

    It’s strangely like working with an actual mental process. It’s probably terrible news for junior programmers, if for no other reason that it pretty much never screws up things like the parameters and formats of arcane system function calls.