<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Peter&#039;s Useful Crap &#187; WordPress</title>
	<atom:link href="https://www.theblog.ca/category/wordpress/feed" rel="self" type="application/rss+xml" />
	<link>https://www.theblog.ca</link>
	<description>Useful tips on Canada, cell phones, banking, technology, WordPress, PHP and more</description>
	<lastBuildDate>Mon, 29 Dec 2025 22:49:25 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=4.0</generator>
	<item>
		<title>WordPress automatically adds slashes to all POST data</title>
		<link>https://www.theblog.ca/wordpress-addslashes-magic-quotes</link>
		<comments>https://www.theblog.ca/wordpress-addslashes-magic-quotes#comments</comments>
		<pubDate>Thu, 15 Nov 2012 02:13:41 +0000</pubDate>
		<dc:creator><![CDATA[Peter Keung]]></dc:creator>
				<category><![CDATA[Web development tutorials]]></category>
		<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://www.theblog.ca/?p=678</guid>
		<description><![CDATA[Since WordPress 1.5 (released in 2005) and up to and including WordPress 3.5, WordPress has been effectively forcing the &#8220;magic_quotes_gpc&#8221; setting to be on. Such behavior is currently implemented in wp-settings.php by calling the function wp_magic_quotes(), which does the following: function wp_magic_quotes() { // If already slashed, strip. if ( get_magic_quotes_gpc() ) { $_GET = [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Since WordPress 1.5 (released in 2005) and up to and including WordPress 3.5, WordPress has been effectively forcing the &#8220;<a href="http://php.net/manual/en/security.magicquotes.php">magic_quotes_gpc</a>&#8221; setting to be on. Such behavior is currently implemented in <em>wp-settings.php</em> by calling the function <em>wp_magic_quotes()</em>, which does the following:</p>
<pre>function wp_magic_quotes() {
    // If already slashed, strip.
    if ( get_magic_quotes_gpc() ) {
        $_GET    = stripslashes_deep( $_GET    );
        $_POST   = stripslashes_deep( $_POST   );
        $_COOKIE = stripslashes_deep( $_COOKIE );
    }

    // Escape with wpdb.
    $_GET    = add_magic_quotes( $_GET    );
    $_POST   = add_magic_quotes( $_POST   );
    $_COOKIE = add_magic_quotes( $_COOKIE );
    $_SERVER = add_magic_quotes( $_SERVER );

    // Force REQUEST to be GET + POST.
    $_REQUEST = array_merge( $_GET, $_POST );
}
</pre>
<p>You might discover this if you&#8217;re dealing with POST data, have long vowed to never turn on the &#8220;magic_quotes_gpc&#8221; setting (which is deprecated in PHP 5.3 and removed in PHP 5.4) and are manually escaping data with specific-use functions such as <a href="http://www.php.net/manual/en/mysqli.real-escape-string.php"><em>mysqli_real_escape_string()</em></a>, only to discover that items are being double-encoded and/or you are being forced to use <em>stripslashes</em> (or the WordPress-specific <a href="http://codex.wordpress.org/Function_Reference/stripslashes_deep"><em>stripslashes_deep</em></a>).</p>
<p>Despite the resulting frustration and surprise to many developers, WordPress has reasons for keeping this seemingly odd behavior, and you can read about this <a href="http://stackoverflow.com/questions/8949768/with-magic-quotes-disabled-why-does-php-wordpress-continue-to-auto-escape-my">here</a> and <a href="http://core.trac.wordpress.org/ticket/18322">here</a>. In short, WordPress is trying to protect its millions of users from the onslaught of basic vulnerabilities that removing this behavior would cause. These vulnerabilities would be exposed in its huge database of publicly contributed plugins of varying qualities; some plugins don&#8217;t properly escape outside data.</p>
<p>At some point, this will need to be resolved, but if you&#8217;ve run into this problem when developing something in WordPress, now you know the 7+ years of history behind it!</p>
]]></content:encoded>
			<wfw:commentRss>https://www.theblog.ca/wordpress-addslashes-magic-quotes/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Free TypeKit alternative: Google Web Fonts review</title>
		<link>https://www.theblog.ca/google-web-fonts-review-free-typekit-alternative</link>
		<comments>https://www.theblog.ca/google-web-fonts-review-free-typekit-alternative#comments</comments>
		<pubDate>Sun, 22 Jul 2012 15:16:30 +0000</pubDate>
		<dc:creator><![CDATA[Peter Keung]]></dc:creator>
				<category><![CDATA[Web development tutorials]]></category>
		<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://www.theblog.ca/?p=656</guid>
		<description><![CDATA[Fonts for websites has long been a complex issue. There are a few standard fonts that most visitors will have. Straying outside of that set of fonts brings up challenges of: load time and Flash dependency (with sIFR), licensing and complexity (with Cufon), maintainability and bad SEO (with images), or cost (with TypeKit). Google Web [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Fonts for websites has long been a complex issue. There are a few standard fonts that most visitors will have. Straying outside of that set of fonts brings up challenges of: load time and Flash dependency (with <a href="http://en.wikipedia.org/wiki/Scalable_Inman_Flash_Replacement" title="Flash-generated text using custom fonts">sIFR</a>), licensing and complexity (with <a href="http://cufon.shoqolate.com/generate/" title="They suggest you try TypeKit">Cufon</a>), maintainability and bad SEO (with images), or cost (with <a href="https://typekit.com/" title="By Adobe">TypeKit</a>).</p>
<p><a href="http://www.google.com/webfonts" title="The Google machine">Google Web Fonts</a> is a free alternative that currently has 501 open source fonts to choose from.  It&#8217;s not the perfect solution, but it is compatible with most browsers (going back to Internet Explorer 6+ and Firefox 3.5+, for example) and very easy to install on a website.</p>
<p>1. Select a font or fonts to use on the <a href="http://www.google.com/webfonts" title="The Google machine">Google Web Fonts</a> site, such as Francois One:</p>
<p><img src="http://images.theblog.ca/2012/07/custom_google_web_font.png" alt="Francois One Google Custom Web Font" /></p>
<p>2. Include the relevant Google stylesheet call:</p>
<pre>
&lt;link href="http://fonts.googleapis.com/css?family=Francois+One" rel="stylesheet" type="text/css" /&gt;
</pre>
<p>or:</p>
<p>Include the relevant Google JavaScript call:</p>
<pre>
&lt;script type="text/javascript"&gt;
  WebFontConfig = {
    google: { families: [ 'Francois+One::latin' ] }
  };
  (function() {
    var wf = document.createElement('script');
    wf.src = ('https:' == document.location.protocol ? 'https' : 'http') +
      '://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
    wf.type = 'text/javascript';
    wf.async = 'true';
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(wf, s);
  })();
&lt;/script&gt;
</pre>
<p>3. Use the font in your stylesheets as per normal:</p>
<pre>
#header h1 { font-family:"Francois One", "Century Gothic", "Trebuchet MS", "Arial Narrow", Arial, sans-serif; }
</pre>
<p>Note that you should always use a list of fonts to provide fallback options if the technology and/or fonts are not supported.</p>
<p>One of the drawbacks of Google Web Fonts is what is commonly called &#8220;flash of unstyled content&#8221; &#8212; for some visitors, site elements will show in a fallback font before the selected font style is applied.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.theblog.ca/google-web-fonts-review-free-typekit-alternative/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>mysqltuner: automatically optimize MySQL settings</title>
		<link>https://www.theblog.ca/mysqltuner-automatically-optimize-mysql-settings</link>
		<comments>https://www.theblog.ca/mysqltuner-automatically-optimize-mysql-settings#comments</comments>
		<pubDate>Sat, 26 May 2012 21:40:56 +0000</pubDate>
		<dc:creator><![CDATA[Peter Keung]]></dc:creator>
				<category><![CDATA[Blogs]]></category>
		<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://www.theblog.ca/?p=649</guid>
		<description><![CDATA[If you run your own server, a virtual private server, or a cloud server with shell access, you might want to optimize your MySQL settings. This can greatly increase your site&#8217;s performance by making the best use out of memory and caching, and minimizing disk swapping. However, if you&#8217;ve read up on settings such as [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>If you run your own server, a <a href="http://en.wikipedia.org/wiki/Virtual_private_server" title="VPS">virtual private server</a>, or a cloud server with shell access, you might want to optimize your MySQL settings.  This can greatly increase your site&#8217;s performance by making the best use out of memory and caching, and minimizing disk swapping.  However, if you&#8217;ve read up on settings such as the InnoDB buffer pool size, query cache, table cache, and so on, you might not know where to start in terms of what to tweak.  Among other things, <a href="http://hackmysql.com/mysqlreport">mysqlreport</a> can give you lots of data to analyze.  My favourite, however, is <a href="http://mysqltuner.pl/mysqltuner.pl">mysqltuner</a>, which is a Perl script that will run diagnostics on your MySQL database and make specific recommendations.</p>
<p>mysqltuner is easy to install and use. You can <a href="http://mysqltuner.pl/mysqltuner.pl">download the Perl file</a> or use an installer, such as <a href="http://en.wikipedia.org/wiki/Yellowdog_Updater,_Modified">yum</a> (<em>yum install mysqltuner</em>) on Red Hat and CentOS.</p>
<p>Then, you can run <em>perl mysqltuner.tpl</em> or <em>mysqltuner</em>, follow the prompts for the database credentials, and wait for the results:</p>
<pre>
-------- Performance Metrics -------------------------------------------------
[--] Up for: 145d 4h 29m 13s (17M q [1.388 qps], 290K conn, TX: 100B, RX: 27B)
[--] Reads / Writes: 97% / 3%
[--] Total buffers: 168.0M global + 2.8M per thread (151 max threads)
[OK] Maximum possible memory usage: 583.2M (14% of installed RAM)
[OK] Slow queries: 0% (199/17M)
[OK] Highest usage of available connections: 8% (13/151)
[OK] Key buffer size / total MyISAM indexes: 8.0M/3.5M
[OK] Key buffer hit rate: 100.0% (120M cached / 13K reads)
[!!] Query cache is disabled
[OK] Sorts requiring temporary tables: 0% (751 temp sorts / 2M sorts)
[!!] Joins performed without indexes: 922026
[!!] Temporary tables created on disk: 35% (1M on disk / 4M total)
[!!] Thread cache is disabled
[!!] Table cache hit rate: 0% (400 open / 238K opened)
[OK] Open file limit used: 5% (58/1K)
[OK] Table locks acquired immediately: 99% (33M immediate / 33M locks)
[!!] InnoDB data size / buffer pool: 3.0G/128.0M

-------- Recommendations -----------------------------------------------------
General recommendations:
    Run OPTIMIZE TABLE to defragment tables for better performance
    Enable the slow query log to troubleshoot bad queries
    Adjust your join queries to always utilize indexes
    When making adjustments, make tmp_table_size/max_heap_table_size equal
    Reduce your SELECT DISTINCT queries without LIMIT clauses
    Set thread_cache_size to 4 as a starting value
    Increase table_cache gradually to avoid file descriptor limits
Variables to adjust:
    query_cache_size (>= 8M)
    join_buffer_size (> 128.0K, or always use indexes with joins)
    tmp_table_size (> 16M)
    max_heap_table_size (> 16M)
    thread_cache_size (start at 4)
    table_cache (> 400)
    innodb_buffer_pool_size (>= 3G)
</pre>
<p>Then, modify the settings you are comfortable with (typically in <em>/etc/my.cnf</em> or directly in the MySQL console to make temporary changes) and restart MySQL if necessary.  You can then run mysqltuner at any time again in the future to get up-to-date recommendations.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.theblog.ca/mysqltuner-automatically-optimize-mysql-settings/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How to access &#8220;most viewed pages&#8221; data using the Google Analytics API 3.0 and PHP</title>
		<link>https://www.theblog.ca/google-analytics-api-most-viewed</link>
		<comments>https://www.theblog.ca/google-analytics-api-most-viewed#comments</comments>
		<pubDate>Mon, 23 Jan 2012 00:20:39 +0000</pubDate>
		<dc:creator><![CDATA[Peter Keung]]></dc:creator>
				<category><![CDATA[Blogs]]></category>
		<category><![CDATA[Web development tutorials]]></category>
		<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://www.theblog.ca/?p=624</guid>
		<description><![CDATA[If you are running Google Analytics on your site, you can use its API to read visitor data and display the information. Here is a quick framework how-to on using the API to get the data to create a &#8220;most viewed&#8221; widget based on the pages that get the most pageviews. The data is accessed [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>If you are running Google Analytics on your site, you can use its API to read visitor data and display the information. Here is a quick framework how-to on using the API to get the data to create a &#8220;most viewed&#8221; widget based on the pages that get the most pageviews. The data is accessed periodically in the background from a script on your website&#8217;s server.</p>
<p>Version 3.0 of the <a href="http://code.google.com/apis/analytics/docs/" title="Introduced in December 2011">Google Analytics API 3.0</a> implements the <a href="http://oauth.net/2/" title="Read about OAuth">OAuth 2.0</a> protocol. Among the benefits of OAuth are the fact that applications don&#8217;t have to store user credential information, and that users can revoke an application&#8217;s permissions at any time. However, the OAuth authorization process can be a bit more involved compared to some other API approaches. Also, the Google documentation is a bit light in terms of summarizing the process for server-side data access.</p>
<p>This how-to will use PHP to make the server-side calls to the Google Analytics API, which returns information in the <a href="http://www.json.org/" title="JavaScript Object Notation">JSON format</a>. The code examples assume that you have <a href="http://php.net/manual/en/book.curl.php" title="php-curl">cURL for PHP</a> installed, and that you are using PHP 5.2.0 or later in order to use the <a href="http://php.net/manual/en/function.json-decode.php">json_decode function</a> to parse the Google responses.</p>
<p>The setup steps are as follows: We will first set up the application in the Google API console. Then we&#8217;ll authorize the application with a Google account that has access to your site&#8217;s data. Using the resulting authorization code, we&#8217;ll request a refresh token from Google. </p>
<p>From there, you can build the application part: The refresh token is used to get access tokens that are finally used to actually read the data from Google. The details of implementing the data for the widget will be left up to you.</p>
<h3>Google API console setup</h3>
<p>Log in to the <a href="https://code.google.com/apis/console" title="To turn on specific parts of the API">Google API console</a> with a Google account. Note that the account that you log in with here does not need to be the same account that has access to your site&#8217;s data.</p>
<p>At the top of the left menu, create a new API project in the dropdown list:</p>
<p><img src="http://images.theblog.ca/2012/01/01_create_new_api_project.png" alt="Create new API project" /></p>
<p><img src="http://images.theblog.ca/2012/01/02_name_api_project.png" alt="Name the API project" /></p>
<p>Once your new project is created, select the &#8220;Services&#8221; page from the left menu, and turn on the Google Analytics API:</p>
<p><img src="http://images.theblog.ca/2012/01/03_turn_on_analytics_api.png" alt="Turn on the Analytics API" /></p>
<p>Note that there are per-day query limits for each API service.</p>
<p>Select the &#8220;API Access&#8221; page from the left menu, then create a web application client.</p>
<p><img src="http://images.theblog.ca/2012/01/04_new_api_key.png" alt="Create a new client ID" /></p>
<p>The name of the web application client will be displayed to the user (you) later when you authorize the application.</p>
<p><img src="http://images.theblog.ca/2012/01/05_client_id.png" alt="Client ID details" /></p>
<p><img src="http://images.theblog.ca/2012/01/06_client_application_type.png" alt="Client ID application type" /></p>
<p>You will need both the client ID and client secret later, used in a PHP script accessible at the redirect URI, which you can rename to add the &#8220;.php&#8221; suffix as necessary.</p>
<p><img src="http://images.theblog.ca/2012/01/07_client_id_parameters.png" alt="Client ID, client secret" /></p>
<p>If you need to access data server-side when an authorized user is not logged in, the web application client is not sufficient. You also have to create a server API key, limited to the IP address(es) of your server(s):</p>
<p><img src="http://images.theblog.ca/2012/01/08_server_key.png" alt="New server key" /></p>
<h3>Authorize the application</h3>
<p>Next, you&#8217;ll have to authorize the application to access your Google Analytics data. Remember that the account that created the API application does not have to be the same account that has access to your site&#8217;s data.</p>
<p>To prompt the user (you) to authorize your application, you have to build the authorization URL.  First, however, we should build a very basic script at the configured redirect URI.  The first iteration of the script has this code at <em>http://www.theblog.ca/oauth2callback.php</em>:</p>
<pre>
&lt;?php
    var_dump( $_REQUEST );
?&gt;
</pre>
<p>This is simply going to output to your screen the raw data that is being sent to the script via either POST or GET parameters. It is not the complete script yet, but will be used for illustrative purposes.</p>
<p>To build the authorization URL (which is a set of GET parameters at the URL <em>https://accounts.google.com/o/oauth2/auth</em>), you&#8217;ll need the following information, which is as follows for our example application:</p>
<ul>
<li>Callback URL: <em>http://www.theblog.ca/oauth2callback.php</em></li>
<li>Client ID: <em>63311316168.apps.googleusercontent.com</em></li>
<li>Client secret: <em>_iXNRZ5zMj1beMTab0wA4lXC</em></li>
</ul>
<p>You will also need to specify a response type (always &#8220;code&#8221;), the type of access you are requesting (&#8220;offline&#8221; since we are accessing the data in the background without user intervention), and a <a href="http://code.google.com/apis/analytics/docs/mgmt/v3/mgmtAuthorization.html" title="Page that lists the scope">&#8220;scope&#8221; specific to Google Analytics</a>. See the <a href="http://code.google.com/apis/accounts/docs/OAuth2WebServer.html" title="Specifically, the Forming the URL section">Google API OAuth 2.0 documentation</a> for information on the URL parameters and the resulting response.</p>
<p>For our example application, the authorization URL is:</p>
<p><em>https://accounts.google.com/o/oauth2/auth?response_type=code&#038;client_id=63311316168.apps.googleusercontent.com<br />&#038;redirect_uri=http://www.theblog.ca/oauth2callback.php&#038;access_type=offline<br />&#038;scope=https://www.googleapis.com/auth/analytics.readonly</em></p>
<h3>Get the permanent refresh token</h3>
<p>When you access the authorization URL, it should prompt the user (you) to allow the application to access the site data. Once you click the authorization button, you should be redirected to your authorization URL, which you will discover has the &#8220;code&#8221; GET parameter. You will have to use the value of that GET parameter to ask Google for the refresh token.</p>
<p>Our example uses cURL to build the refresh token request, whose URL uses a similar format to the authorization URL, posting a set of fields to <em>https://accounts.google.com/o/oauth2/auth</em>. According to the <a href="http://code.google.com/apis/accounts/docs/OAuth2WebServer.html" title="Specifically, the Handling the Response section">Google API OAuth 2.0 documentation</a>, you&#8217;ll need the client ID, client secret, and redirect URI information from before, as well as the &#8220;code&#8221; and an extra field &#8220;grant_type&#8221;, which is always &#8220;authorization_code&#8221;.</p>
<p>In <em>oauth2callback.php</em>, use the following code:</p>
<pre>
&lt;?php
if( isset( $_GET['code'] ) )
{
    $ch = curl_init();
    $timeout = 5;
    curl_setopt( $ch, CURLOPT_URL, 'https://accounts.google.com/o/oauth2/token' );
    curl_setopt( $ch, CURLOPT_POST, 1);
    curl_setopt( $ch, CURLOPT_POSTFIELDS, 'code=' . $_GET['code']. '&#038;client_id=63311316168.apps.googleusercontent.com&#038;client_secret=_iXNRZ5zMj1beMTab0wA4lXC&#038;redirect_uri=http://www.theblog.ca/oauth2callback.php&#038;grant_type=authorization_code');
    curl_setopt( $ch, CURLOPT_RETURNTRANSFER,1 );
    curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, $timeout );
    $data = curl_exec( $ch );
    curl_close( $ch );
    $result = json_decode( $data, true );
    var_dump( $result );
}
else
{
    var_dump( $_REQUEST, true );
}
?&gt;
</pre>
<p>Note that we are once again using a quick and dirty &#8220;var_dump&#8221; to output the returned information on the screen.  If you visit the same authorization URL in your browser once again, you should get redirected to the same URL as before, but this time showing the response from the extra request for the refresh token. This response should, of course, contain the refresh token.</p>
<p>One extra note: to see which applications you&#8217;ve authorized under a Google account, you can go to this URL: <a href="https://accounts.google.com/IssuedAuthSubTokens" title="Showing your existing authorizations">https://accounts.google.com/IssuedAuthSubTokens</a>, where you can also revoke access, forcing the application(s) to ask for your authorization again.</p>
<h3>Use the refresh token and access the data</h3>
<p>The refresh token is the most important piece of information, as it is essentially the permanent key that you need to access the Google Analytics data.  (It is permanent as long as the user account that authorized the application does not revoke access.)  The more accurate description is that the refresh token is used to get a time-limited access token, and that the access token is used along with the API key you had set up, valid only for specific IP addresses, to access the data.</p>
<p>In our example, the refresh token is:<br />
<em>1/7DRzjcqm-ypH9By1FrY3T-l_oSW3KdklC0LJuZLk5Q0</em></p>
<p>The following example script is a framework for what you would use with your website, and assumes that you are running it periodically, about once an hour, and therefore you need to ask for a new access token every time. A Google Analytics access token typically has an expiry of just over 1 hour, and thus you can use it for more than 1 request within that hour.</p>
<p>The script gets the top 10 pages based on pageviews for the past 1 day.</p>
<pre>
&lt;?php

// First, ask for an access token using the refresh token
$ch = curl_init();
$timeout = 5;
curl_setopt( $ch, CURLOPT_URL, 'https://accounts.google.com/o/oauth2/token' );
curl_setopt( $ch, CURLOPT_POST, 1);
curl_setopt( $ch, CURLOPT_POSTFIELDS, 'refresh_token=1/7DRzjcqm-ypH9By1FrY3T-l_oSW3KdklC0LJuZLk5Q0&#038;client_id=63311316168.apps.googleusercontent.com&#038;client_secret=_iXNRZ5zMj1beMTab0wA4lXC&#038;grant_type=refresh_token');
curl_setopt( $ch, CURLOPT_RETURNTRANSFER,1 );
curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, $timeout );
$data = curl_exec( $ch );
curl_close( $ch );
$result = json_decode( $data, true );

/*
You should get a response like this in the result:
{
  "access_token":"1/fFBGRNJru1FQd44AzqT3Zg",
  "expires_in":3920,
  "token_type":"Bearer",
}

Then, you can use that access token to build queries (see http://code.google.com/apis/analytics/docs/gdata/gdataExplorer.html or http://code.google.com/apis/explorer/#_s=analytics&#038;_v=v3 for examples):
*/

if( isset( $result['access_token'] ) )
{
    // Look up only the last day of visits
    $endDate = date( 'Y-m-d' );
    $startDate = date( 'Y-m-d', strtotime( '-1 day' ) );
    /*
        "ids" value comes from this URL in the last portion of the URL, after the "p": https://www.google.com/analytics/web/#dashboard/default/a381759w192893p9122283/
        Or use http://code.google.com/apis/analytics/docs/gdata/gdataExplorer.html to show the GA ID for each your Analytics accounts
        "key" is the API key that you'd set up in the Google APIs console, restricted to certain IP addresses
    */
    $url = 'https://www.googleapis.com/analytics/v3/data/ga?' . 'key=AIzaSyDyWgfb45VYfVYdVnmpH4JZCCRNas5P0SE&#038;ids=ga:9122283&#038;start-date=' . $startDate . '&#038;end-date=' . $endDate . '&#038;metrics=ga:pageviews&#038;sort=-ga:pageviews&#038;dimensions=ga:pagePath&#038;max-results=10';
    $ch = curl_init();
    $timeout = 5;
    curl_setopt( $ch, CURLOPT_URL, $url );
    curl_setopt( $ch, CURLOPT_HTTPHEADER, array( 'Authorization: Bearer ' . $result['access_token'] ) );
    curl_setopt( $ch, CURLOPT_RETURNTRANSFER,1 );
    curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, $timeout );
    $data = curl_exec( $ch );
    curl_close( $ch );
    $mostViewedRaw = json_decode( $data, true );
    var_dump( $mostViewedRaw );
}
?&gt;
</pre>
<p>From here, it is up to you to use the returned data in ways that best suit your needs and application. For example, for performance reasons and API call limitations, you might want to run the script only about once an hour, and store the results in a database table or a data structure specific to your content management system. You might also want to do some extra filtering on the Google Analytics results to only display certain types of pages (omitting the front page, for example). Note that the resulting Google Analytics data is simply output to the screen in our example so that you can see its structure and determine how to use it to suit your needs.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.theblog.ca/google-analytics-api-most-viewed/feed</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Redirecting users on first login in WordPress</title>
		<link>https://www.theblog.ca/wordpress-redirect-first-login</link>
		<comments>https://www.theblog.ca/wordpress-redirect-first-login#comments</comments>
		<pubDate>Sat, 24 Dec 2011 19:26:24 +0000</pubDate>
		<dc:creator><![CDATA[Peter Keung]]></dc:creator>
				<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://www.theblog.ca/?p=621</guid>
		<description><![CDATA[On membership-based WordPress sites and other sites where you want to display a special welcome message or instructions to new users, you can implement some custom login redirect functionality. This functionality would kick in only once (or for the first few logins) per user. The important elements on the code-side for such functionality is to [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>On membership-based WordPress sites and other sites where you want to display a special welcome message or instructions to new users, you can implement some custom login redirect functionality. This functionality would kick in only once (or for the first few logins) per user.</p>
<p>The important elements on the code-side for such functionality is to use the built-in WordPress &#8220;login_redirect&#8221; filter, and to store information on whether or not the user has gotten the &#8220;first login&#8221; treatment. There are a couple of possible approaches to store the information, either in a cookie or in the user&#8217;s meta information (stored in the WordPress database in the &#8220;wp_usermeta&#8221; table).</p>
<p>Here is some sample code you can use in your theme&#8217;s functions.php file or in a plugin:</p>
<h4>Cookie-based solution</h4>
<pre>
// Send new users to a special page
function redirectOnFirstLogin( $redirect_to, $requested_redirect_to, $user )
{
    // URL to redirect to
    $redirect_url = 'http://yoursite.com/firstloginpage';
    // How many times to redirect the user
    $num_redirects = 1;
    // Cookie-based solution: captures users who registered within the last n hours
    // The reason to set it as "last n hours" is so that if a user clears their cookies or logs in with a different browser,
    // they don't get this same redirect treatment long after they're already a registered user
    // 172800 seconds = 48 hours
    $message_period = 172800;

    // If they're on the login page, don't do anything
    if( !isset( $user->user_login ) )
    {
        return $redirect_to;
    }

    $key_name = 'redirect_on_first_login_' . $user->ID;
    
    if( strtotime( $user->user_registered ) > ( time() - $message_period )
        &#038;&#038; ( !isset( $_COOKIE[$key_name] ) || intval( $_COOKIE[$key_name] ) < $num_redirects )
      )
    {
        if( isset( $_COOKIE[$key_name] ) )
        {
            $num_redirects = intval( $_COOKIE[$key_name] ) + 1;
        }
        setcookie( $key_name, $num_redirects, time() + $message_period, COOKIEPATH, COOKIE_DOMAIN );
        return $redirect_url;
    }
    else
    {
        return $redirect_to;
    }
}

add_filter( 'login_redirect', 'redirectOnFirstLogin', 10, 3 );
</pre>
<p><a href="http://www.theblog.ca/wp-content/uploads/2011/12/peters_redirect_first_login_cookie.zip">Download the cookie-based redirect on first login plugin</a></p>
<h4>User meta table based solution</h4>
<pre>

// Send new users to a special page
function redirectOnFirstLogin( $redirect_to, $requested_redirect_to, $user )
{
    // URL to redirect to
    $redirect_url = 'http://yoursite.com/firstloginpage';
    // How many times to redirect the user
    $num_redirects = 1;
    // If implementing this on an existing site, this is here so that existing users don't suddenly get the "first login" treatment
    // On a new site, you might remove this setting and the associated check
    // Alternative approach: run a script to assign the "already redirected" property to all existing users
    // Alternative approach: use a date-based check so that all registered users before a certain date are ignored
    // 172800 seconds = 48 hours
    $message_period = 172800;

    // If they're on the login page, don't do anything
    if( !isset( $user->user_login ) )
    {
        return $redirect_to;
    }

    $key_name = 'redirect_on_first_login';
    // Third parameter ensures that the result is a string
    $current_redirect_value = get_user_meta( $user->ID, $key_name, true );
    if( strtotime( $user->user_registered ) > ( time() - $message_period )
        &#038;&#038; ( '' == $current_redirect_value || intval( $current_redirect_value ) < $num_redirects )
      )
    {
        if( '' != $current_redirect_value )
        {
            $num_redirects = intval( $current_redirect_value ) + 1;
        }
        update_user_meta( $user->ID, $key_name, $num_redirects );
        return $redirect_url;
    }
    else
    {
        return $redirect_to;
    }
}

add_filter( 'login_redirect', 'redirectOnFirstLogin', 10, 3 );
</pre>
<p><a href="http://www.theblog.ca/wp-content/uploads/2011/12/peters_redirect_first_login_meta.zip">Download the user-meta based redirect on first login plugin</a></p>
<p>Some extra notes:</p>
<ul>
<li>The code can be modified so that you don't actually redirect the user anywhere different than normal users, but set the user meta information or cookie and read that data to show a special pop-up or box to the user.</li>
<li>WordPress has some default limitations disallowing redirects to URLs outside of your site's domain. If you need to redirect users elsewhere, you'll have to add the URLs to the "allowed redirect" list with <a href="http://www.theblog.ca/wp-content/uploads/2008/11/add_allowed_redirect_hosts.txt">code similar to this</a>.</li>
<li>If you have more sophisticated login redirect needs, you can adapt the code as an extension to <a href="http://www.theblog.ca/wplogin-redirect" title="Peter's Login Redirect">this fully-featured redirect plugin</a>.</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>https://www.theblog.ca/wordpress-redirect-first-login/feed</wfw:commentRss>
		<slash:comments>24</slash:comments>
		</item>
		<item>
		<title>How to use custom canonical URLs in WordPress</title>
		<link>https://www.theblog.ca/custom-canonical-urls-wordpress</link>
		<comments>https://www.theblog.ca/custom-canonical-urls-wordpress#comments</comments>
		<pubDate>Tue, 20 Dec 2011 05:36:49 +0000</pubDate>
		<dc:creator><![CDATA[Peter Keung]]></dc:creator>
				<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://www.theblog.ca/?p=619</guid>
		<description><![CDATA[A quick guide to overriding the rel_canonical function that is automatically activated in the wp_head action.]]></description>
				<content:encoded><![CDATA[<p><a href="http://wordpress.org" title="Blogging tool of choice">WordPress</a> has been automatically adding <a href="http://googlewebmastercentral.blogspot.com/2009/02/specify-your-canonical.html" title="Description from Google themselves">canonical URL</a> tags to individual posts and pages since version 2.3. For many reasons, such as <a href="http://googlewebmastercentral.blogspot.com/2009/12/handling-legitimate-cross-domain.html" title="You should handle this properly!">duplicating content across domains</a>, especially when syndicating content, you might want to specify a custom canonical URL for certain posts.  For example, you would point the canonical URL to the authoritative original source of a syndicated post.</p>
<p>Here&#8217;s a quick and simple, do-it-yourself guide to overriding a canonical URL whenever you make use of a specifically named <a href="http://codex.wordpress.org/Custom_Fields" title="Define your own">custom field</a>. As of WordPress 3.3, the default function that handles the canonical URL feature is <em>rel_canonical</em> in <em>wp-includes/link-template.php</em>:</p>
<pre>
function rel_canonical() {
    if ( !is_singular() )
        return;

    global $wp_the_query;
    if ( !$id = $wp_the_query->get_queried_object_id() )
        return;

    $link = get_permalink( $id );
    echo "&lt;link rel='canonical' href='$link' /&gt;\n";
}
</pre>
<p>To override this function, you have to build your own copy of it in your theme&#8217;s <em>functions.php</em> file (or in a plugin):</p>
<pre>
// A copy of rel_canonical but to allow an override on a custom tag
function rel_canonical_with_custom_tag_override()
{
    if( !is_singular() )
        return;

    global $wp_the_query;
    if( !$id = $wp_the_query->get_queried_object_id() )
        return;

    // check whether the current post has content in the "canonical_url" custom field
    $canonical_url = get_post_meta( $id, 'canonical_url', true );
    if( '' != $canonical_url )
    {
        // trailing slash functions copied from http://core.trac.wordpress.org/attachment/ticket/18660/canonical.6.patch
        $link = user_trailingslashit( trailingslashit( $canonical_url ) );
    }
    else
    {
        $link = get_permalink( $id );
    }
    echo "&lt;link rel='canonical' href='" . esc_url( $link ) . "' /&gt;\n";
}

// remove the default WordPress canonical URL function
if( function_exists( 'rel_canonical' ) )
{
    remove_action( 'wp_head', 'rel_canonical' );
}
// replace the default WordPress canonical URL function with your own
add_action( 'wp_head', 'rel_canonical_with_custom_tag_override' );
</pre>
<p>Then, on any post or page on which you want to override the canonical URL, add a custom field with the name &#8220;canonical_url&#8221; and the full URL value that you want to use:</p>
<p><img src="http://images.theblog.ca/2011/12/canonical_url_custom_field_wordpress.png" alt="Custom field for a canonical URL in WordPress" /></p>
<p>In the future, there could be a more efficient, &#8220;pluggable&#8221; way to accomplish this. The current way that WordPress handles this, you could end up with multiple plugins overriding the canonical URL feature, resulting in duplicate or clashing custom functions (as an example, the <a href="http://simple-press.com" title="Great plugin, by the way">Simple:Press forum plugin</a> has its own override function). There&#8217;s an <a href="http://core.trac.wordpress.org/ticket/18660">open ticket</a> on the topic of adding a filter in the <em>rel_canonical</em> function to make this process cleaner. Some of the example code above was taken from a patch in that ticket.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.theblog.ca/custom-canonical-urls-wordpress/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Autocompletions on a custom input field with Google Custom Search</title>
		<link>https://www.theblog.ca/autocomplete-google-custom-search-input-field</link>
		<comments>https://www.theblog.ca/autocomplete-google-custom-search-input-field#comments</comments>
		<pubDate>Sun, 07 Aug 2011 15:34:49 +0000</pubDate>
		<dc:creator><![CDATA[Peter Keung]]></dc:creator>
				<category><![CDATA[Web development tutorials]]></category>
		<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://www.theblog.ca/?p=595</guid>
		<description><![CDATA[Some example code and documentation on how to use a custom form and custom styling on a two-page Google Custom Search with autocomplete.]]></description>
				<content:encoded><![CDATA[<p><a href="http://www.google.com/cse/" title="Get Google on your own site">Google Custom Search</a> is a mostly free, relatively easy way to add a customizable, powerful search engine to your own website. Although it is not as tunable as <a href="http://lucene.apache.org/solr/" title="Apache Solr">some other systems</a>, it does not require installing anything on your server or much knowledge of code. And by mostly free, I mean that if you are not a non-profit organization, university, or government, you have to show Google Ads on the results page or pay a yearly fee.</p>
<p>Among many features, it has a nice autocomplete feature&#8230;</p>
<p><img src="http://images.theblog.ca/2011/08/google_custom_search_autocomplete.png" alt="Google Custom Search autocomplete example" /></p>
<p>&#8230; and quite a few layout options:</p>
<p><img src="http://images.theblog.ca/2011/08/google_custom_search_layouts.png" alt="Configuration of Google Custom Search layouts" /></p>
<p>The &#8220;two page&#8221; layout option is what enables you to have a search field in, for example, the header or sidebar of your site, and have the results display in a dedicated page. Google will provide you with JavaScript code snippets for your particular site to insert for the search form and the results page, and these should work out of the box.</p>
<p>With the provided JavaScript code, Google will automatically create the search field and form within a table. In my case all I wanted was to enable autocomplete and also to have the input field and the search button on separate lines, which was not possible with the automatically-generated code, since it puts the input field and search button in adjacent table cells. There is some decent <a href="http://code.google.com/apis/websearch/docs/reference.html" title="Google Custom Search API documentation">API documentation</a> and an <a href="http://ajax-apis.appspot.com/html/two_page_search.html" title="Posted by someone at Google, apparently">example proof of concept</a> (which also adds a basic Google search logo to the field background, and clears it when the cursor enters the field), but neither showed a basic, documented example.</p>
<p>Here&#8217;s some barebones example code &#8212; for the search form only &#8212; if you&#8217;re using the two-page layout with autocomplete enabled and want to use an existing form instead of having Google generate it. The only thing that you would absolutely have to change is the first parameter in the &#8220;attachAutoCompletion&#8221; function: <strong>your_custom_search_id</strong>, which is the ID that Google gives you for your particular site&#8217;s search engine.</p>
<pre>
<!-- Send form to your results page. The single text field contains your search query -->
&lt;form id="yoursite-search" action="/search">
    &lt;input id="yoursite-search-field" name="q" type="text" />  
    &lt;input type="submit" value="Search" />
&lt;/form>

&lt;script src="http://www.google.ca/jsapi" type="text/javascript">&lt;/script>
&lt;script type="text/javascript">
    google.load( 'search', '1' );
    
    // Run this once the Google search JavaScript has loaded
    google.setOnLoadCallback( function() {
        // This makes your input field support autocomplete
        // The first parameter is your account identifier with Google Search
        // The second parameter is the DOM element of your search input field, which you could also grab with jQuery
        // The third parameter is the ID of your search form, not the actual DOM element. This is required so that when a user clicks on a search suggestion, the form gets submitted automatically
        google.search.CustomSearchControl.attachAutoCompletion(
            'your_custom_search_id',
            document.getElementById( 'yoursite-search-field' ),
            'yoursite-search' );
    });
&lt;/script>
</pre>
<p>Extra developer note: if you&#8217;re trying to style the auto-complete result dropdown, it&#8217;s populated in a result table with class &#8220;gsc-completion-container&#8221; and individual suggestions in spans, so you can change the font size, colour, and so on with specific style rules overriding the defaults, such as:</p>
<pre>.gsc-completion-container span { font-size: 12px; }</pre>
]]></content:encoded>
			<wfw:commentRss>https://www.theblog.ca/autocomplete-google-custom-search-input-field/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Logging actions to a file in WordPress</title>
		<link>https://www.theblog.ca/wordpress-file-logs</link>
		<comments>https://www.theblog.ca/wordpress-file-logs#comments</comments>
		<pubDate>Mon, 13 Sep 2010 01:14:43 +0000</pubDate>
		<dc:creator><![CDATA[Peter Keung]]></dc:creator>
				<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://www.theblog.ca/?p=539</guid>
		<description><![CDATA[The eZLog class in eZ Publish provides automatic time stamping and log rotation, which you can use within your code in any system.]]></description>
				<content:encoded><![CDATA[<p>If you&#8217;re writing a WordPress plugin, you might want to log certain actions, whether that&#8217;s an editing audit log, a specific visitor action, or really anything related to your site. One convenient, accessible, and useful option is to log such activities to a file.</p>
<p>There are several things to consider when logging information to a file, including date-stamping, basic formatting, and maximum log file sizes combined with automatic file rotation (when the log file reaches a certain file size, it is renamed to something like logfile.txt.1 and a new logfile.txt is started). This makes the log activities traceable, easy to read, and manageable on the server.</p>
<p><a href="http://ez.no" title="Great CMS">eZ Publish</a>, an open source, enterprise-grade content management framework, has a general purpose <em>eZLog</em> class that already handles file logging. I&#8217;ve very slightly modified it for use within WordPress. Here is an example plugin that logs password change requests (normally sent as e-mails to the site administrator) to a file: <a href="http://www.theblog.ca/wp-content/uploads/2010/09/peters_file_logs.txt">peters_file_logs.txt</a></p>
<p>You can easily re-purpose the <em>eZLog</em> class for your own needs. Its <em>write</em> method simply takes the message to be logged, the log file name, and the path to the log file:</p>
<p><code>$message = 'Logging this';<br />
$logger = new eZLog();<br />
$logFile = 'wp-logs.txt';<br />
$logPath = WP_CONTENT_DIR;<br />
$logger->write( $message, $logFile, $logPath );<br />
</code></p>
<p>The code above would produce something like the following on a new line in wp-content/wp-logs.txt each time it is run:</p>
<p><code>[ Aug 21 2010 12:21:07 ] Logging this<br />
</code></p>
<p>You can of course customize the messages logged to add relevant information such as who performed the action, on what page, and so on.  You can also customize the maximum number of rotated log files to keep and the maximum size of a log file.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.theblog.ca/wordpress-file-logs/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>How to back up your site cheaply with Amazon S3 and s3sync</title>
		<link>https://www.theblog.ca/amazon-s3sync-backup</link>
		<comments>https://www.theblog.ca/amazon-s3sync-backup#comments</comments>
		<pubDate>Sun, 11 Jul 2010 23:07:40 +0000</pubDate>
		<dc:creator><![CDATA[Peter Keung]]></dc:creator>
				<category><![CDATA[Blogs]]></category>
		<category><![CDATA[Computer Stuff]]></category>
		<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://www.theblog.ca/?p=527</guid>
		<description><![CDATA[The two main parts of WordPress and other popular CMS-powered websites are the database and the files. Your web host might keep regular backups of all elements of your site, but the unfortunate thing is that sometimes when your server is down, that is the most difficult time to access your host&#8217;s backups (as I [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>The two main parts of <a href="http://wordpress.org" title="Some don't call it a CMS">WordPress</a> and other popular CMS-powered websites are the database and the files.  Your web host might keep regular backups of all elements of your site, but the unfortunate thing is that sometimes when your server is down, that is the most difficult time to access your host&#8217;s backups (as I discovered with a shared host).  So it&#8217;s good to consider your own backup solutions.</p>
<p>If you have a MySQL database, a simple <em>mysqldump</em> command can save the database as a file and you can potentially <a href="http://www.theblog.ca/mysql-email-backup" title="Compress the db dump to make it easier">e-mail it to yourself</a> if it&#8217;s small enough. With or without the dumped database file, you then have to back up the rest of the files. <a href="http://www.theblog.ca/dropbox-web-server" title="Which actually uses Amazon S3">Dropbox</a> is a creative solution, but an easier and more direct solution is to use <a href="http://aws.amazon.com/s3/" title="Cheap, well-documented, and robust">Amazon&#8217;s Simple Storage Service</a>, otherwise known as Amazon S3.  S3 is a service that&#8217;s part of the much hyped <a href="http://en.wikipedia.org/wiki/Cloud_computing" title="Where everything is... in the cloud">cloud computing</a>, a land of seemingly infinite data and virtualized resources.  Amazon S3 is cheap, well-documented, robust, easy-to-use, and there are many good scripts to interface with S3.  It is more than just a good backup option, but for this post I&#8217;ll only talk about it within the context of backups.</p>
<p>Amazon S3&#8217;s costs are pay-per-use / pay-as-you-go and are split into a few categories:</p>
<ul>
<li>Storage: starting at $0.15 per gigabyte</li>
<li>Data transfer in: Free until November 1, 2010; usually starting at $0.10 per GB</li>
<li>Data transfer out: starting at $0.15 per GB</li>
<li>Put and similar requests: 0.01 per 1,000 requests (if you have lots of files this will be the initial input)</li>
</ul>
<p>Account management in Amazon breaks this down really well into minute details.  My blog is relatively small, as I&#8217;ve only accumulated about 100mb of files in 4 years and the database is less than 2mb compressed.  My monthly cost for daily and weekly backups is less than 20 cents.</p>
<p>Here are a few ways to get the most out of using Amazon S3 for backups:</p>
<ul>
<li>Back up only what you need to. Most CMSs let you place all your modifications in one or only a few places. In the case of <a href="http://wordpress.org" title="Some don't call it a CMS">WordPress</a>, if your entire site is contained in plugins, themes, and uploads, you might only need to back up the <em>wp-content/</em> folder, since you could restore the rest of the site from a downloaded instance of WordPress.</li>
<li>Look into some of S3&#8217;s extra features, such as versioning and access control by IP as discussed in its <a href="http://aws.amazon.com/s3/faqs/" title="Frequently Asked Questions">FAQ</a></li>
<li>Although Amazon S3 claims to be designed to provide &#8220;99.999999999% durability and 99.99% availability of objects over a given year&#8221;, it could still fail at the same time that your server fails. To mitigate this already-small risk, you could back up to multiple S3 regions, or look at a supplementary backup service such as <a href="http://www.rackspacecloud.com/cloud_hosting_products/files" title="There are many such services now">Rackspace Cloud Files</a></li>
<li>To actually access the backed up files, you can use the web-based <a href="http://aws.amazon.com/console/" title="Managing your Amazon Web Services">AWS Management Console</a> to view and download files, make them publicly accessible if needed, create &#8220;buckets&#8221; (top-level folders, which you can store in different physical locations). One of the more useful tools is a Firefox plugin called <a href="http://www.s3fox.net" title="Firefox plugin for browsing your Amazon S3 buckets">S3Fox Organizer</a>, which lets you do much of the same stuff in a familiar explorer-like, two-panel interface (where you can drag-and-drop between your local file system on the left and the remote files on the right):</li>
</ul>
<p><img src="http://www.theblog.ca/wp-content/uploads/2010/07/s3fox.png" alt="Firefox plugin for browsing your Amazon S3 buckets" /></p>
<p>If you need a step-by-step process to using Amazon S3 starting with the sign-up process, check out <a href="http://www.hongkiat.com/blog/amazon-s3-the-beginners-guide/" title="Easy to follow">this post</a>. Below are my notes on how to use the free <a href="http://www.s3sync.net" title="Ruby script">s3sync</a> and <a href="http://code.google.com/p/s3fs/wiki/FuseOverAmazon" title="Fuse Over Amazon">s3fs</a> tools for the actual backup process. Both assume that you&#8217;ve already created a bucket via the <a href="http://aws.amazon.com/console/" title="Managing your Amazon Web Services">AWS Management Console</a> or <a href="http://www.s3fox.net" title="Firefox plugin for browsing your Amazon S3 buckets">S3Fox Organizer</a>, although you can also use both tools to create buckets.</p>
<h3>s3sync</h3>
<p><a href="http://s3sync.net" title="Ruby Ruby Ruby Ruby!">s3sync</a> is a script written in <a href="http://ruby-lang.org/" title="Open Source">Ruby</a>. Ruby is usually installed on Linux servers and is easy to install if needed.</p>
<p>There are 3 main steps to start using s3sync.</p>
<h4>Download and extract the script</h4>
<p>You can currently download the latest version of s3sync at:<br />
<a href="http://s3.amazonaws.com/ServEdge_pub/s3sync/s3sync.tar.gz" title="A link around the URL">http://s3.amazonaws.com/ServEdge_pub/s3sync/s3sync.tar.gz</a></p>
<p>Then, you can extract the file through your control panel or with a simple tar command:</p>
<p><code>tar -zxf s3sync.tar.gz</code></p>
<h4>Create a configuration file</h4>
<p>You&#8217;ll need to make a copy of the included s3config.yml.example file.  It is very simple and has only two required lines:</p>
<p><code>aws_access_key_id: your_access_key_id<br />
aws_secret_access_key: your_secret_access_key</code></p>
<p>You can get both the access key ID and the secret access key on your account when you sign in to <a href="http://aws.amazon.com" title="It's so easy">Amazon Web Services</a>. Go to <em>Account > Security Credentials</em>.</p>
<p>As for where to place the configuration file, take a look at s3config.rb. Line 15 shows:</p>
<p><code>confpath = ["#{ENV['S3CONF']}", "#{ENV['HOME']}/.s3conf", "/etc/s3conf"]</code></p>
<p>So you can either place the s3config.yml file in a self-defined environmental variable (if you have command line access); or in your user&#8217;s home directory (usually <em>/home/yourusername/.s3conf/s3config.yml</em>) or in <em>etc/s3conf/s3config.yml</em>.  If all else fails, you can just edit s3config.rb and hardcode a path!</p>
<h4>Run the script</h4>
<p>Before you set up your full backup, you can test that your credentials and connection are working properly by listing the buckets on your account.  The command is simply:</p>
<p><code>./s3cmd.rb listbuckets</code></p>
<p>Note that s3cmd.rb needs to have executable permissions.  If you don&#8217;t have command line / shell access to your server, you likely have access to a control panel where you can set up a cron job to execute s3cmd.rb.  Just make sure that the cron job either refers to the full path where s3cmd.rb sits, or that it first changes directory (cd) into the folder to which you&#8217;ve extracted s3sync.</p>
<p>If all is well, you can then run the full backup script and then set up a cron job to back up your files daily and weekly (or at whatever frequency you&#8217;d like).</p>
<p><code>/path/to/s3sync/s3sync.rb -r /path/to/your/site theblog:daily</code></p>
<p>The command would back up your site to an existing bucket &#8220;theblog&#8221; under a sub-folder &#8220;daily&#8221;.</p>
<p>s3sync can also do many other Amazon S3 management tasks.  Check the well-detailed <a href="http://s3.amazonaws.com/ServEdge_pub/s3sync/README.txt" title="Finally, something with good documentation!">README file</a> for more information.</p>
<h3>s3fs</h3>
<p><a href="http://code.google.com/p/s3fs/wiki/FuseOverAmazon" title="Fuse Over Amazon">s3fs</a> is a simple solution for VPS and dedicated servers that enables you to mount an Amazon S3 bucket via a local <a href="http://fuse.sourceforge.net/" title="Filesystem in USErspace">FUSE</a> file system.  You can thus access your backup and sync it more or less locally.</p>
<p>(A quick note to VPS users &#8212; first, make sure that FUSE has been installed on the hardware node of the VPS setup!  Your hosting company will have to do this for you.  Otherwise, you might struggle with errors that seem to be file permission related (&#8220;fuse: failed to open /dev/fuse: Permission denied&#8221;) but that are in fact not simply chmod, chown, or user group fixable!)</p>
<p>If needed, install the fuse, fuse-devel, and curl-devel packages on your server.  If you&#8217;re on Red Hat, Fedora, or CentOS, you can make use of <a href="http://en.wikipedia.org/wiki/Yellowdog_Updater,_Modified" title="Yellowdog Updater, Modified">yum</a>:</p>
<p><code>yum install fuse<br />
yum install fuse-devel<br />
yum install curl-devel</code></p>
<p>Then, download and extract the latest s3fs source files from the <a href="http://code.google.com/p/s3fs/downloads/list" title="You'll have to compile the source!">s3fs website</a>.  You&#8217;ll have to compile the source, but that&#8217;s usually a simple &#8220;make -f Makefile&#8221; command.  Then, move the <em>s3fs</em> binary into <em>/usr/bin</em> and you&#8217;re ready to mount the Amazon S3 bucket!</p>
<p><code>/usr/bin/s3fs name_of_bucket -o accessKeyId=aaaa -o secretAccessKey=aaaa /mount_path<br />
</code></p>
<p>As noted in the above s3sync notes, you can get both the access key ID and the secret access key on your account when you sign in to <a href="http://aws.amazon.com" title="It's so easy">Amazon Web Services</a>. Go to <em>Account > Security Credentials</em>.</p>
<p>You can use the friendly <a href="http://en.wikipedia.org/wiki/Rsync" title="Incremental file transfers!">rsync command</a> to produce daily, weekly, or other frequency of backups:</p>
<p><code>rsync -av --delete /path/to/your/site/ /mount_path/daily<br />
</code></p>
]]></content:encoded>
			<wfw:commentRss>https://www.theblog.ca/amazon-s3sync-backup/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Sending e-mail to multiple recipients via SMTP in WordPress</title>
		<link>https://www.theblog.ca/wordpress-smtp</link>
		<comments>https://www.theblog.ca/wordpress-smtp#comments</comments>
		<pubDate>Sat, 25 Jul 2009 06:00:17 +0000</pubDate>
		<dc:creator><![CDATA[Peter Keung]]></dc:creator>
				<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://www.theblog.ca/?p=87</guid>
		<description><![CDATA[By default, e-mails sent from within a WordPress installation use the wp_mail() function (find it in wp-includes/pluggable.php), which in turn uses PHP&#8217;s mail() function. Therefore, e-mail is sent using whatever is configured for your server. You can specify to WordPress that you want to use a particular SMTP server to send e-mail. Plugins such as [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>By default, e-mails sent from within a WordPress installation use the wp_mail() function (find it in <em>wp-includes/pluggable.php</em>), which in turn uses PHP&#8217;s mail() function.  Therefore, e-mail is sent using whatever is configured for your server.</p>
<p>You can specify to WordPress that you want to use a particular <a href="http://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol" title="Yet another web acronym">SMTP server</a> to send e-mail.  Plugins such as the aptly named <a href="http://wordpress.org/extend/plugins/wp-mail-smtp/" title="Simple to configure">WP Mail SMTP</a> enable you to specify this, including a username and password if required.</p>
<p>However, up to and including WordPress 2.9, if you send mail via SMTP, it does not allow you to e-mail multiple recipients in the &#8220;to&#8221; field.  Most users don&#8217;t ever have to deal with this limitation, but if you&#8217;re using WordPress in one of several ways (such as with a <a href="http://www.theblog.ca/wordpress-collaboration-emails" title="Peter's Collaboration E-mails">collaboration workflow</a>), this can cause frustration.</p>
<p>Luckily, the wp_mail() function can be overridden.  Specifically, you want to replace this code:</p>
<p><code><br />
// Set destination address<br />
$phpmailer->AddAddress( $to );<br />
</code></p>
<p>With this:</p>
<p><code><br />
// Set destination address(es)<br />
// Accept either a comma-separated list or an array<br />
$to = explode( ',', implode( ',', (array) $to ) );<br />
foreach ( $to as $to_recipient ) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;$phpmailer->AddAddress( trim( $to_recipient ) );<br />
}<br />
</code></p>
<p>The best practice in such a case is to use a plugin so that whenever you upgrade WordPress, you won&#8217;t lose changes.  Here is <a href="http://www.theblog.ca/wp-content/uploads/2009/07/wp_mail_better.txt" title="It works!">an example of such a plugin</a>, which copies the default wp_mail() function and makes the change as detailed above.  To install it, just rename the extension to &#8220;php&#8221;, put it in your WordPress plugins folder, and activate it.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.theblog.ca/wordpress-smtp/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>
