Archive for the ‘Web Development’ Category

Export data as a CSV using PHP

Wednesday, February 10th, 2010

This is a very simple class for constructing basic comma-separated value (CSV) files in PHP. The export method forces the correct headers to initiate a file download. More information can be had in the PHP documentation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php
 
	class CSV {
		protected $data;
 
		/*
		 * @params array $columns
		 * @returns void
		 */
		public function __construct($columns) {
			$this->data = '"' . implode('","', $columns) . '"' . "\n";
		}
		/*
		 * @params array $row
		 * @returns void
		 */
		public function addRow($row) {
			$this->data .= '"' . implode('","', $row) . '"' . "\n";
		}
		/*
		 * @returns void
		 */
		public function export($filename) {
			header('Content-type: application/csv');
			header('Content-Disposition: attachment; filename="' . $filename . '.csv"');
 
			echo $this->data;
			die();
		}
		public function __toString() {
			return $this->data;
		}
	}
 
	$csv = new CSV(array('date', 'name', 'address'));
	$csv->addRow(array('2/2/2010', 'John', 'Portland, OR'));
 
        // export csv as a download
	$csv->export('names.csv');
 
        // pass the csv data to a variable as a string
        $string = $csv;
 
?>

JSON-RPC 2.0 Implementation

Saturday, December 5th, 2009

Here’s another quick braindump post. A custom JSON-RPC library I wrote for a project at my last job. It should follow the JSON-RPC 2.0 Spec pretty closely. It does require the jQuery library as well.

You can pretty much ignore the MIKU references. Basically it’s just a way of namespacing objects to make them globally available. Try reading up on the YUI library for more information.

As always, feel free to post with any questions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
var MIKU;
 
/*
 * Method to allow namespacing of new objects within MIKU;
 *
 * Example:
 * MIKU.namespace('objectname');
 * MIKU.objectname = function() { return {'...'} }
 *
 */
MIKU = function() {
  return {
    namespace: function(name) {
      try {
        if (MIKU[name]) {
          throw 'Namespace exists.';
        }
        return {}
      }
      catch (e) {
        this.throwError(e);
      }
    }
  }
}();
 
MIKU.namespace('throwError');
 
/*
 * Method to catch MIKU errors
 */
MIKU.throwError = function(e) {
  if (console) {
    console.error((e.message || 'MikuError:'), e);
  }
};
 
/*
 * Init the JsonRpc namespace into the MIKU object.
 */
MIKU.namespace('JsonRpc');
 
/*
 * Miku Json-RPC Implementation
 */
 
MIKU.JsonRpc = function() {
  var _url = 'json-rpc/call';
  var _timeout = false;
  var _requests = [];
  var _responses = [];
  var _callbacks = {};
  var _requestId = 0;
  var _failures = 0;
 
  function _send() {
    _request();
    _t = false;
    _requests = [];
  }
 
  function _request() {
    try {
      if (!_url) {
        throw('undefined post url.');
      }
      $.ajax({
        type: 'POST',
        url: _url,
        data: ({
          request: JSON.stringify(_requests)
        }),
        dataFilter: function(data, type) {
          //check for php errors
          try {
            return JSON.parse(data);
          }
          catch (e) {
            var phpError = /^.*?(Error|Warning|Notice).*?\:\s*(.*?) in .*?(\/[0-9A-Za-z\/\.\-\_\ ]+).* on line .*?([0-9]+).*$/i;
            var lines = data.split(/\n/g);
            $.each(lines, function() {
              var error = this.match(phpError);
              if (error) {
                MIKU.throwError({
                  message: error[1] + ': ' + error[2],
                  file: error[3],
                  line: error[4]
                });
              }
            });
          }
        },
        success: function(data) {
          _success(data);
        },
        error: function(XMLHttpRequest, textStatus, errorThrown) {
          // retry after connection failures
          if (XMLHttpRequest.status != '200') {
            _failures ++;
            if (_failures < 3) {
              _request();
              return;
            }
          }
 
          // give up and throw errors...
          MIKU.throwError([XMLHttpRequest, textStatus, errorThrown]);
        }
      });
    }
    catch(e) {
      MIKU.throwError(e);
    }
  }
 
  function _success(response) {
    $.each(jQuery.makeArray(response), function() {
      try {
        if (this.error) {
          // check for error
          throw(this.error);
        }
        else if(this.result) {
          // trigger callback
          var callback = _callbacks[this.id];
          callback(this.result);
        }
      }
      catch(e) {
        MIKU.throwError(e);
      }
    });
 
    // reset callbacks
    _callbacks = {};
  }
 
  function _genId() {
    return ++_requestId;
  }
 
  return {
    version: '2.0',
    delay: 10,
    setUrl: function(url) {
      _url = url;
    },
    call: function(args) {
      var id = _genId();
 
      var request = {
        jsonrpc: this.version,
        method: args.method,
        params: args.params,
        id: id
      }
 
      _requests.push(request);
      _callbacks[id] = args.onSuccess;
 
      if (_timeout) {
        clearTimeout(_timeout);
      }
      _timeout = setTimeout(_send, this.delay);
 
      return request;
    }
  }
}();
 
