Textpattern database functions

Some rare documentation for plugin authors

Apr 24, 06:20 PM

For my own reference, I went through Textpattern’s database library and made a list of the functions with a short description for each. I suspect this could be useful for others who need to extend Textpattern.

This is every function in txplib_db.php, as of version 4.0.8:


safe_pfx($table):
Adds the table prefix defined for this instance of Textpattern to $table and wraps the table name in backticks.

safe_pfx_j($table):
Does the same as safe_pfx(), but for a comma-separated list of tables.

safe_query($q='',$debug='',$unbuf=''):
Run a query and return the result handle or false. If $debug is true, the query will be logged. safe_query() will warn if the query fails and the current page is the admin interface or the site is currently in “debug” or “testing” mode. If $unbuf is true, use mysql_unbuffered_query() to get the result, otherwise use mysql_query(). safe_query() seems to be mainly intended as a resource for the other functions in this library, rather than as something to be called directly—(nearly) all of the functions below call safe_query, and the $debug argument is always just passed to it.

safe_delete($table, $where, $debug=''):
Deletes a row or rows from $table where $where.

safe_update($table, $set, $where, $debug=''):
"UPDATE $table SET $set WHERE $where".

safe_insert($table,$set,$debug=''):
Again using safe_query(), inserts a row into $table using data $set.

safe_upsert($table,$set,$where,$debug=''):
Tries to update a table row with the arguments provided, if that fails it will insert a new row.

safe_alter($table, $alter, $debug=''):
ALTERs $table. Use this to modify an existing table in the database (adding a new field, for example).

safe_optimize($table, $debug=''):
OPTIMIZEs $table. Probably not useful for plugin authors.

safe_repair($table, $debug=''):
REPAIRs $table. Again, probably (hopefully!) not useful for plugin authors.

safe_field($thing, $table, $where, $debug=''):
Runs the query provided (”SELECT $thing FROM $table WHERE $where“) and returns the first cell of the first result row.

safe_column($thing, $table, $where, $debug=''):
Returns a (number-indexed) array containing the first field from the result of the query given. If $thing includes more than one field, all of them after the first are ignored.

safe_row($things, $table, $where, $debug=''):
Returns an associative array of the first row of the results from the query generated from the arguments.

safe_rows:
Returns an array of associative array containing the complete results of the query generated from the arguments.

safe_rows_start($things, $table, $where, $debug=''):
Returns a query resource handle, which can be used to read data directly or passed through nextRow() (below), which will automatically run mysql_free_result() after the last row is returned.

safe_count($table, $where, $debug=''):
Returns the number of rows in $table matching the query $where.

safe_show($thing, $table, $debug=''):
Runs a MySQL SHOW query. Note that the query takes the format of "SHOW $thing FROM $table"—this will only work for a few possible values of $thing.

fetch($col,$table,$key,$val,$debug=''):
Returns the first row from a query: "SELECT $col FROM $table WHERE $key = $val". Very much like safe_row, except it builds the (simple) SQL WHERE clause for you.

getRow($query,$debug=''):
Yet another way to get the first row of a result set. Used as the back end of safe_row()

getRows($query,$debug=''):
Returns a query result as an array.

startRows($query,$debug=''):
Returns a MySQL result resource. Called by safe_rows_start() to do its thing.

nextRow($r):
$r is a result resource. This function returns the next row, or, if we’re at the end of the result set, false. It also frees the memory used by $r when it reaches the end.

numRows($r):
Just returns the number of rows in the query.

getThing($query,$debug=''):
Like safe_field(), returns the first cell of the first result row.

getThings($query,$debug=''):
Returns the first column of results of $query in a number-indexed array.

getCount($table,$where,$debug=''):
Returns a row count of the query.

getTree($root, $type, $where='1=1', $tbl='txp_category'):
Returns all of the children of category $root (if you want the entire hierarchy tree, set this to 'root'). $type is one of ‘file’, ‘image’, ‘link’, or ‘article’. The returned array includes (for each category):
  • 'id': The category’s primary key (an auto-increment);
  • 'name': The category’s url-friendly name;
  • 'title': The human-readable name;
  • 'level': The number of steps between this category and $root;
  • 'children': The number of children this category has; and
  • 'parent': The category’s parent.

