Quick Sprite Builder

Sprites allow you to combine many images into a single file, reducing both the number of requests and bandwidth required to deliver pages.

I had 51 images, each was about 10K, so the total was about 510K.

These images had dimensions of about 250px width and 125px height.

I wanted to combine them all into a single image, and generate the CSS to compute the offsets into the sprite.


#!/bin/bash

# Remove the prior appended image
rm appended.jpg

# Create a list of all the original jpg files
ls *.jpg > jpg_files

# Resize all the images to ensure they have the same height and width
for f in $(cat jpg_files); do
        convert "$f" -resize 250x125! +repage "$f";
done

# Break the list into rows of 10 images
split -l 10 jpg_files jpg_row.

# Generate the ImageMagick command to append the images into rows 
c=convert
for f in $(ls jpg_row.*); do 
        h=`cat "$f" | tr '\n' ' '`
        c="$c"' ( '"$h"' +append ) '
done
# Combine the rows into the appended image, reduce the quality to save space
c="$c"' -background transparent -append -quality 70  appended.jpg'
`$c`

echo '.tag{height:125px;width:250px;overflow:hidden;background-color:transparent;background-image:url("appended.jpg");}' > ap.css

# Generate the CSS
r=0
for f in $(ls jpg_row.*); do 
        c=0
        for g in $(cut -f 1 -d '.' "$f"); do
                echo ."$g"'{background-position:-'$((c*250))'px -'$((r*125))'px;}' >> ap.css
                c=$((c+1))
        done
        r=$((r+1))
done

The final image was about 260K, still large, but the quality is good. Compressed for transfer, this image will serve well.

This code isn’t generalized, if you would like to use it, you’ll need to adjust the image dimensions and the number used to calculate the offsets.

XML Fed Form Interface

This is a demonstration of how you can use XML to feed PHP to generate a form. It is helpful when a flexible form interface is needed. This example includes the name of the field, the length, a regex validation string, default value, label for the input, whether it is required or not, and error text. It could be extended have multilingual validation and error text.

<?xml version="1.0" encoding="utf-8" ?>
<interface>
        <name>Works</name>
        <fields>
                <field>
                        <name>URL</name>
                        <length>255</length>
                        <validation>[\w\.\-]{2,255}</validation>
                        <default>domain.com</default>
                        <label>URL</label>
                        <value>url.com</value>
                        <required>true</required>
                        <errortext>Letters, numbers, periods and dashes only</errortext>
                </field>
                <field>
                        <name>id</name>
                        <length>11</length>
                        <validation>[\d]{1,11}</validation>
                        <default>1</default>
                        <label>Id</label>
                        <required>true</required>
                        <errortext>Ids must be all digits</errortext>
                </field>
        </fields>
</interface>

This is the PHP that reads the XML. It uses SimpleXML, which is really nice.