/*
 * Testing Below
 */
 
$(document).ready(function() {
  var rpc = MIKU.JsonRpc;
 
/*
  rpc.call({
    method: 'System.getTitle',
    params: [
      'this is a title'
    ],
    onSuccess: function() {
      console.log('w00t');
    }
  });
*/
});

Writing a custom Wordpress multi-instance widget

Thursday, December 3rd, 2009

wplogoblue-hoz-rgb

While working on a soon-to-be finished Wordpress theme for a client, I ran into a situation where it would be useful to create a custom widget that they could use to organize content on their site. Basically, they wanted to be able to select a random post or page and display some associated meta data. Essentially a custom image and content teaser. They were going to be doing this several times throughout the site, but in slightly different configurations. A post here, a page there. It seemed silly to hard-code these features. Using a widget would allow them to swap them out for a Twitter stream, or an RSS news feed in the future.

Making multi-instance widgets in Wordpress 2.8+ couldn’t be easier. Here is a good example to start with from the Wordpress codex.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class My_Widget extends WP_Widget {
	function My_Widget() {
		// widget actual processes
	}
 
	function widget($args, $instance) {
		// outputs the content of the widget
	}
 
	function update($new_instance, $old_instance) {
		// processes widget options to be saved
	}
 
	function form($instance) {
		// outputs the options form on admin
	}
}
register_widget('My_Widget');
?>

And here is my finished widget (evolved from the above example).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
<?php
/*
 * Custom mini-post widget
 */
class FGX_MiniPost_Widget extends WP_Widget {
	function FGX_MiniPost_Widget() {
		// widget actual processes
		parent::WP_Widget(false, $name = 'Floragenex MiniPost', array(
			'description' => 'Display a teaser for a post or a page.'
		));
	}
 
	function widget($args, $instance) {
		// outputs the content of the widget
			global $post;
 
			extract( $args );			
			$type = $instance['type'];
			$include = (!empty($instance['include']) ? explode(',', $instance['include']) : '');
			$category = (is_numeric($instance['category']) ? (int)$instance['category'] : '');
 
                        // Set up query for posts with the provided filters
			query_posts(array(
				'post_type' => $type,
				'post__in' => $include,
				'post__not_in' => array($post->ID),
				'cat' => $category,
				'post_status' => 'publish',
				'meta_key' => 'teaser_value',
				'meta_value' => '',
				'meta_compare' => '!=',
				'orderby' => 'rand',
				'posts_per_page' => '1'
			));
 
			echo $before_widget;
 
                        // Output widget, if a post exists that matches our query
			if ( have_posts() ) :
				while ( have_posts() ) : the_post();
					$post_meta = get_post_custom($post->ID);
					echo (!empty($post_meta['image_value'][0]) ? '<a href="' . get_page_link() . '">' .
							 '<img alt="image" src="' . get_bloginfo('template_url') . '/scripts/timthumb.php?src=' . htmlentities($post_meta['image_value'][0]) . '&h=62&w=180&zc=1" />' .
							 '</a>' : '') .
							 '<h3><a href="' . get_page_link() . '">' . get_the_title() . '</a></h3>' .
							 '<p>' . htmlentities($post_meta['teaser_value'][0]) . '</p>' .
							 '<p><a class="learn-more" href="' . get_page_link() . '">learn more</a></p>';
				endwhile;
			else:
				echo '<p>No posts found.</p>';
			endif;
 
                        // Very important to reset the query here.
			wp_reset_query();
 
			echo $after_widget;
	}
 
