<?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>David Egan</title>
	<atom:link href="https://davidegan.me/feed/" rel="self" type="application/rss+xml" />
	<link>https://davidegan.me</link>
	<description>My Blog</description>
	<lastBuildDate>Fri, 24 Mar 2017 10:42:27 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.0.3</generator>
	<item>
		<title>Checking Backup Integrity</title>
		<link>https://davidegan.me/checking-backup-integrity-of-html-php-and-mysql-backups/</link>
		
		<dc:creator><![CDATA[David Egan]]></dc:creator>
		<pubDate>Tue, 21 Mar 2017 11:38:56 +0000</pubDate>
				<category><![CDATA[Backup]]></category>
		<category><![CDATA[BASH]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">http://davidegan.me/?p=753</guid>

					<description><![CDATA[If you&#8217;ve set up a backup solution for WordPress or other dynamic PHP websites, you will probably be backing up site files as well as the site database. For a &#8230; <a href="https://davidegan.me/checking-backup-integrity-of-html-php-and-mysql-backups/" class="more-link">More&#160;&#187; <span class="screen-reader-text">Checking Backup Integrity</span></a>]]></description>
										<content:encoded><![CDATA[<p>If you&#8217;ve set up a backup solution for WordPress or other dynamic PHP websites, you will probably be backing up site files as well as the site database. For a proper backup solution, you need to check that the backup copy is viable.</p>
<p>You may have a copy of the site files, along with a (hopefully properly) dumped database, but unless you connect these up, how do you know that your backup copy is sound?</p>
<p><strong>The integrity of your backups is not something that you should discover during an emergency recovery situation.</strong></p>
<p>Manually rebuilding a working copy of a dynamic website is time consuming. For each site database, internal site URLs all relate to production domains, complicating the rebuild process. When you have a server backup with ten or twenty important client sites, verification of backups looks pretty daunting &#8211; and I suspect that a lot of people just don&#8217;t bother.</p>
<p>This article describes how to partially automate this process.</p>
<p>If you want to hack on this, <a href="https://github.com/DavidCWebs/check-backups">get the files on GitHub</a>.</p>
<h2 style="text-align: left;">Context</h2>
<p>In our case, site files from the Apache root of a production server are backed up incrementally on a daily basis to a date-stamped directory. This contains:</p>
<ul>
<li>A subdirectory <code class="highlighter-rouge">html</code> &#8211; which in turn contains a subdirectory for each site under the document root</li>
<li>A subdirectory <code class="highlighter-rouge">sql</code> which contains a collection of dumped databases for the sites in question</li>
</ul>
<p>Important config files are also backed up, but that is beyond the scope of this article.</p>
<ul>
<li><a href="http://dev-notes.eu/2015/06/incremental-backup-on-server/">Production server backup procedure</a></li>
<li><a href="http://dev-notes.eu/2015/06/secure-rsync-between-servers/">Secure rsync between servers</a> &#8211; describes consolidating backup copies on a central backup server</li>
<li><a href="http://dev-notes.eu/2015/10/backup-rsync-between-devices/">Rsync between backup server and local machine</a></li>
</ul>
<p>This article assumes that the backup has been downloaded to a local machine.</p>
<h2>Checking Integrity: Overview</h2>
<p>To test the integrity of backed up sites, one option is to build working clones of the sites on a virtual machine. To avoid the need to change URLs on the backup copies, the <code>/etc/hosts</code> file is amended on the guest VM.</p>
<p>Obviously, the guest VM needs to run a server that broadly matches the original backed up server (in this case Apache), and the virtual hosts settings for the guest VM server need to be set up correctly (this is a one-time import from the backed-up config directory).</p>
<p>You don&#8217;t necessarily need to use a VM &#8211; you could use any machine on the local network. The reason this is done on a VM/separate machine is so that the main host computer can access the <strong>actual</strong> live sites for maintenance purposes.</p>
<p>This method also keeps seperation between backed up clones and ongoing development websites &#8211; which are two different things.</p>
<p>This article assumes that a backup archive is available. Building working copies involves:</p>
<ol>
<li>One-time setup of a suitable Virtual Machine &#8211; in this case, a Ubuntu Xenial, Apache, MariaDB and PHP 7 LAMP stack</li>
<li>A one-time import of relevant database users to the VM</li>
<li>Exporting files from the Host machine backup archive to the Guest VM (run command in Host)</li>
<li>Importing databases in the Guest (run command in Guest)</li>
</ol>
<h2>Aims</h2>
<p>Check the integrity of multiple site backups by building working local copies. This is achieved by:</p>
<ul>
<li>Moving site files and databases for a backed-up production server from a host machine into a local virtual machine</li>
<li>Import MySQL/MariaDB databases and set up working sites on the VM</li>
</ul>
<p>Backup integrity should be checked regularly, so this should be a simple process.</p>
<p>Ideally, once the system has been setup it should be run by administrators rather than developers.</p>
<h2>Requirements</h2>
<p>These BASH scripts have been tested on Ubuntu Xenial Xerus 16.04 Desktop.</p>
<p><a href="https://help.gnome.org/users/zenity/stable/intro.html.en">Zenity</a> is used to create user dialogues.</p>
<p><a href="https://www.virtualbox.org/">VirtualBox</a> is required for the Virtual Machine. In this case, the VM runs Ubuntu 16.04 Xenial Xerus desktop &#8211; desktop rather than server because it allows easy checking of the moved sites. To achieve this, the guest machine hosts file (<code class="highlighter-rouge">/etc/hosts</code>) must be set up properly to point at the local copies.</p>
<p>The VM also runs Ubuntu 16.04 Desktop. The database server is MariaDB, but the commands would work on a standard MySQL database server.</p>
<p>The sql backups directory includes the <code class="highlighter-rouge">performance_schema.sql</code>, <code class="highlighter-rouge">phpmyadmin.sql</code>, <code class="highlighter-rouge">mysql.sql</code> and log files from the original server. These aren’t necessary to build clones from backups, and if imported will probably mess up the VM MySQL configuration. Because of this, we exclude these files from the transfer &#8211; see the <code class="highlighter-rouge">sql-verification-exclude</code> file in the linked repo for an example.</p>
<p>&nbsp;</p>
<p><strong>Note: For the backed up sites on the guest machine to work properly, the MySQL users from the original server should be imported in a one-time operation.</strong></p>
<h2>Move Files to the VM</h2>
<p>This is achieved with the <code class="highlighter-rouge">move-backups</code> script. This script prompts the user to choose a directory to move. The script is tightly coupled to our requirements, but would be easy to amend.</p>
<p>The directory to be moved is a datestamped directory that contains the entire <code class="highlighter-rouge">html</code> directory (i.e. document root) from a backed-up Apache server. It also contains backed up MySQL files (originally created by <code class="highlighter-rouge">mysqldump</code>) in a <code class="highlighter-rouge">sql</code> directory.</p>
<h2>Move Backups Script</h2>
<p>Run on the Host computer.</p>
<pre><code class="bash">
#!/bin/bash
#
# Move a directory into a local Virtual Machine for testing purposes.
#
# This file should be executable and in your path. E.g.:
# - `mv move-backups /usr/local/bin`
# - `chmod +x /usr/local/bin/move-backups`
# - Run: `move-backups`
#
# Add username@network-IP for your VM in place of `david@192.168.1.145`
# Add root@network-IP for your VM in place of `root@192.168.1.145`
# ------------------------------------------------------------------------------
STORAGE=/media/david/storage/servername
SQL_DESTINATION=david@192.168.1.145:staging
HTML_DESTINATION=root@192.168.1.145:/var/www/html
VM=Xenial
SQL_EXCLUDE=/media/david/storage/sql-verification-exclude # rsync excludes are controlled in a file

cd $STORAGE;

# Select the Directory to move
# ------------------------------------------------------------------------------
zenity --info \
--text="Begin the build process for backup up client websites. Click \"OK\" to begin. Then select the date-stamped directory in the backup storage area."

SOURCE=`zenity --file-selection --directory --title="Select a Directory to Sync"`

case $? in
0)
echo "\"$SOURCE\" selected.";;
1)
echo "No file selected.";;
-1)
echo "An unexpected error has occurred.";;
esac

# Start the Virtual Machine - for headless, append --type headless
# ------------------------------------------------------------------------------
VBoxManage startvm "$VM" --type headless | zenity --progress \
--pulsate --width="320" --height="150" \
--text="Starting $VM Virtual Machine" \
--title="Please Wait while $VM is started" --auto-close

# Sync HTML directories INDIVIDUALLY
# ------------------------------------------------------------------------------
HTML_DIRS=($SOURCE/html)

# Loop through Directories only
for DIR in $HTML_DIRS/*/; do

# For our rsync setup, the source directory MUST NOT have a trailing slash -
# so that if the directory doesn't exist, it will be created.
SOURCE_DIR=${DIR%/}

# basename of the $DIR - used as the destination directory, under `/var/www/html`
DEST_DIR= $(basename $DIR)

rsync -azv --progress --delete $SOURCE_DIR $HTML_DESTINATION/$DEST_DIR | zenity --progress \
--pulsate --width="320" --height="150" \
--text="Syncing the HTML directory: $SOURCE_DIR" \
--title="Please Wait" --auto-close

done

# Sync SQL backups to a staging directory
# ------------------------------------------------------------------------------
rsync -azv --exclude-from=$SQL_EXCLUDE --progress --delete $SOURCE/sql/ $SQL_DESTINATION/sql | zenity --progress \
--pulsate --width="320" --height="150" \
--text="Syncing the SQL directory" \
--title="Please Wait" --auto-close

# Tidy up
zenity --question \
--text="Sync complete. Do you want to shut down the VM?"

case $? in

0)
echo "0"
# Close up the VM, maintain state
VBoxManage controlvm $VM savestate | zenity --progress \
--pulsate --width="320" --height="150" \
--text="Shutting down $VM Virtual Machine" \
--title="Please Wait while VMs are Saved" \
--auto-close
zenity --info\
--window-icon="info" \
--text="The VM $VM has been shut down."
echo "$vm was closed to a saved state"

;;

1)
echo "1"
zenity --info\
--window-icon="info" \
--text="Your VM $VM is Running - though it may be in a headless[GitHub repo with scripts](https://github.com/DavidCWebs/check-backups).

-1)
echo "An unexpected error has occurred."
;;

esac
</code></pre>
<p>Usage:</p>
<ul>
<li>Add <code class="highlighter-rouge">move-backups</code> to <code class="highlighter-rouge">usr/local/bin</code> on the Host computer: <code class="highlighter-rouge">mv move-backups /usr/local/bin</code></li>
<li>Make executable: <code class="highlighter-rouge">chmod +x /usr/local/bin/move-backups</code></li>
<li>Run <code class="highlighter-rouge">move-backups</code> in a terminal and follow instructions</li>
</ul>
<p>When prompted, you should select a directory that contains the backed-up <code class="highlighter-rouge">html</code> directory from the Apache doc root &#8211; the directory that is normally located at <code class="highlighter-rouge">/var/www/</code> in a standard Apache setup.</p>
<p>Note that the moved files won’t do anything unless you also import the associated databases on the guest machine.</p>
<h2 style="text-align: left;">Import Databases</h2>
<ul>
<li>Add <code class="highlighter-rouge">import-databases</code> to <code class="highlighter-rouge">usr/local/sbin</code> on the Guest computer/VM: <code class="highlighter-rouge">mv import-databases /usr/local/sbin</code></li>
<li>Make executable: <code class="highlighter-rouge">chmod +x /usr/local/sbin/import-databases</code></li>
<li>Run <code class="highlighter-rouge">sudo import-databases</code> in a terminal on the Guest VM</li>
</ul>
<pre><code class="bash">
#!/bin/bash
#
# The purpose of this script is to import databases so that working copies of
# backed up PHP/WordPress websites can be quickly and easily checked.
#
# The script loops through all databases in a staging directory and imports them
# into MySQL/MariaDB. Existing databases having the same name will be overwritten.
#
#-------------------------------------------------------------------------------

SOURCE=/home/david/staging/sql
PASSWORD=thenicelongpassword
DATABASES=($SOURCE/*)

for (( i = 0; i &lt; ${#DATABASES[@]}; i++ )); do

# The file extension - in our case, there are *.log files that should be ignored
EXT=${DATABASES[$i]#*.}

if [[ "sql" == $EXT ]]; then

DB_SOURCE=${DATABASES[$i]}
DB_NAME=$(basename ${DATABASES[$i]} .sql)

# If a Databse exists with this name, DROP it
mysql --user=root --password=$PASSWORD -e "DROP DATABASE IF EXISTS \`$DB_NAME\`"

# Create new DB with the name of the DB backup file
mysql --user=root --password=$PASSWORD -e "create database \`$DB_NAME\`; GRANT ALL PRIVILEGES ON \`$DB_NAME\`.* TO root@localhost IDENTIFIED BY '$PASSWORD'"

# Import the Database
mysql --user=root --password=$PASSWORD $DB_NAME &lt; $DB_SOURCE

fi

done

# Set proper ownership of site files
chown -R www-data /var/www/html/*
</code></pre>
<h2>TODO</h2>
<p>These scripts are a good start, and allow us to build and check backup copies quite easily. There is room for further automation &#8211; ideally we&#8217;d like the process to be fully automated, integrated naturally into the backup process.</p>
<p>Our setup includes passwordless SSH keys which allows for easier rsync&#8217;ing, and this has not been documented.</p>
<p>Other enhancements might include:</p>
<ul>
<li>Prevent selection of the &#8216;wrong&#8217; backup directory</li>
<li>Auto creating the staging directory for the sql files transfer</li>
<li>Trigger the `import-databases` script from the host, so working copies are built with a single command</li>
<li>Better feedback on the `import-databases` script (there&#8217;s none at the moment!)</li>
<li>Document how to import users from original server to the guest machine</li>
</ul>
<h2>Resources</h2>
<ul>
<li><a href="https://github.com/DavidCWebs/check-backups">GitHub repo with scripts</a>.</li>
<li><a href="https://www.virtualbox.org/manual/ch08.htmlvboxmanage-controlvm">Closing the VM</a></li>
<li><a href="http://stackoverflow.com/a/10274182">Get path of a filename</a></li>
<li><a href="https://www.virtualbox.org/manual/ch08.htmlvboxmanage-startvm">Starting the VM from a script</a></li>
<li><a href="http://stackoverflow.com/a/19485757">Remove trailing slash</a></li>
<li><a href="http://ubuntuforums.org/archive/index.php/t-1078689.html">VM Management</a></li>
</ul>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Filtering the WordPress Menu</title>
		<link>https://davidegan.me/filtering-the-wordpress-menu/</link>
		
		<dc:creator><![CDATA[David Egan]]></dc:creator>
		<pubDate>Tue, 24 Jan 2017 10:41:14 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">http://davidegan.me/?p=762</guid>

					<description><![CDATA[You can filter both the &#60;li&#62; tag and the contained anchor tag in a WordPress menu using the ‘nav_menu_css_class’ and ‘nav_menu_link_attributes’ filters. The ‘nav_menu_link_attributes’ filter is not well documented &#8211; &#8230; <a href="https://davidegan.me/filtering-the-wordpress-menu/" class="more-link">More&#160;&#187; <span class="screen-reader-text">Filtering the WordPress Menu</span></a>]]></description>
										<content:encoded><![CDATA[<p>You can filter both the <code class="highlighter-rouge">&lt;li&gt;</code> tag and the contained anchor tag in a WordPress menu using the ‘nav_menu_css_class’ and ‘nav_menu_link_attributes’ filters.</p>
<p>The ‘nav_menu_link_attributes’ filter is not well documented &#8211; but very useful nonetheless.</p>
<p>This example shows how to add the required classes for a Bootstrap 4 menu markup &#8211; in which the <code class="highlighter-rouge">&lt;li&gt;</code> requires the class <code class="highlighter-rouge">nav-item</code> and the anchor tag requires the class <code class="highlighter-rouge">nav-link</code>:</p>
<pre><code class="PHP">&lt;?php
add_filter( 'nav_menu_css_class', function($classes) {
$classes[] = 'nav-item';
return $classes;
}, 10, 1 );

add_filter( 'nav_menu_link_attributes', function($atts) {
$atts['class'] = "nav-link";
return $atts;
}, 100, 1 );

</code></pre>
<p>&nbsp;</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Prevent User Enumeration in WordPress</title>
		<link>https://davidegan.me/prevent-user-enumeration-in-wordpress/</link>
					<comments>https://davidegan.me/prevent-user-enumeration-in-wordpress/#comments</comments>
		
		<dc:creator><![CDATA[David Egan]]></dc:creator>
		<pubDate>Tue, 25 Oct 2016 09:22:46 +0000</pubDate>
				<category><![CDATA[Apache]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">http://davidegan.me/?p=745</guid>

					<description><![CDATA[We recently had to deal with a hacking attempt against a client WordPress site that had a few interesting aspects. The fix involved additional .htaccess rules to block user enumeration. &#8230; <a href="https://davidegan.me/prevent-user-enumeration-in-wordpress/" class="more-link">More&#160;&#187; <span class="screen-reader-text">Prevent User Enumeration in WordPress</span></a>]]></description>
										<content:encoded><![CDATA[<div class="entry">
<p>We recently had to deal with a hacking attempt against a client WordPress site that had a few interesting aspects. The fix involved additional <code class="highlighter-rouge">.htaccess</code> rules to block user enumeration.</p>
<p>We experienced multiple failed login attempts against WordPress. This wasn’t particularly worrying &#8211; the originating IP address was automatically blocked by our Fail2Ban setup after three unsuccessful attempts. The attacker (probably a script) then switched IP address and repeated the process, trigerring a further ban and repeating the cycle. The attack lasted for approximately 10 minutes and triggered more than 50 bans.</p>
<p>The attack focused on usernames that were very close to (but not actually the same as) actual usernames on the site. It looked like a partially-successful user-enumeration attempt made up the initial phase of the attack. Puzzlingly, only some usernames had been enumerated.</p>
<h2 id="user-enumeration">User Enumeration</h2>
<p>User Enumeration is when would-be attackers collect usernames by interacting with your app. Unfortunately, by default WordPress makes this process easy. Entering <code class="highlighter-rouge">http://example.com/?author=1</code> in the browser will trigger display of all articles authored by the user with an ID of ‘1’ &#8211; along with their registered username. This provides would-be attackers with a toe-hold &#8211; they can attempt to log in to valid usernames rather than having to guess.</p>
<p>Our usual setup involves user-enumeration prevention measures &#8211; so it was surprising to see (almost valid) usernames cropping up in the log.</p>
<p>It turns out that our user-enumeration prevention relied on ‘redirect_canonical’ WordPress filter. This filter is triggered if you navigate to <code class="highlighter-rouge">http://example.com/?author=1</code> &#8211; in this case, it performs a redirect to the Author archives for the author with an ID of 1.</p>
<p class="emphasis-block">The problem: If a registered user on the site has not authored any articles, the redirect will not take place. The user does not have an archive, the redirect doesn’t take place, and the user-enumeration can proceed.</p>
<p>In our case, the enumerated users had a custom membership role rather than an author role &#8211; so they will never have an archive page. In our context, these are pretty low risk users, with very few permissions on the site. Nevertheless, it’s a pain having to check when these attacks occur, and it places unecessary load on the server.</p>
<p>We verified the partially successful enumeration attempt by doing some penetration testing using <a href="https://github.com/wpscanteam/wpscan">WPScan</a> &#8211; this turned up the exact “usernames” that were tried during the hack attempt.</p>
<p>The solution involved extra <code class="highlighter-rouge">.htaccess</code> rules to prevent user-enumeration. We also added some extra rules to block login attempts using the enumerated (incorrect) usernames &#8211; just in case the attacker is logging them for future usage.</p>
<h2 id="htaccess-rule-to-prevent-user-enumeration">.htaccess Rule to Prevent User enumeration</h2>
<pre><code class="Apache">
RewriteEngine On
%{REQUEST_URI} !^/wp-admin [NC]
RewriteCond %{QUERY_STRING} author=\d
RewriteRule (.*) $1? [L,R=301]
</code></pre>
<h2 id="explanation">Explanation</h2>
<h3 id="line-one">Line One</h3>
<p>Turn on rewriting functionality &#8211; the Apache <code class="highlighter-rouge">mod_rewrite</code> module must be installed on the server. This module rewrites requested URLs on the fly by means of a rule-based rewriting engine. The rewrite engine is based on a <a href="http://www.pcre.org/">Perl Compatible Regular Expressions(PCRE)</a> parser.</p>
<h3 id="line-2">Line 2</h3>
<p>Apply a rewrite condition such that the rule will be ignored if the <code class="highlighter-rouge">REQUEST_URI</code> begins with <code class="highlighter-rouge">/wp-admin</code>.</p>
<blockquote><p>REQUEST_URI</p>
<p>The path component of the requested URI, such as “/index.html”. This notably excludes the query string which is available as its own variable named QUERY_STRING. — <a href="http://httpd.apache.org/docs/current/mod/mod_rewrite.html#page-header">Apache mod_rewrite Docs</a></p></blockquote>
<p><code>REQUEST_URI</code> in simple terms is the bit after your domain.</p>
<p>The <code>author=\d</code> string that we’ll use to match the user enumeration attempt is used legitimately to display author posts in back end &#8211; so the rewrite rule should not apply if the request takes place in the WordPress admin area.</p>
<h3 id="line-3">Line 3</h3>
<p>Specify the rewrite condition &#8211; the target query string must include <code>'author=\d'</code>, where <code>\d</code> means a single digit.</p>
<p>This means that <code>http://example.com/?dummy&amp;author=1</code> will trigger the rewrite, as will <code>http://example.com/?dummy&amp;author=100</code> &#8211; provided we’re not in the admin area, as specified by the previous condition.</p>
<p>Note that the rule doesn’t specify that the ‘author’ variable is at the start of the query string (e.g. <code>^/?author=([0-9]*)</code> &#8211; a query string that starts with <code>/?author=</code> followed by any number of digits).</p>
<h3 id="line-4">Line 4</h3>
<p>The rewrite rule: replace the entire path <code>(.*)</code> with itself <code>$1</code> but with an empty query string <code>?</code>.</p>
<p>Make this the last rule and specify that it is a permanent redirect <code>[L,R=301]</code>.</p>
<h2 id="tldr-htaccess-rules">TLDR: .htaccess Rules</h2>
<p>Add these rules to .htaccess to prevent all malicious user-enumeration attempts. Such attempts will redirect to the site home page:</p>
<pre><code class="Apache">

RewriteEngine On
RewriteCond %{REQUEST_URI} !^/wp-admin [NC]
RewriteCond %{QUERY_STRING} author=\d
RewriteRule (.*) $1? [L,R=301]

</code></pre>
<p>Note that preventing user-enumeration is only one component of an effective security policy.</p>
<h2 id="references">References</h2>
<ul>
<li><a href="http://httpd.apache.org/docs/current/mod/mod_rewrite.html">Apache mod_rewrite</a></li>
<li><a href="http://stackoverflow.com/a/3457460/3590673">http://stackoverflow.com/a/3457460/3590673</a></li>
<li><a href="http://wordpress.stackexchange.com/a/130171/67129">http://wordpress.stackexchange.com/a/130171/67129</a></li>
</ul>
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://davidegan.me/prevent-user-enumeration-in-wordpress/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Parse YAML in PHP Using Symfony YAML</title>
		<link>https://davidegan.me/parse-yaml-in-php-using-symfony-yaml/</link>
					<comments>https://davidegan.me/parse-yaml-in-php-using-symfony-yaml/#respond</comments>
		
		<dc:creator><![CDATA[David Egan]]></dc:creator>
		<pubDate>Sat, 26 Mar 2016 12:07:31 +0000</pubDate>
				<category><![CDATA[Jekyll]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">http://davidegan.me/?p=720</guid>

					<description><![CDATA[Convert data in YAML format into a PHP array. I do a lot of work in WordPress. I also build a lot of static websites &#8211; both for rapid design &#8230; <a href="https://davidegan.me/parse-yaml-in-php-using-symfony-yaml/" class="more-link">More&#160;&#187; <span class="screen-reader-text">Parse YAML in PHP Using Symfony YAML</span></a>]]></description>
										<content:encoded><![CDATA[<p>Convert data in YAML format into a PHP array.</p>
<p>I do a lot of work in <a href="https://wordpress.org/">WordPress</a>. I also build a lot of static websites &#8211; both for rapid design in-the-browser and as a low-cost small-business website solution. I mainly use the excellent <a href="https://jekyllrb.com/">Jekyll</a> static site generator.</p>
<p>Jekyll uses <a href="http://yaml.org/">YAML</a> for config and data files (it can also use CSV format, but that&#8217;s another story). WordPress doesn&#8217;t use YAML.</p>
<p>I like YAML because it is very human friendly &#8211; the whole team (including non-developers) can easily build YAML config files in a way that you&#8217;re not going to see with formats like JSON or XML. This is an example of a YAML array used to create Javascript variables for use in a Google map:</p>
<pre><code class="YAML">
# Map centre Latitude &amp; Longitude
# ------------------------------------------------------------------------------
latitude: 52.7157856867271
longitude: -8.8741735070805

zoom: 15
#height: 548px

# Set custom colour variables
# ------------------------------------------------------------------------------
waterColour: "#398A8D"
landColour: "#dec7c7"
mainRoadColour: "#777777"
minorRoadColour: "#a9a9a9"

# A nested array
# ------------------------------------------------------------------------------
test:
- One
- Two
- Three

</code></pre>
<p>In the context of Jekyll, you could place this data in a <a href="https://jekyllrb.com/docs/datafiles/">data file</a> &#8211; e.g.<code>/_data/map.yml</code> &#8211; writing Javascript variables into <code>&lt;head&gt;</code> something like this:</p>
<pre><code class="JS">
&lt;script&gt;
var cwCentre = {
latitude:{{ site.data.map.latitude }},
longitude:{{ site.data.map.longitude }},
zoom:{{ page.map-zoom }},
mainMarker:"{{ site.baseurl}}/{{ site.data.map.mainMarker }}",
secondaryMarker:"{{ site.baseurl}}/{{ site.data.map.secondaryMarker }}",
waterColour:"{{ site.data.map.waterColour }}",
landColour:"{{ site.data.map.landColour }}",
mainRoadColour:"{{ site.data.map.mainRoadColour }}",
minorRoadColour:"{{ site.data.map.minorRoadColour }}",
title: "{{ site.data.map.title | escape }}",
description:'{{ map_description | markdownify | strip_newlines }}',
};
{% if "multi-centre" == page.map-type %}
var markers = [
{% for location in site.data.secondary-map-coords %}
[
'{{ location.name | escape }}',
{{ location.latitude }},
{{ location.longitude }},
'{{ location.description | escape }}'
]
{% unless forloop.last %},{% endunless %}
{% endfor %}
];
{% endif %}
&lt;/script&gt;
</code></pre>
<h2>YAML in WordPress</h2>
<p>I recently needed to convert a Jekyll site to a WordPress theme. Moving the map config settings required parsing YAML data into a PHP array. Fortunately this can be achieved pretty easily thanks to the <a href="http://symfony.com/doc/current/components/yaml/introduction.html">Symfony YAML component</a>.</p>
<p>I&#8217;m a recent convert to <a href="https://getcomposer.org/">Composer</a>, and find it amazingly powerful. You can add the Symfony YAML component with a single composer command.</p>
<h2>Add Symfony/YAML Using Composer</h2>
<pre><code class="BASH">
composer require symfony/yaml
</code></pre>
<p>When you run this, composer will add a new `symfony/yaml` directory under the project &#8216;vendor&#8217; directory. It will also add the relevant namespace to the &#8216;autoload_psr4.php&#8217; file, so that the new class will be autoloaded.</p>
<h2>Using the YAML parser</h2>
<p>To read the YAML contents of the config fields into a PHP array:</p>
<pre><code class="PHP">
&lt;?php
use Symfony\Component\Yaml\Parser;

$yaml = new Parser();

$value = $yaml-&gt;parse( file_get_contents( get_template_directory() . '/assets/map.yml' ) );
</code></pre>
<p>For the YAML content presented above, the following will be output:</p>
<pre><code class="PHP">
$value = array (
'latitude' =&gt; 52.715785686727102,
'longitude' =&gt; -8.8741735070804992,
'zoom' =&gt; 15,
'waterColour' =&gt; '#398A8D',
'landColour' =&gt; '#dec7c7',
'mainRoadColour' =&gt; '#777777',
'minorRoadColour' =&gt; '#a9a9a9',
'test' =&gt; array (
0 =&gt; 'One',
1 =&gt; 'Two',
2 =&gt; 'Three',
),
);
</code></pre>
<p>This array can be passed to <code>wp_localize_script()</code> when enqueuing the map script.</p>
<p>The WordPress/PHP way would be to collect such data from a form on an admin page, storing the data in the wp_options table. However taking variables from YAML files can be a good way to quickly port settings, which might even be used as defaults. It might also be a good way to configure certain project settings.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://davidegan.me/parse-yaml-in-php-using-symfony-yaml/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Semantic Bootstrap Layout Classes with an Offset</title>
		<link>https://davidegan.me/semantic-bootstrap-layout-classes-with-an-offset/</link>
					<comments>https://davidegan.me/semantic-bootstrap-layout-classes-with-an-offset/#respond</comments>
		
		<dc:creator><![CDATA[David Egan]]></dc:creator>
		<pubDate>Sat, 13 Feb 2016 11:03:16 +0000</pubDate>
				<category><![CDATA[Bootstrap]]></category>
		<category><![CDATA[Front End]]></category>
		<guid isPermaLink="false">http://davidegan.me/?p=714</guid>

					<description><![CDATA[Making CSS classes descriptive, or semantic, can help improve code maintainability by describing an elements purpose, rather than it’s presentational function. People level this as a criticism against Bootstrap (and &#8230; <a href="https://davidegan.me/semantic-bootstrap-layout-classes-with-an-offset/" class="more-link">More&#160;&#187; <span class="screen-reader-text">Semantic Bootstrap Layout Classes with an Offset</span></a>]]></description>
										<content:encoded><![CDATA[<p>Making CSS classes descriptive, or semantic, can help improve code maintainability by describing an elements purpose, rather than it’s presentational function.</p>
<p>People level this as a criticism against Bootstrap (and other CSS frameworks) &#8211; where column names are presentational rather than semantic. In other words, in a typical Bootstrap project the main content area might have a class name like <code>.col-md-8</code>, which is not semantic.</p>
<div class="entry">
<p>Fortunately, Bootstrap makes it pretty easy to define semantic classes that apply the built-in presentational logic, by use of the <code class="highlighter-rouge">make-x-column()</code>mixins.</p>
<h2 id="sage---example-of-semantically-defining-classes">SAGE &#8211; EXAMPLE OF SEMANTICALLY DEFINING CLASSES</h2>
<p>The <a href="https://roots.io/sage/">Sage</a> WordPress starter theme (a great starting point for WordPress projects) defines a <code class="highlighter-rouge">.main</code> and a <code class="highlighter-rouge">.sidebar</code> class out of the box.</p>
<p>The column widths for these elements can then be set in a <code class="highlighter-rouge">_variables.scss</code>file.</p>
<h2 id="offset-sidebar">OFFSET SIDEBAR</h2>
<p>I’ve modified the Sage definition of <code class="highlighter-rouge">.main</code> and <code class="highlighter-rouge">.sidebar</code> to add an offset to the sidebar, using the additional Bootstrap mixin <code class="highlighter-rouge">make-sm-column-offset()</code>.</p>
<p>I find the offset really good for user experience &#8211; constraining the main content area generally makes for greater readability, and the extra whitespace between content and sidebar can reduce the sensation of clutter.</p>
<pre><code class="sass">

// Grid system

.main {

// No sidebar, `.main` is full width
@include make-sm-column($main-sm-columns);

// `.main` is contained by the `.sidebar-primary` parent class -

// this class is added to the &lt;body&gt; of pages that display the sidebar.

// If a sidebar displays, `.main` can take a reduced width.

.sidebar-primary &amp; {

// `.main` is narrower by 2 columns, to give room for the offset
@include make-sm-column($main-sm-columns - $sidebar-sm-columns - 2 );

}

}

.sidebar {

// Set the width, then the offset, using Bootstrap mixins
@include make-sm-column($sidebar-sm-columns);
@include make-sm-column-offset(2);

}

</code></pre>
<h2 id="resources">RESOURCES</h2>
<ul>
<li><a href="https://www.w3.org/QA/Tips/goodclassnames">W3C on class names</a></li>
<li><a href="http://willschenk.com/bootstrap-advanced-grid-tricks/">Description of semantic classes in Bootstrap</a></li>
</ul>
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://davidegan.me/semantic-bootstrap-layout-classes-with-an-offset/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Checking Bash Scripts</title>
		<link>https://davidegan.me/checking-bash-scripts/</link>
					<comments>https://davidegan.me/checking-bash-scripts/#respond</comments>
		
		<dc:creator><![CDATA[David Egan]]></dc:creator>
		<pubDate>Mon, 05 Oct 2015 18:21:27 +0000</pubDate>
				<category><![CDATA[BASH]]></category>
		<category><![CDATA[Linux]]></category>
		<guid isPermaLink="false">http://davidegan.me/?p=692</guid>

					<description><![CDATA[I found this very useful resource that allows you check bash scripts online: http://www.shellcheck.net/ I&#8217;ve been writing quite a few bash scripts lately &#8211; amongst other things, I find them &#8230; <a href="https://davidegan.me/checking-bash-scripts/" class="more-link">More&#160;&#187; <span class="screen-reader-text">Checking Bash Scripts</span></a>]]></description>
										<content:encoded><![CDATA[<p>I found this very useful resource that allows you check bash scripts online:</p>
<p><a href="http://www.shellcheck.net/">http://www.shellcheck.net/</a></p>
<p>I&#8217;ve been writing quite a few bash scripts lately &#8211; amongst other things, I find them useful for running automatic backups and scaffolding out projects from a Github repo starting point.</p>
<p>The tool helps with:</p>
<blockquote><p>&#8230;typical beginner and intermediate level syntax errors and pitfalls where the shell just gives a cryptic error message or strange behavior, but it also reports on a few more advanced issues where corner cases can cause delayed failures.</p>
<ul>
<li>http://www.shellcheck.net/about.html</li>
</ul>
</blockquote>
<p>It is certainly helping me to improve my bash scripting.</p>
<h2>Linter Shellcheck in Atom</h2>
<p>You can set up Shellcheck as an <a href="https://atom.io/packages/linter-shellcheck">Atom package</a>.</p>
<p>In Ubuntu:</p>
<pre><code class="bash">
# Install shellcheck on your system
sudo apt-get install shellcheck

# Install Base linter for Atom
apm install linter

# Install shellcheck
apm install linter-shellcheck
</code></pre>
<p>You&#8217;ll need to restart Atom.</p>
<h2>Resources</h2>
<ul>
<li><a href="https://atom.io/packages/linter-shellcheck">Linter Shellcheck Atom Package</a></li>
<li><a href="https://github.com/atom-community/linter">Base Linter for Atom</a> (required)</li>
<li><a href="https://github.com/koalaman/shellcheck/wiki">Error explanations</a></li>
</ul>
<p>&nbsp;</p>
]]></content:encoded>
					
					<wfw:commentRss>https://davidegan.me/checking-bash-scripts/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Apollo Images</title>
		<link>https://davidegan.me/apollo-images/</link>
					<comments>https://davidegan.me/apollo-images/#respond</comments>
		
		<dc:creator><![CDATA[David Egan]]></dc:creator>
		<pubDate>Mon, 05 Oct 2015 16:23:58 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">http://davidegan.me/?p=696</guid>

					<description><![CDATA[Public domain image archive of the Apollo missions]]></description>
										<content:encoded><![CDATA[<p>As a kid I was obsessed with the Apollo missions &#8211; it&#8217;s great to see that NASA have <a href="https://www.flickr.com/photos/projectapolloarchive/">made the images available on Flickr</a> &#8211; with no copyright restrictions.</p>
<p><span id="more-696"></span></p>
<p>Here are a few images:</p>
<p><a href="https://www.flickr.com/photos/projectapolloarchive/21477024109/in/album-72157656682617854/"><img src="https://farm1.staticflickr.com/612/21477024109_eb617efac5_z.jpg" alt="AS17-145-22254" width="640" height="640" /></a></p>
<p><a href="https://www.flickr.com/photos/projectapolloarchive/21912173266/in/album-72157659042210300/"><img loading="lazy" src="https://farm6.staticflickr.com/5807/21912173266_44fc56c029_z.jpg" alt="AS09-20-3064" width="612" height="640" /></a></p>
<p><a href="https://www.flickr.com/photos/projectapolloarchive/21317188223/in/album-72157659042210300/"><img loading="lazy" src="https://farm1.staticflickr.com/765/21317188223_5d167c1662_z.jpg" alt="AS09-20-3071" width="612" height="640" /></a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://davidegan.me/apollo-images/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Adding a .htaccess File to a Jekyll Site</title>
		<link>https://davidegan.me/adding-a-htaccess-file-to-a-jekyll-site/</link>
					<comments>https://davidegan.me/adding-a-htaccess-file-to-a-jekyll-site/#respond</comments>
		
		<dc:creator><![CDATA[David Egan]]></dc:creator>
		<pubDate>Fri, 02 Oct 2015 15:40:26 +0000</pubDate>
				<category><![CDATA[Apache]]></category>
		<category><![CDATA[Jekyll]]></category>
		<guid isPermaLink="false">http://davidegan.me/?p=685</guid>

					<description><![CDATA[How to have Jekyll build a .htaccess file into your project]]></description>
										<content:encoded><![CDATA[<p>If you serve your site with Apache, adding a .htaccess file to your document root allows fine control over access permissions.</p>
<p>Amongst other things, .htaccess rules can set:</p>
<ul>
<li>In-browser caching</li>
<li>Access &#8211; you could allow/disallow access from certain IP addresses or ranges</li>
<li>Redirects</li>
<li>Rewrites</li>
</ul>
<p>You can also prevent modification of code over 3G on some European providers (I&#8217;ve experienced UK providers in particular totally mangling site styles).</p>
<p>When setting up WordPress sites, I would typically lock down access to the entire admin area by IP address as a security measure. While this isn&#8217;t necessary for Jekyll sites (where there is no login), .htaccess rules can be a useful way of controlling how your site resources are cached. This has the potential to speed up site loading times.</p>
<h2>.htaccess in Jekyll</h2>
<p><strong>Update: by default, Jekyll includes .htaccess files &#8211; so explicitly including your .htaccess file is unnecessary.</strong></p>
<p>By default Jekyll excludes dotfiles &#8211; but you can easily override this behaviour.</p>
<p>In the project config.yml (or config_prod.yml if you have a production environment config file), add this line:</p>
<pre><code class="php">
include: ['.htaccess']
</code></pre>
<p>Jekyll will now build the .htaccess file &#8211; which is much more convenient than editing the file on the server.</p>
<p><strong>Create .htaccess file in the root of your Jekyll project</strong>.</p>
<p>When you build  the project, the .htaccess file will be included in the project root.</p>
<h2>Sample .htaccess for Cache Control</h2>
<p>The following .htaccess directive tells browsers to use cached content for the specified files. Just add the directive to the project .htaccess file:</p>
<pre><code class="php">
# Set browser caching
# ------------------------------------------------------------------------------
&lt;IfModule mod_expires.c&gt;
ExpiresActive On
ExpiresByType image/jpg "access 1 year"
ExpiresByType image/jpeg "access 1 year"
ExpiresByType image/gif "access 1 year"
ExpiresByType image/png "access 1 year"
ExpiresByType text/css "access 1 month"
ExpiresByType text/html "access 1 month"
ExpiresByType application/pdf "access 1 month"
ExpiresByType text/x-javascript "access 1 month"
ExpiresByType application/x-shockwave-flash "access 1 month"
ExpiresByType image/x-icon "access 1 year"
ExpiresDefault "access 1 month"
&lt;/IfModule&gt;
# End caching block
</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://davidegan.me/adding-a-htaccess-file-to-a-jekyll-site/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Hide Git Repos on Public Sites</title>
		<link>https://davidegan.me/hide-git-repos-on-public-sites/</link>
					<comments>https://davidegan.me/hide-git-repos-on-public-sites/#respond</comments>
		
		<dc:creator><![CDATA[David Egan]]></dc:creator>
		<pubDate>Sat, 19 Sep 2015 10:39:36 +0000</pubDate>
				<category><![CDATA[Security]]></category>
		<guid isPermaLink="false">http://davidegan.me/?p=655</guid>

					<description><![CDATA[Don't provide would-be hackers with your source code!]]></description>
										<content:encoded><![CDATA[<p>If you use git to manage projects, you should be careful about publicly disclosing your repo &#8211; this would give would-be hackers access to your entire source code.</p>
<p><span id="more-655"></span></p>
<p>You should <strong>never</strong> include configuration or other sensitive files in version control for security reasons &#8211; gitignore is there for a reason.</p>
<p>Even if you keep sensitive files outside your git repo, it&#8217;s still important to restrict access to the .git directory for public-facing projects.</p>
<p>If your git directory is available, it would be simple for would-be hackers to:</p>
<ul>
<li>Download the .git directory (using a tool like <a href="http://www.gnu.org/software/wget/">wget</a>)</li>
<li>Run git commands on the downloaded repo</li>
</ul>
<p>I&#8217;ve tested this locally (on a test site) and it took no more than a few minutes to get full access to all the code contained in a downloaded git repo.</p>
<p>Even if you don&#8217;t have config files included, how do you feel about a complete stranger having full access to your source code? You might be happy enough, but bear in mind you are allowing potential attackers to go through your work with a fine-tooth-comb looking for vulnerabilites.</p>
<p>It&#8217;s one thing to consciously share a project on Github &#8211; it&#8217;s quite another to inadvertently give public access to potentially sensitive data.</p>
<h2>Am I Vulnerable?</h2>
<p>Enter &#8216;http://site.com/project-path/.git/config&#8217; in your browser URL bar, where &#8216;project-path&#8217; is the path to your version controlled directory. If you see something like this:</p>
<pre>[core]
   repositoryformatversion = 0
   filemode = true
   bare = false
   logallrefupdates = true
[remote "origin"]
   url = git@bitbucket.org:UserName/your-repo.git
   fetch = +refs/heads/*:refs/remotes/origin/*</pre>
<p>&#8230;you need to take action!</p>
<p>You could move your .git directory outside the document root, so that it is not publicly accessible. This is quite a good solution, though I have found it a bit fiddly when a project uses git submodules.</p>
<p>Another alternative is to selectively block public access to all files under the .git directory.</p>
<h2>Apache Fix</h2>
<p>If your site is served by Apache, and you have access to Apache config files, there is a very simple way of preventing access to git files.</p>
<p>Open /etc/apache2/conf-enabled/security.conf for editing:</p>
<pre><code class="bash">sudo nano /etc/apache2/conf-enabled/security.conf</code></pre>
<p>You will see the following block:</p>
<pre><code class="apache">
# Forbid access to version control directories
#
# If you use version control systems in your document root, you should
# probably deny access to their directories. For example, for subversion:
#
#&lt;DirectoryMatch "/\.svn"&gt;
# Require all denied
#&lt;/DirectoryMatch&gt;

</code></pre>
<p>Amend this to:</p>
<pre><code class="bash">

# Forbid access to version control directories
#
# If you use version control systems in your document root, you should
# probably deny access to their directories. For example, for subversion:
#
&lt;DirectoryMatch "/\.git"&gt;
Require all denied
&lt;/DirectoryMatch&gt;

</code></pre>
<p>Save (ctrl + o) and exit (ctrl + x) and restart apache:</p>
<pre><code class="bash">sudo service apache2 restart</code></pre>
<p>Now try accessing the .git/config file. You should see something like this:</p>
<pre>Forbidden
You don't have permission to access /wp-content/themes/david/.git/config on this server.</pre>
<p>If you DO NOT have access to Apache config files, add these lines to a .htaccess file in your project root:</p>
<pre><code class="bash">

# ==================================================================
# Prevent .git access
# ==================================================================

RedirectMatch 404 /\.git

</code></pre>
<p>It is also a good idea to disable directory indexing &#8211; which can be done either as part of Apache config, or by adding the following to your .htaccess file:</p>
<pre><code class="bash">

# ==================================================================
# DISABLE DIRECTORY BROWSING
# (probably not needed due to empty index files in directories)
# ==================================================================

Options All -Indexes
Options +FollowSymLinks

</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://davidegan.me/hide-git-repos-on-public-sites/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>WordPress Debugging</title>
		<link>https://davidegan.me/wordpress-debugging/</link>
					<comments>https://davidegan.me/wordpress-debugging/#respond</comments>
		
		<dc:creator><![CDATA[David Egan]]></dc:creator>
		<pubDate>Fri, 18 Sep 2015 11:53:51 +0000</pubDate>
				<category><![CDATA[Debugging]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">http://davidegan.me/?p=622</guid>

					<description><![CDATA[On-screen debugging output, writing errors to a log file, real time monitoring of the debug log]]></description>
										<content:encoded><![CDATA[<p>In general, error logging should be enabled in the development environment and disabled in production environments.</p>
<p>To enable WordPress error reporting to the browser, and to enable error logging to file, add the following lines to wp-config.php:</p>
<pre><code class="php">&lt;?php
// Enable error reporting output to browser. Default value is false.
 define( 'WP_DEBUG', true );
// log errors to `/wp-content/debug.log`. Useful when debugging code that does not output to browser.
define('WP_DEBUG_LOG', true);
</code></pre>
<p><span id="more-622"></span></p>
<blockquote><p>Enabling WP_DEBUG will cause all PHP errors, notices and warnings to be displayed. This is likely to modify the default behavior of PHP which only displays fatal errors and/or shows a white screen of death when errors are reached.</p>
<p>Showing all PHP notices and warnings often results in error messages for things that don’t seem broken, but do not follow proper data validation conventions inside PHP. These warnings are easy to fix once the relevant code has been identified, and the resulting code is almost always more bug-resistant and easier to maintain.</p>
<p>– WordPress Codex</p></blockquote>
<h2>Browser Debugging</h2>
<p>Enabling WP_DEBUG provides useful error messages rather than the “white screen of death”.</p>
<p>Even when code is not broken, debugging output will highlight deprecated WordPress functions and other problems.</p>
<h2>Logging</h2>
<p>The WP_DEBUG_LOG option is very useful for situations that do not involve output to the browser &#8211; for example AJAX requests. It is also useful when debugging classes or functions that pass data to other scripts.</p>
<p>You can make code write to the debug log using the PHP error_log() function.</p>
<p>For example, in the following code fragment, taken from a class method, the $column_name variable is output to the error log &#8211; so that we can check we’re getting the right values at this point in the programme:</p>
<pre><code class="php">&lt;?php

...

 // Headings
 // -------------------------------------------------------------------------
 foreach ( $wpdb-&gt;get_col( "DESC " . $table_name, 0 ) as $column_name ) {

 // output $column_name to the error log
 error_log( $column_name );
 $csv_output = $csv_output . $column_name . $this-&gt;delimiter;

 }</code></pre>
<p>&nbsp;</p>
<h2>Monitoring The Log File</h2>
<p>The log file must be owned by the server process &#8211; in the case of Ubuntu, this is ‘www-data’.</p>
<p>I often chown ownership back and forth for various reasons &#8211; so wrong ownership of debug.log often trips me up. When developing a theme or plugin, it’s probably best to create an alias giving ownership of just the theme or plugin directory to $USER &#8211; leaving the rest of the development site owned by www-data.</p>
<p>It can be useful to view the debug.log in realtime. The tail utility with the -f (follow) option is good for this.</p>
<p>Open a shell terminal (BASH) and add the following:</p>
<pre><code class="bash">tail -f /path/to/debug.log
</code></pre>
<p>This outputs the last 10 lines of debug.log, and the output data will be appended if the file grows &#8211; so you’ll have a realtime monitor of the debug log in your terminal. If you need more lines, append the -n flag to the tail command. To output the last 50 lines of the log file:</p>
<pre><code class="bash">tail -f /path/to/debug.log -n 50</code></pre>
<p>Alternatively, open debug.log in a text editor (e.g. gedit). After running your code, activate the editor window &#8211; under Ubuntu at least you’ll see a “changed on disk” notification &#8211; reloading the file will show the new log output.</p>
<h2>User Roles &amp; Capabilities</h2>
<p>It can sometimes be useful to output user capabilities onto the page, especially when you are fine tuning access for custom user roles.</p>
<p>A combination of the &#8216;get_role()&#8217; WordPress function and &#8216;var_dump()&#8217; is useful in this case:</p>
<pre><code class="php">
&lt;?php
// Debug: Dump user capabilities for 'student' custom user role

$user_role = 'student';
$user_caps = get_role( $user_role );
echo "&lt;pre&gt;&lt;h3&gt;$user_role Caps&lt;/h3&gt;";
var_dump( $user_caps );
echo "&lt;/pre&gt;";
</code></pre>
<p>You might also need to check available metadata for a given user:</p>
<pre><code class="php">
&lt;?php
// Debug: Dump usermeta

$student_meta = get_user_meta( $current_user_id );
echo "&lt;pre&gt;&lt;h2&gt;usermeta&lt;/h2&gt;";
var_dump( $student_meta );
echo "&lt;/pre&gt;";
</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://davidegan.me/wordpress-debugging/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