<?php echo '<?xml version="1.0" encoding="utf-8" ?>' ?>
<?php
$xml=file_get_contents('bw.xml');
$xmldata = new SimpleXMLElement($xml);
$bValid=true;
/* Validate the submitted data */
if (isset($_POST['submit']))
{
        $bValid=true;
        foreach ($xmldata->fields->field as $f)
                if (isset($_POST["{$f->name}"]))
                {
                        $f->value=$_POST["{$f->name}"];
                        if (!preg_match('/^'.$f->validation.'$/',$_POST["{$f->name}"]))
                        {
                                $f->invalid=(bool)true;
                                $bValid=false;
                        }
                }
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html lang="en-US" xml:lang="en-US" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>XML Form</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
label
{
font-weight:bolder;
}
input,label
{
display:block;
}
.invalid
{
color:#f00;
}
</style>
</head>
<body>
<h1>XML Form Sourcing Demo</h1>
<hr />
<h2>Generated Inputs</h2>
<!-- 
Generates the form from the XML data.  Note the use of the invalid flag for highlighting, and the display of
errortext if appropriate.
-->  
<form action="#" method="post">
<?php foreach ($xmldata->fields->field as $f) : ?>
<label <?php if (isset($f->invalid)) echo 'class="invalid"' ?> for="<?php echo $f->name?>"><?php echo (($f->required==
'true')?'*':'').$f->label ?>
<?php if (isset($f->invalid)) echo ' '.$f->errortext; ?>
<input name="<?php echo $f->name ?>" id="<?php echo $f->name ?>"
        maxlength="<?php echo $f->length ?>"
        value="<?php echo (isset($f->value)?$f->value:$f->default) ?>" />
</label>
<?php endforeach ?>
<br />
<input name="submit" type="submit" />
</form>
<h2>Raw XML Data</h2>
<!-- Display the raw XML data, for debugging/development -->
<blockquote><?php echo nl2br(htmlentities(file_get_contents('bw.xml'))) ?></blockquote>
<h2>Parsed XML</h2>
<!-- Display how SimpleXML parsed the XML, again for debugging/development -->
<?php
echo '<pre>';
var_dump($xmldata);
echo '</pre>';
?>
<h2>Updated XML (if valid)</h2>
<!-- Show how the XML was updated, if it was valid -->
<?php
if ($bValid)
        echo '<pre>'.nl2br(htmlentities($xmldata->asXML())).'</pre>';
?>
</body>
</html>

This is well-suited for applications which must use connection data into a variety of external interfaces. In this case, the data can be presented, entered, validated, and stored, then used later by a different process.

Blocking Site Visitors by User Agent

One of my sites was receiving a lot of hits with a user agent of Mozilla/4.0 (compatible; ICS), within a very short timeframe (requests within the same second), from a huge variety of IP addresses.

A quick look around showed ‘compatible; ICS’ is probably not a person or search engine.

I checked several IP addresses that used that user agent at: Project HoneyPot, and most of them were listed as potential sources of dictionary attacks and spam senders.

A common reaction to this is to block the requests by user agent, and that’s what I did, using:

RewriteCond %{HTTP_USER_AGENT} (compatible;\ ICS)
RewriteRule ^ - [F]

These must go before any other RewriteRules.

To test the site and ensure it still runs properly, I used Bots vs. Browsers, which allows you to request pages using a specific user agent string. Be sure to check the page with user agents that include the string you want to block, and those which should be allowed.

eZ publish - Creative Commons Licensed Image - Class

Using Creative Commons licensed images allows you to include some beautiful photos and illustrations on your site.

It’s really important to add the appropriate credits when you’re using the images, to comply with the license, but more importantly, to give credit to the creator. To properly credit the work, review this.

To make it easier to credit the images in eZ publish, I created a class and supporting templates. The content class package export (the link above) includes the class definition. The templates I used are below:

design/site/override/templates/full/cc_image.tpl

{* CC_Image - Full view *}
{let sort_order=$node.parent.sort_array[0][1]
     sort_column=$node.parent.sort_array[0][0]
     sort_column_value=cond( $sort_column|eq( 'published' ), $node.object.published,
                             $sort_column|eq( 'modified' ), $node.object.modified,
                             $sort_column|eq( 'name' ), $node.object.name,
                             $sort_column|eq( 'priority' ), $node.priority,
                             $sort_column|eq( 'modified_subnode' ), $node.modified_subnode,
                             false() )
     previous_image=fetch_alias( subtree, hash( parent_node_id, $node.parent_node_id,
                                                class_filter_type, include,
                                                class_filter_array, array( 'cc_image' ),
                                                limit, 1,
                                                attribute_filter, array( and, array( $sort_column, $sort_order|choose( '>', '<' ), $sort_column_value ) ),
                                                sort_by, array( array( $sort_column, $sort_order|not ), array( 'node_id', $sort_order|not ) ) ) )
     next_image=fetch_alias( subtree, hash( parent_node_id, $node.parent_node_id,
                                            class_filter_type, include,
                                            class_filter_array, array( 'cc_image' ),
                                            limit, 1,
                                            attribute_filter, array( and, array( $sort_column, $sort_order|choose( '<', '>' ), $sort_column_value ) ),
                                            sort_by, array( array( $sort_column, $sort_order ), array( 'node_id', $sort_order ) ) ) )}

<div class="content-view-full">
    <div class="class-image">

        <h1>{$node.name|wash}</h1>

        {if is_unset( $versionview_mode )}
        <div class="content-navigator">
            {if $previous_image}
                <div class="content-navigator-previous">
                    <div class="content-navigator-arrow">&laquo;&nbsp;</div><a href={$previous_image[0].url_alias|ezurl} title="{$previous_image[0].name|wash}">{'Previous image'|i18n( 'design/base' )}</a>
                </div>
            {else}
                <div class="content-navigator-previous-disabled">
                    <div class="content-navigator-arrow">&laquo;&nbsp;</div>{'Previous image'|i18n( 'design/base' )}
                </div>
            {/if}

            {if $previous_image}
                <div class="content-navigator-separator">|</div>
            {else}
                <div class="content-navigator-separator-disabled">|</div>
            {/if}

            {let forum=$node.parent}
                <div class="content-navigator-forum-link"><a href={$forum.url_alias|ezurl}>{$forum.name|wash}</a></div>
            {/let}

            {if $next_image}
                <div class="content-navigator-separator">|</div>
            {else}
                <div class="content-navigator-separator-disabled">|</div>
            {/if}

            {if $next_image}
                <div class="content-navigator-next">
                    <a href={$next_image[0].url_alias|ezurl} title="{$next_image[0].name|wash}">{'Next image'|i18n( 'design/base' )}</a><div class="content-navigator-arrow">&nbsp;&raquo;</div>
                </div>
            {else}
                <div class="content-navigator-next-disabled">
                    {'Next image'|i18n( 'design/base' )}<div class="content-navigator-arrow">&nbsp;&raquo;</div>
                </div>
            {/if}
        </div>
        {/if}

        <div class="attribute-image">
            <p>{attribute_view_gui attribute=$node.data_map.image image_class=large}</p>
        </div>

        <div class="attribute-caption">
            {attribute_view_gui attribute=$node.data_map.caption}
        </div>

        <div class="attribute-attribution">
            {'Image credit'|i18n( 'design/base' )}&nbsp;&raquo;&nbsp;{attribute_view_gui attribute=$node.data_map.attribution}
        </div>

        <div class="attribute-creative-commons-license">
                {'Creative Commons License'|i18n( 'design/base' )}:&nbsp;{attribute_view_gui attribute=$node.data_map.creative_commons_license}
        </div>

    </div>
</div>
{/let}

design/site/override/templates/line/cc_image.tpl

{* CC_Image - Line view *}

<div class="content-view-line">
    <div class="class-image">

    <div class="content-image">
        <p style="float:left;width:125px">{attribute_view_gui attribute=$node.data_map.image image_class=small href=$node.url_alias|ezurl()}&nbsp;</p><h2>{$node.name}</h2>
        {attribute_view_gui attribute=$node.data_map.caption}
    </div>

    </div>
</div>

design/site/override/templates/galleryslide/cc_image.tpl

{* CC_Image - Gallery slide view *}

<div class="content-view-galleryslide">
    <div class="class-image">

    <h1>{$parent_name|wash()}: {$node.name|wash()}</h1>

    <div class="attribute-image">
        <p>{attribute_view_gui attribute=$node.data_map.image image_class=large}</p>
    </div>

    <div class="attribute-caption">
        {attribute_view_gui attribute=$node.data_map.caption}
    </div>

    </div>
</div>


design/site/override/templates/embed/cc_image.tpl

<div class="content-view-embeddedmedia">
<div class="class-image">

<div class="attribute-image">
<p>
{if or( is_set( $link_parameters.href ),$object.data_map.attribution.has_content )}
    {attribute_view_gui attribute=$object.data_map.image image_class=first_set( $object_parameters.size,ezini( 'ImageSettings', 'DefaultEmbedAlias', 'content.ini' ), '' ) href=first_set( $link_parameters.href,$object.main_node.url )|ezurl target=$link_parameters.target link_class=first_set( $link_parameters.class, '' ) link_id=first_set( $link_parameters['xhtml:id'], '' ) link_title=first_set( $link_parameters['xhtml:title'], $object.data_map.attribution.content.data_text, '' )}
{else}
  {if is_set($object_parameters.size)}
    {attribute_view_gui attribute=$object.data_map.image image_class=$object_parameters.size}
  {else}
    {attribute_view_gui attribute=$object.data_map.image image_class=ezini( 'ImageSettings', 'DefaultEmbedAlias', 'content.ini' )}
  {/if}
{/if}
</p>
</div>

{if $object.data_map.caption.has_content}
{if is_set($object.data_map.image.content[$object_parameters.size].width)}
<div class="attribute-caption" style="width: {$object.data_map.image.content[$object_parameters.size].width}px">
{else}
<div class="attribute-caption">
{/if}
    {attribute_view_gui attribute=$object.data_map.caption}
</div>
{/if}
</div>
</div>

Template override settings. I used the default image class for some cases.

settings/siteaccess/site/override.ini.append.php


[cc_image_full]
Source=node/view/full.tpl
MatchFile=full/cc_image.tpl
Subdir=templates
Match[class_identifier]=cc_image

[cc_image_galleryslide]
Source=node/view/galleryslide.tpl
MatchFile=galleryslide/cc_image.tpl
Subdir=templates
Match[class_identifier]=cc_image

[cc_image_embed]
Source=content/view/embed.tpl
MatchFile=embed/cc_image.tpl
Subdir=templates
Match[class_identifier]=cc_image

[cc_image_embed-inline]
Source=content/view/embed-inline.tpl
MatchFile=embed-inline/image.tpl
Subdir=templates
Match[class_identifier]=cc_image

[cc_image_embed_node]
Source=node/view/embed.tpl
MatchFile=embed/cc_image.tpl
Subdir=templates
Match[class_identifier]=cc_image

[cc_image_embed-inline_node]
Source=node/view/embed-inline.tpl
MatchFile=embed-inline/image.tpl
Subdir=templates
Match[class_identifier]=cc_image

[cc_image_line]
Source=node/view/line.tpl
MatchFile=line/cc_image.tpl
Subdir=templates
Match[class_identifier]=cc_image

[cc_image_galleryline]
Source=node/view/galleryline.tpl
MatchFile=galleryline/image.tpl
Subdir=templates
Match[class_identifier]=cc_image

I tested this with a custom site design, but it was based on one of the eZ site styles, and very few of the templates were modified. It should integrate well with most designs, but customization is ‘eZ’ enough. :)

I used the standard, default eZ publish image class as the base for the templates.

Login Access Limits

After reviewing the log files for this blog, I noticed many attempts to log into it, and send bogus contact form data.

This is my blog, registration and comments are disabled. To all those who would post helpful comments and legitimate information, I’m sorry.

I access the blog administration from a very limited set of IP addresses, so, instead of wasting my time blocking access from IPs that shouldn’t be logging in, I decided to block all accesses to the administration interface, except my IP address.

This is done using server configuration directives. Refer to the appropriate documentation on blocking access.

After making the changes, be sure to test the effect. The link above is for a nice proxy service that will allow you to visit your pages with a different IP address. The pages should display fine for all navigation through the blog, except things like logging in, and perhaps the contact form. Check anything that’s important to you.

This works if you have a site, blog, or system where the authorized users are from a limited set of IP addresses. It can’t be used to protect against ‘bots and spammers on a forum or contact form. In those cases, I recommend BotScout.

For all those who have been trying to login, please go away.