	function update($new_instance, $old_instance) {
		// processes widget options to be saved
		return $new_instance;
	}
 
	function form($instance) {
		// outputs the options form on admin		
		$type = esc_attr($instance['type']);
		$include = esc_attr($instance['include']);
		$category = esc_attr($instance['category']);
 
                // Get the existing categories and build a simple select dropdown for the user.
		$categories = get_categories();
 
		$cat_options = array();
		$cat_options[] = '<option value="BLANK">Select one...</option>';
 
		foreach ($categories as $cat) {
			$selected = $category === $cat->cat_ID ? ' selected="selected"' : '';
			$cat_options[] = '<option value="' . $cat->cat_ID .'"' . $selected . '>' . $cat->name . '</option>';
		}
 
		?>
			<p>
				<label for="<?php echo $this->get_field_id('type'); ?>">
					<?php _e('Content type:'); ?>
				</label>
				<select id="<?php echo $this->get_field_id('type'); ?>" class="widefat" name="<?php echo $this->get_field_name('type'); ?>">
					<option value="post"<?php echo ($type === 'post' ? ' selected="selected"' : ''); ?>>Post</option>
					<option value="page"<?php echo ($type === 'page' ? ' selected="selected"' : ''); ?>>Page</option>
				</select>
			</p>
			<p>
				<label for="<?php echo $this->get_field_id('include'); ?>">
					<?php _e('Include post IDs (optional):'); ?>
				</label>
				<input id="<?php echo $this->get_field_id('include'); ?>" class="widefat" type="text" name="<?php echo $this->get_field_name('include'); ?>" value="<?php echo $include; ?>" />
			</p>
			<p>
				<label for="<?php echo $this->get_field_id('category'); ?>">
					<?php _e('Include category (optional):'); ?>
				</label>
				<select id="<?php echo $this->get_field_id('category'); ?>" class="widefat" name="<?php echo $this->get_field_name('category'); ?>">
					<?php echo implode('', $cat_options); ?>
				</select>
			</p>				
		<?php
	}
}
 
// register widget
register_widget('FGX_MiniPost_Widget');
?>

Just paste this code in your theme’s functions.php file and the widget should appear under your available widgets.

This was a quick braindump post, so please feel free to post with any specific questions.

Create and list new tickets through the Assembla API

Friday, October 23rd, 2009

A while back I created a PHP library for interacting with the Assembla Ticket API. I thought it might be useful to someone, so here it is.

You can use this library to create and list tickets that exist for one of your Assembla spaces. It’s useful for integrating bug reporting and other feedback features into an application that you may be developing. This code is very much a work in progress, so please let me know if you encounter any bugs or have any new feature requests.

You can view and download the code on GitHub:

github-logo

Moving your blog off Blogger to a self-hosted Wordpress site

Monday, October 19th, 2009

wplogoblue-hoz-rgb

I have a client who absolutely loves Wordpress, but one of her long-time blogs has been hosted at Blogger since the dawn of time. Luckily, thanks to some persistence and a handy Wordpress plugin, she’s now happily blogging with Wordpress.

Step 1: Set up and configure Wordpress
First thing to do is get yourself a self-hosted Wordpress install. Many hosts these days even offer easy “one-click” installs of Wordpress through their control panels. Otherwise check out the Wordpress docs for help getting started.

If you need hosting recommendations shoot me an email or leave a comment. I’d be glad to recommend some of my favorites.

Step 2: Import your Blogger content
Once you’ve got Wordpress installed and hooked into your database, it’s time to import your old Blogger content. Log into the Wordpress admin panel and go to “Tools->Import->Blogger.” Follow the onscreen directions to complete this step.

Double check the imported posts and pages to make sure that all your content is there. Pay special attention to images in posts.

Step 3: Install plugins
Now, we all know you’ve developed a huge following on your blog, at least, that’s what you tell your friends. You don’t want all your incoming links to go dark once you move off Blogger, ideally you’d like them to point to the same posts on your new Wordpress site. The solution here is to use a nifty plugin called “wp-maintain-blogger-permalinks.” Following the directions at the link below will make this happen:

http://justinsomnia.org/2006/10/maintain-permalinks-moving-from-blogger-to-wordpress/

Now, if you don’t want to mess around with your .htaccess file to go “.html-less.” You can simply append “.html” to the end of your permalink structure. In Wordpress go to “Settings->Permalinks” and check the “Custom Structure” radio button. Modify this structure by removing the trailing “slash” and adding “.html” to the end. Don’t forget to save your changes!