getTreePath($target, $type, $tbl='txp_category'):
Gets all the parents of category $target. The return value is the same as for getTree(), except that the 'parent' field isn’t included.

rebuild_tree($parent, $left, $type, $tbl='txp_category') and rebuild_tree_full($type, $tbl='txp_category'):
Rebuild the category hierarchy tree. Use rebuild_tree_full() instead of rebuild_tree() to rebuild an entire tree from category 'root'

get_prefs():
Retrieves a list of preferences from the database. Shouldn’t be necessary for plugin writers—you can read the $prefs global variable instead.

db_down():
Return a MySQL error message, ready to be sent to the browser.

XSLT: Formatting nested groups of items

Apr 17, 08:01 AM

I’m in the process of builidng my first site using the Symphony CMS, which uses XSLT as its template language.

Symphony uses XSLT as it’s template language. In theory, this should make it more flexible than core Textpattern, with its limited number of built-in tags (Although the huge number of plugins available for Textpattern definitely means Txp is still capable of holding its own). That is, if I actually knew XSLT well enough to write a single template turning to The Google dozens of times for answers to newbie questions.

For the site in question, I need to display a list of categories as a set of song lyrics, divided into verses of four lines each. In this first version, I’d decided I wanted the song divided into columns of four verses each. I’ve changed my mind on this design decision, but rather than let the headache experience go to waste, I’ve decided the code might interest other, still-newer-than-me newbies. It demonstrates how to build nested lists in XSLT, which is much less intuitive than in a procedural language like PHP (or Textpattern Tags with a plugin like zem_nth).

Your first impulse, if you’re used to PHP, is to do something like this to group blocks of items together:

<p>
<xsl:template match="data/posts">
	<xsl:if test="position() mod 4 = 1"></p><p></xsl:if>
	<xsl:apply-templates />
</xsl:template>
</p>

That so doesn’t work.

The problem is, it’s not valid XML—the parser will see an xsl:if tag matched with a closing p tag and scold you (Symphony won’t even let you save a template with this markup in it—it fails the first test the CMS subjects it to).

To handle nested groups, you have to do something like this (I’m using the Blueprint CSS framework, thus the divs with class "span-8"):

<xsl:template match="/data/categorylist">
	<!-- For every sixteenth item in the list: -->
	<xsl:for-each select="entry[position() mod 16 = 1]">

		<!--Process this item and the next 16, putting them inside a div -->
		<div class="lyrics span-8">
		<xsl:for-each select=".|following-sibling::entry[position() &lt; 16]">
			<!-- With every fourth item in this block of 16: -->
			<xsl:if test="position() mod 4 = 1">
			<!-- Wrap the current item and the next four in a p -->
			<p>
				<xsl:apply-templates select=".|following-sibling::entry[position() &lt; 4]"/>
			</p>
			</xsl:if>
		</xsl:for-each>
		</div>

	</xsl:for-each>
</xsl:template>

As the comments explain, you have to grab every nth item, then apply your templates to that item plus the following n-1, or, in XPath, .|following-sibling::entry[position() &lt; 16] (’.’ means the current item in the list being processed; ‘|’ means “and”).

Mixing GET variables and segment-based addressing in CodeIgniter

Feb 19, 09:15 AM

“Since CodeIgniter does not utilize GET strings, there is no reason to allow” GET variables, the CodeIgniter User Guide informs us.

Unfortunately, occasionally there is a reason to use those variables—in my case, an in-house authentication program interfered with POST requests made by Javascripts—and it’s not entirely obvious how to make them available in your CodeIgniter app. The Guide seems to suggest enabling query strings, but while that gives you access to the $_GET array, it also cripples your existing URLs—using “query strings” essentially means choosing controllers and methods to execute using GET variables exclusively, what I usually see called “messy URLs.”

The solution is (relatively) simple, but not immediately obvious. I found the solution in this forum post (hopefully it’ll be more visible here). Add these settings to your applications config.php:

$config['uri_protocol'] = "PATH_INFO"; 
$config['enable_query_strings'] = TRUE;
$config['permitted_uri_chars'] = 'a-z 0-9~%.:_\-?=+&;';

This should give you an app that uses URI segments to map requests to controllers, but can also pull an occasional argument out of the $_GET superglobal, using the more-or-less generic, universal .htaccess settings I “borrowed” from Textpattern:

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)$ index.php/$1 [L]
</IfModule>