Step 4: Update the DNS settings for your custom domain
It took me a while to find out how to update the DNS settings for a custom domain bought through the Blogger interface. It’s actually pretty easy. Just go to the following URL:

http://www.google.com/a/cpanel/yourdomainhere.com/

Log in to Google Apps using your Blogger ID and password. Then navigate to “Domain Settings ->Domain Names” and click on the “Advanced DNS settings” link.

google-blogger-custom-domain-dns-settings

Step 5: Get your custom domain transferred to a new registrar

You can certainly leave your domain hosted with Google’s affiliate registrar, but if you are hosting other domains elsewhere, it may make sense to consolidate your resources. To initiate the transfer process, you’ll need to obtain your domain’s authorization code from Google’s affiliate registrar.

If you’d like to transfer your domain to another domain registrar, you’ll need your domain’s authorization code. To obtain your authorization code, sign in to the DNS console of your domain host. Instructions vary by domain host.

https://www.google.com/support/a/bin/answer.py?hl=en&answer=112049

Your domain will either be registered at ENOM or GoDaddy through Google. The Google Apps. panel will tell you where your domain is registered. You’ll need to contact the registrar directly to obtain the transfer authorization code. ENOM can be contacted at GoogleClients@enom.com or by phone at 425-974-4623. Although you may need to pester them to get a response.

Good luck and let me know if you have any advice I may have overlooked!

New Post on Open-Source in Academia [EMU Marketing]

Saturday, September 5th, 2009

Just wanted to direct your attention to my new post over at EMU Marketing on open-source software in academia:

http://emumarketing.uoregon.edu/tristan/2009/09/04/coding-open-the-benefits-of-free-and-open-source-software-in-academia/

Check it out and leave us a comment!

Virtuemart – Shipping Method List – Invalid Markup

Wednesday, August 19th, 2009

virtuemart-shipping-method-list

A user recently reported an odd problem on a Virtuemart site I maintain. They weren’t able to properly select the bottom two shipping methods listed (both USPS options).

I took a gander at the cart and quickly found the problem. The UPS shipping module was outputting the radio buttons wrapped in a <label> tag. Problem was, it wasn’t doing this consistently, and the last UPS shipping option had an open tag that was encompassing all the remaining radio buttons. This meant that anywhere you clicked in that region would result in the last UPS option being selected.

Now for some reason the USPS shipping module didn’t output labels at all. So I figured the most consistent solution would be to simply prevent the UPS module from outputting those <label> tags.

The method you’re looking for is ups::list_rates(), which is in the ./administrator/components/com_virtuemart/classes/shipping/ups.php file.

You can see the changes I made below on lines 362 and 384. The lines that are commented out are the original ones.

356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
$shipping_rate_id = urlencode(__CLASS__."|UPS|".$value['ServiceName']."|".$charge);
$checked = (@$d["shipping_rate_id"] == $value) ? "checked=\"checked\"" : "";
if (count($shipment) == 1 ) {
        $checked = "checked=\"checked\"";
}
//$html .= '<label for="'.$shipping_rate_id.'">'."\n<input type=\"radio\" name=\"shipping_rate_id\" $checked value=\"$shipping_rate_id\" id=\"$shipping_rate_id\" />\n";
$html .= "<input type=\"radio\" name=\"shipping_rate_id\" $checked value=\"$shipping_rate_id\" id=\"$shipping_rate_id\" />\n";
 
$_SESSION[$shipping_rate_id] = 1;
 
$html .= $value['ServiceName'].' ';
$html .= "<strong>(".$value['TransportationCharges'].")</strong>";
if (DEBUG) {
        $html .= " - ".$VM_LANG->_('PHPSHOP_PRODUCT_FORM_WEIGHT').": ".$order_weight." ". $weight_measure.
        ", ".$VM_LANG->_('PHPSHOP_RATE_FORM_VALUE').": [[".$charge_unrated."(".$fsc_rate.")]+".UPS_HANDLING_FEE."](".$taxrate.")]";
}
// DELIVERY QUOTE
if (Show_Delivery_Days_Quote == 1) {
        if( !empty($value['GuaranteedDaysToDelivery'])) {
                $html .= "&nbsp;&nbsp;-&nbsp;&nbsp;".$value['GuaranteedDaysToDelivery']." ".$VM_LANG->_('PHPSHOP_UPS_SHIPPING_GUARANTEED_DAYS');
        }
}
if (Show_Delivery_ETA_Quote == 1) {
        if( !empty($value['ScheduledDeliveryTime'])) {
                $html .= "&nbsp;(ETA:&nbsp;".$value['ScheduledDeliveryTime'].")";
        }
}
if (Show_Delivery_Warning == 1 && !empty($value['RatedShipmentWarning'])) {
        //$html .= "</label><br/>\n&nbsp;&nbsp;&nbsp;*&nbsp;<em>".$value['RatedShipmentWarning']."</em>\n";
        $html .= "<br/>\n&nbsp;&nbsp;&nbsp;*&nbsp;<em>".$value['RatedShipmentWarning']."</em>\n";
}
$html .= "<br />\n";

14 Social Media Icon Sets

Sunday, August 16th, 2009

As many of you know, there are some crappy icon sets out there. Here are a few that I deem acceptable for general consumption. Enjoy!

Aquaticus Social icons
AquaticusSocialIcons

High Gloss Web 2.0 Icons
High Gloss Web Icons

108 Free Matte White Square Social Networking Icons
108 Free Matte White Square Social Networking Icons

154 Matte Black Social Media Icons
166__608x608_01-matte-black-social-media-icons-webtreats-preview

Web 2 Icons
web2_icons

Webtoolkit Icon Set Vol. 1
Webtoolkit Icon Set Vol. 1

Free Vectors – 20 Free Social Bookmarking Icons
Free Vectors - 20 Free Social Bookmarking Icons

Social Media Icons Pack
Social Media Icons Pack

34 (ou plus) boutons gratuits pour votre site
34 (ou plus) boutons gratuits pour votre site

Socialize Icons
Socialize Icons

Web social icons
Web social icons

Social Post Stamps: Free icon set
Social Post Stamps: Free icon set

Polaroid icon set
Polaroid icon set

Set of social icons no.3
Set of social icons no.3

Bonus: Twitm Icon (very slick app-icon)
Twitm Icon

New Skins – EMU Marketing

Sunday, July 26th, 2009

emu-marketing-destyle-theme

I decided it was time to freshen up the EMU Marketing blog a bit, so after a little hunting, I swapped out the old theme for the fantastic (and free) deStyle Wordpress theme – from ThemeShift.

I was pleasantly surprised by the quality of the deStyle theme. It features a custom theme options page; integrated Twitter and Flickr modules; ad-support; author bios; and really nice looking markup. The whole theme is just nicely polished.

ThemeShift has two other premium themes, deGusto and deCasa.

EMU Marketing is where I work. We do web development and and print design for student groups and University services housed within the Erb Memorial Union. Keep an eye out for some exciting news in the next few days. Hopefully we’ll have something fun to show you.

Hacking Virtuemart 1.1.3

Wednesday, July 15th, 2009

virtue-joomla

My first experience with Joomla and Virtuemart has left me less than enthusiastic. By far the most time I spent was trying to figure out what files performed what functions. It seems like there are three files that you need to modify for every change you’d like to make. The Virtuemart developer site is useless, and you have to resort to Google to find any answers. So I thought I’d document some of my findings here.

Finding Files
One of the best tips I can give you is to utilize the Unix find utility if you’re working on a *nix platform. You can locate a file using wildcards like so:

$ find / -name *searchterm*

Taxes
Virtuemart’s default implementation of the tax module leaves much to be desired. In our case we had to charge Denver City tax in addition to a Colorado State tax. This is simply impossible using the default tax module. Luckily I found someone with the same problem and was able to hack in a work around:

How to configure Virtuemart tax functions to work with U.S. states and zip codes

The key files in this case being:

  • basket.php and ro_basket.php
  • basket_b2c.html.php and basket_b2b.html.php
  • ro_basket_b2c.html.php and ro_basket_b2b.html.php
  • confirmation_email.tpl.php

If you poke around in these files and take a look at the link above it should give you a good start on modifying the way taxes are calculated and displayed to shoppers on your Virtuemart store.

Other Useful Files
I found that by far the most useful file was ps_checkout.php. It seems like Virtuemart runs a lot of its primary functions through this file. Other useful files include:

  • ps_user.php
  • ps_shopper.php

Further Reading