Paypal in Textpattern

Using smd_each to produce custom select boxes

Jan 14, 03:28 AM

Here’s another potentially-interesting Textpattern form, this one for a PayPal shopping cart. It pulls articles with a number of custom fields (for things like price and shipping weight) and uses smd_each to generate select boxes for sizes.

For the most part, this is pretty straightforward. PayPal will generate most of it for you from your merchant account, then it’s just a matter of throwing Textpattern tags where you need them and styling as appropriate.

My favorite part comes near the beginning. This uses smd_each to generate

<txp:if_custom_field name="sizes">
<p class="storesize"><strong>Size:</strong> <select name="item_name">
<txp:smd_each include="sizes" subset="2">
<option value="<txp:title /> {var_value}">{var_value}</option>
</txp:smd_each>
</select></p>

The complete form is below; it uses several of custom fields, most of them with names like “(Store) Something” to help my client remember what they’re used for (“sizes” doesn’t look like that because smd_each doesn’t seem to like custom fields with special characters or spaces—at least, I had problems with them during testing).

<form target="paypal" action="https://www.paypal.com/cgi-bin/webscr" method="post" class="paypalbutton">

<input type="hidden" name="add" value="1" />
<input type="hidden" name="cmd" value="_cart" />
<input type="hidden" name="business" value="store@example.com" />

<txp:if_custom_field name="sizes">
<p class="storesize"><strong>Size:</strong> <select name="item_name">
<txp:smd_each include="sizes" subset="2">
<option value="<txp:title /> {var_value}">{var_value}</option>
</txp:smd_each>
</select></p>

<txp:else />
<input type="hidden" name="item_name" value="<txp:title no_widow="0" />" />
</txp:if_custom_field>

<input type="hidden" name="amount" value="<txp:custom_field name="(Store) Price" default="100" />" />
<input type="hidden" name="no_shipping" value="0" />
<input type="hidden" name="no_note" value="1" />
<input type="hidden" name="currency_code" value="USD" />
<input type="hidden" name="weight" value="<txp:custom_field name="(Store) Shipping Weight" default="20" />" />
<input type="hidden" name="weight_unit" value="lbs" />
<input type="hidden" name="lc" value="US" />
<input type="hidden" name="bn" value="PP-ShopCartBF" />
<p><input class="ppbutton" type="image" src="/img/addtocart-trans.png"  name="submit" alt="PayPal - The safer, easier way to pay online!" /></p>
<img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1" />
</form>

Amazon Links in Textpattern

Oct 2, 11:21 PM

Here’s a (Textpattern) code snippet someone might find interesting; this is the form to generate the Amazon links in my right sidebar. I think it’s an elegant solution to the problem of creating Amazon affiliate links:

<li><a rel="nofollow" href="http://www.amazon.com/exec/obidos/ASIN<txp:link_url />/adamtb-20">
<img src="http://images.amazon.com/images/P<txp:chh_if_data><txp:link_description />
<txp:else /><txp:link_url /></txp:chh_if_data>.01._SY75_.jpg"  alt="<txp:link_name />"  
title="<txp:link_name />" /><br /><txp:link_name /></a></li>

The “Link URL” field contains only the ASIN, Amazon’s product ID number. That makes it possible to use it both to generate a product URL and to pull product images, a process amusingly described by Nat Gertler in Abusing Amazon images.

The one likely-confusing bit from this snippet is here:

<txp:chh_if_data><txp:link_description /><txp:else /><txp:link_url /></txp:chh_if_data>

If there is content in the link’s description field, this will assume that text is an ASIN and use that instead of the link URL. I set this up after realizing that the ASIN can’t be used to pull images for some products on Amazon (notably MP3 downloads). This makes it possible to pull an image using an alternate ASIN title.

atb_image_pulldown

Easily assign article images.

Jun 9, 06:06 AM Screenshot: atb_image_pulldown

atb_image_pulldown is a simple plugin that replaces the Article Image text box with a pulldown list showing all images in order by category and name. This should make it fairly easy to assign article images, assuming you use well-chosen names for images and categories. There’s no configuration unless you’re using another plugin to enable multiple article images, then there are some options in the plugin code to replace the pulldown list with a multi-select list box.

Download: atb_image_pulldown_v021.txt