Setting up XAMPP for WordPress Development on Windows

Background

I’ve spent the last 3-4 years doing my WordPress development in the Cloud9 IDE (c9.io) until Amazon AWS bought it, fucked up a perfectly good and extremely simple, usable IDe, and now shut down the original service.

I am in the process I switching over to the Codeanywhere IDE as my replacement WordPress development environment, however, I decided it’s time to setup a secondary/backup environment. And being a user of Windows I needed an WAMP (Windows, Apache, MySQL, PHP) style solution to suite.
In the past I’d tried WampServer on my secondary laptop but after leaving and coming back to it, it worked no more. Also, I found it more complicated than I wanted to configure (I hate complicated, particularly in development environments.

So this time I tried something different: XAMPP.

 

System/XAMPP Setup and Configuration

Setup it really easy. Just go to https://www.apachefriends.org/, select the download for you operating system (in my case Windows) and install.

For Windows do the default install, as the following screenshots show.

Step 1:

Step 2:

Step 3:

Step 4:

Step 5:

Step 6:

Step 7:

Step 8:

Step 9 (select your language – English or German):

Step 10: If you ticked “Do you want to start the Control Panel now?” in step 8 then the control panel should start and appear. Services will not start by default (there is an option for that in the control panel Config though).

See the next section for running Apache beside IIS.

 

Conflicts with IIS

If you have IIS installed (as many software developers with Windows will) then you will have conflicts on Port 80. The XAMPP FAQ suggests disabling IIS, but if you’re also a .NET developer like me then you most likely need IIS.

I chose to instead change the port XAMPP uses and this page – How to: Change Your Apache Listen Port Number in XAMPP – Alex Justesen – has all the information you need.

When XAMPP Control Panel first loads you will probably see the following errors:

This is the full log text:

When following the “Alex Justesen” blog post above, the following screenshot show the Apache “Config” and httpd.conf option you need to select.

The general configuration screen for XAMPP (note: I changed my editor from notepad.exe to Notepad++). This is the screen with the “Service and Port Settings” button the “Alex Justesen” post references:

The “Service and Port Settings” screen referenced by the “Alex Justesen” post. I changed “Main Port” to 8080 and “SSL Port” to 4443

Once you finish the the “Alex Justesen” post you can come back to the main XAMPP Control Panel and start Apache and MySQL using the “Start” buttons beside each service:

When starting Apache for the first time you will see the following Windows Security Alert. Select “Allow access” for the service to run:

When starting MySQL for the first time you will see the following Windows Security Alert. Select “Allow access” for the service to run:

You should now see the following success status and logs in XAMPP Control Panel after starting Apache and MySQL:

Select the “Admin” button on the MySQL line to easily open phpmyadmin (you will need this in the section “Setup WordPress”). Note it loads in localhost on your new port:

Select the “Admin” button on the Apache line to easily open XAMPP Dashboard page:

 

XAMPP Control Panel Shortcuts

There are no shortcuts installed to the XAMPP Control Panel (as far as I can tell), so look for xampp-control.exe in your XAMPP installation directory – likely C:\xampp if you use the default install directory.
You will need to run the .exe directly or create your own shortcut to it.

 

Setup Multiple Sites/Ports in XAMPP

Setting up multiple sites in XAMPP (running localhost on multiple ports) was easy.

I followed the following 2 pages:

What you nee to do:

  1. Open “C:\xampp\apache\conf\httpd.conf” (you can do this via the Apache “Config” button in the XAMPP Control Panel.
  2. Look for the line “Listen 80”. Add new Listen lines for any other ports you want to listen for (e.g. I started with 8080 to replace 80, and now I’m adding 8081).
  3. Open “C:\xampp\apache\conf\extra\httpd-vhosts.conf”.
  4. Everything will be commented out, but at the end there is a VirtualHost example like this:
    ##<VirtualHost *:80>
    ##ServerAdmin webmaster@dummy-host2.example.com
    ##DocumentRoot "D:/xampp/htdocs/dummy-host2.example.com"
    ##ServerName dummy-host2.example.com
    ##ErrorLog "logs/dummy-host2.example.com-error.log"
    ##CustomLog "logs/dummy-host2.example.com-access.log" common
    ##</VirtualHost>
  5. Copy and un-comment the entry, then adjust as needed. I created a new site root folder at “C:/xampp/htdocs/8081”, so my entry looks like:
    <VirtualHost *:8081>
        ServerAdmin webmaster@dummy-host2.example.com
        DocumentRoot "D:/xampp/htdocs/8081"
        ServerName dummy-host2.example.com
        ErrorLog "logs/dummy-host2.example.com-error.log"
        CustomLog "logs/dummy-host2.example.com-access.log" common
    </VirtualHost>
  6. “Stop” then “Start” Apache in XAMPP Control panel and you should be good.

 

Setup a DocumentRoot outside of XAMPP ‘htdocs’

I needed to the root document of a site to a folder outside the standard XAMPP installation ‘htdocs’ folder (i.e. pointing to a workspace for my source controlled files).

I followed all the steps above but received the following 2 errors when setting up another environment:

Access forbidden!
You don't have permission to access the requested directory. There is either no index document or the directory is read-protected.

If you think this is a server error, please contact the webmaster.

Error 403
localhost
Apache/2.4.39 (Win64) OpenSSL/1.1.1b PHP/7.3.5

(Resource used: https://stackoverflow.com/questions/17816732/xampp-access-forbidden-php)

and later followed by:

Object not found!
The requested URL was not found on this server. The link on the referring page seems to be wrong or outdated. Please inform the author of that page about the error.

If you think this is a server error, please contact the webmaster.

Error 404
localhost
Apache/2.4.39 (Win64) OpenSSL/1.1.1b PHP/7.3.5

 

(Resource Used: https://stackoverflow.com/questions/17129612/xampp-localhost-returns-object-not-found-after-installing-laravel)

To resolve the issues I ended up with the following VirtualHost configuration:

<VirtualHost *:8081>
    DocumentRoot "C:/Workspaces/my-project"
    ErrorLog "logs/8081-error.log"
    CustomLog "logs/8081-access.log" common
    
    <Directory C:/Workspaces/my-project/> 
        AllowOverride All
        Require all granted 
    </Directory>
</VirtualHost>

Note the <Directory></Directory> element with ‘AllowOverride’ and ‘Require’ lines. That fixed it.

 

Setup WordPress

Note: I have core WordPress and all plug-ins under source control.

Steps to get WordPress running:

  1. Login to phpmyadmin for MySQL. You can do this via the “Admin” button beside MySQL in the XAMPP Control Panel.
  2. Create a new database for your WordPress installation.
  3. (I tried creating a new user to user as the DB user in WordPress but couldn’t get it working, so I’m just using root in this environment.
  4. Setup your site files in the website folder. In my environment C:\xampp\htdocs is the root web folder. I created a sub-folder for my WordPress site and cloned my Git repo in there.
  5. Browser to your site. My apache is running under for 8080 and my site is in a folder called “portal”, so I browser to http://localhost:8080/portal.
  6. Go through the usual process for setting up a new WordPress website.
    1. In the screen for database settings, enter the name of the database you created.
    2. Set the username to “root” and leave password blank.
    3. Leave host name as “localhost”.
  7. Your WordPress site should be good to go!

 

The following screenshots show the standard setup screens for a new WordPress site.

Step 5a – Select a language:

Step 5b – Enter your database connection details. I went with the “root” user (no password) that is created when installing MySQL with XAMPP. You will need to first create a database via the phpmyadmin interface (see steps 1 and 2):

Step 5c – WordPress should now be good to setup:

Step 5d – Create your first admin user:

Step 5e – WordPress should be successfully setup:

Step 5e – And now you can login with your new admin user:

 

 

Resolving display of /var/www/html/dportal.com/wp-content/plugins/query-monitor/wp-content/db.php

After getting WordPress running I immediately started to see “/var/www/html/dportal.com/wp-content/plugins/query-monitor/wp-content/db.php” echoed to every page.

A quick search lead me to HTML Output on site migration · Issue #101 · johnbillion/query-monitor and this comment gave me the answer.

I deleted wp-content/db.php file and no more text.

As this stage I’m not sure what is going to happen when I do this for real and start working in both Windows and my Linux-based IDE.

 

Resources

XAMPP FAQ – https://www.apachefriends.org/faq_windows.html

How to Install XAMPP and WordPress Locally on PC/Windows – WPMU DEV

How to Troubleshoot and Fix Common XAMPP and WordPress Issues – WPMU DEV

 

WordPress: Changing File Upload and Processing Sizes

[This is a post for self-reference]

Up front I’m going to say I got exactly the help I need from here: https://www.wpbeginner.com/wp-tutorials/how-to-increase-the-maximum-file-upload-size-in-wordpress/.
The author of that post deserves all my credit.

I’m creating this post to summarise it in my own words and to give it a little educational context.


When allowing file uploads on a website there are 3 big things you need to think about:

  • The size of each file.
  • The total size of all files in an upload.
  • The time it takes to process the upload.

Actually, there are a couple of things to consider that are worthy of mention but I won’t cover in this post:

  • Asynchronous uploads (AJAX as we used to call them) can change the figure of an upload, because instead of uploading multiple files at one, you upload one file at a time (even if they are in parallel) so the “total size of all files” number will be different.
  • Tell users about the limits they face before they upload. Also, when uploading images a bit of guidance around things like “27MB images are a stupid side for most sites because 27MB isn’t going to be better than 7MB, and the site will trim the size down to a couple of hundred KB at most anyway.”
  • Trap, log and respond to any errors. This is on my mind because WordPress/PHP seems to like gobbling errors and not telling anyone about it (I’m in the process of hunting down how to handle this and will be a topic of another post).

 

There are 3 main ways in PHP to affect file upload sizes and times:

  • @set_init() in a PHP script itself.
  • php.ini file
  • .hataccess file.

Be aware, as a PHP/Linux novice I reckon there are other defaults and hierarchies that come into play.
This article is spawned by a situation where I was changing php.ini without realising .htaccess had a value overriding it.

 

How do you implement these values?

This is where I rip from the wpbeginner site I mentioned above for quick reference:

1. PHP script file (e.g. functions.php in WordPress themes)

@ini_set( 'upload_max_size' , '64M' );
@ini_set( 'post_max_size', '64M');
@ini_set( 'max_execution_time', '300' );

(I haven’t tried this approach yet, but I have used @ini_set() in other instances (e.g. ‘memory_limit’) and it’s worked well.

2. php.ini

upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 300

I’ve used this approach in development and it did the trick.

3 .htaccess

php_value upload_max_filesize 64M
php_value post_max_size 64M
php_value max_execution_time 300

Before writing this post I had to fix a production issue and after trying php.ini I got lucky and discovered .htaccess was where the value was coming from.
So the moral: .htaccess overrides php.ini.

Also, I did not need to restart apache for this change to take affect.

 

DISCLAIMER: I haven’t actually read in-depth how all this works. I’m a typical developer who expects other people’s shit to work, and when it doesn’t I’m usually in the middle of my own concerns so it’s a case of “search and scan by keyword until someone else’s solution works for me”.
Such is this age of software development.

 

Need to know what your current settings are?

It’s helpful in debugging and verification to know what your actual PHP settings are so you know what your starting point is (and  you have a number to search your files for).

A good way is to call phpinfo();.

For example, create a file in your site (I call mine info.php) and add the following PHP code:

<?php
phpinfo();
?>

You should see something like the following when you run it. This screenshot shows some output from the section titled “Core”, and the 3 columns are (from left to right): “Directive”, “Local Value” and “Master Value”. I can say from experience the “Local Value” is the one that matters.

phpinfo() output

Change a WordPress site URL (when copying or migrating a WordPress site)

This is a post for quick reference. But given 25% (or more) of the Interwebs run on WordPress I want to give it some love too.

Background

Moving (migrating) or copying a WordPress site has some hidden complexity.

While it is easy, there are 3 things you need to do:

  1. Copy the whole website structure, starting with the folder than contains the wp-config.php and other files (it also contains the  “wp-admin”, “wp-content” and “wp-includes” folders).
  2. Change the URL of the site as defined in WordPress (ordinarily set during setup, or within the Settings area).
  3. Update all the saved URLs within the database.

Step 3 is the real gotacha. When you add a link to another page within your site, or insert media, or set a navigation item, it saves the full URL of that item in the database.

For example, if you have a site “https://MySite.com” but want to change it to “https://MyNewSite.com” (or as a software developer, you may want to create a copy of the production site at “https://test.MySite.com”) then after you change the actual URL of the site, all the internal links and images will still be pointing to the original MySite.com.

 

Solution

Many sites have covered how to change a WordPress site URL before, including the official Codex at https://codex.wordpress.org/Changing_The_Site_URL.

I like to keep things simple, so this post is my process:

  1. Backup your site database if you can (I connect directly to the database and run backup scripts, or use phpmyadmin and export).
  2. Update your site URLs by adding the following lines to the top of your active theme’s functions.php file:
    update_option( 'siteurl', 'https://MyNewSite.com' );
    update_option( 'home', 'https://MyNewSite.com' );
    
    1. Browser to your new site (via the IP or URL).
    2. Browse to it again and make sure it’s OK.
    3. Remove the lines from functions.php
    4. Browser to the site again one more time to make sure you can access it now you’ve removed the lines from functions.php.
  3. Upload the interconnect/it “Database Search and Replace Script” script to our site, found at https://interconnectit.com/products/search-and-replace-for-wordpress-databases/.
    1. Install it in a “not easily guessed” sub-directory of the site. This is for security reasons (so other people can’t scan and execute the scripts).
    2. Most fields are filled in. In the “search for…” enter your old URL or IP address, including “http” or “https” (e.g. “https://MySite.com” – you should be using https by now). I don’t end with a trailing forward slash.
      In the “replace with…” field enter the new URL or IP Address (e.g. “https://MyNewSite.com”). Again, not trailing forward slash.
    3. Make sure you do “dry run” to start. This should tell you if you have any problems.
    4. If all is good, do a “live run” and update your database.
    5. At the end, delete the “Database Search and Replace Script” from your site. This is important. If you leave it there bad people may find it and execute it and break your site.

Install XML RPC for PHP 7.2 on Ubuntu Linux

Background

One of the components in a web application solution we’re developing uses xmlrpc_encode_request() in PHP.

It was executing fine in most of our systems systems – development and multiple test servers – but while trying to run it for the first time in my development environment I received the following error:

Fatal error: Uncaught Error: Call to undefined function xmlrpc_encode_request()

So off to Google I go and, find a few answers, have a couple of mis-starts, then finally get it working.

 

Solution

The solution was very simple.

  1. Install the xmlrpc extension.
    sudo apt-get install php-xmlrpc
  2. Restart Apache web server
    sudo service apache2 restart

As a sanity check, create a PHP file (e.g. info.php) and add the following:

<?php
echo 'XMLRPC is ', extension_loaded('xmlrpc') ? 'loaded' : 'not loaded';
?>

When you run the file it you should see “XMLRPC is loaded” if all is good.

If you’re still having trouble, try updating your php.ini file to include:

extension=xmlrpc.so;

and/or

extension=php_xmlrpc.so;

Unfortunately, if you still have trouble then I apologise and suggest hitting Google/Bing/DuckDuckGo/StackOverflow again and playing around with search terms that meet your specific Linux/OS/PHP versions.

 

More Background

I started with a search for “Fatal error: Uncaught Error: Call to undefined function xmlrpc_encode_request” then “lamp Fatal error: Uncaught Error: Call to undefined function xmlrpc_encode_request”, and had these results:

But they were aimed at PHP 5.x (by the way, if you’re still on PHP 5.x then update! It’s no longer supported and you’re now a security risk).

Then I refined my search to “apt-get php 7.2 xmlrpc” and had:

And I found the helpful suggestion for the info PHP file (you could also just run phpinfo(); in a file) at:

Git: branch commit is in the future

I was just checking some branch commits from one of my developers and noticed the commits where 22 hours in the future.

It turns out they’d changed their system time for testing and forgot to revert it before pushing.

It makes sense when I think about it.

Because git is a distributed system, it would use the local system time when committing.
Git doesn’t track track off a server time.
That means you can be totally disconnected from other systems and still make commits – in that case the only time it knows is the system time.

That doesn’t make it any less confusing or counter-intuitive in an age of servers, synchronisation and centralisation.

Visually hidden field with ‘required’ attribute causes error: “An invalid form control with name=’xxxxx’ is not focusable.”

Situation

Receive a JavaScript console error of An invalid form control with name='xxxxx' is not focusable when trying to submit the form.

I asked one of my developers to comment out a field in a web page as part of a requirements change (I prefer to comment out initially before deleting so we are sure everything works fine with the change).

The developer added a style="display: none" to a surrounding div to hide a <select> field.

However, this in turn caused a JavaScript console error of An invalid form control with name='xxxxx' is not focusable when trying to submit the form.

 

Solution

Remove the “required” attribute from the form element.

In helping my developer I did a Google search for “an invalid form control with name=” is not focusable” and one of the first results lead to https://stackoverflow.com/questions/22148080/an-invalid-form-control-with-name-is-not-focusable.

On that page I immediately saw “Adding a novalidate attribute to the form will help” and that made me to immediately remember there is a “required” attribute still on the field that was hidden.

Experience and instinct kicked in.

We removed the “required” attribute and the form submission worked.

My guess is when the form was submitted the native browser-based validation did not work correctly because the specified field is “visually” hidden so the native validation message could not be shown, and that triggered the error in the console.

RegEx: Find occurrences in code, except when commented

Background

I’ve created a debug helper function in my PHP/WordPress development called TraceIt($value, $type, $param1).
TraceIt does a bit more than just echo out a value. It will format echo out based on value type or specified type, can be environmentally switched to not echo, and can write to a log file.

It’s also easier to find where actual tracing is being used versus the legitimate echo output.

The problem is, sometimes I forget to comment a trace, which is no good, especially when trying to perform a redirect.
And while I usually include a string value in the output that I can search for, sometimes in the heat of the debugging moment I forget that.

 

Solution

The easiest [and some may argue “painful”] way to find occurrences of forgotten TraceIt calls is regex.

What I needed was a way to find all instances of “TraceIt” but not “//TraceIt” (already commented).

And here it is:

^(?!([ \t]*\/\/[ \t]*TraceIt)|[ \t]*\/\/).*((^TraceIt)|( TraceIt)|(\tTraceIt)|(;TraceIt))

You can see it in action, with an explanation of each component, at https://regexr.com/46nbq.

The only problem with this is it also picks up instances of TraceIt on a line that already has //TraceIt earlier in the line.  But I can live with those rare occurences for now.

Oh, and don’t forget to use the /gim flags (global, case insensitive and multi-line).

 

The regex will find occurrences like this:

coding is here; TraceIt("aaaa");
TraceIt("aaaa");
another line of code;
TraceIt('bbb'); more code
TraceIt('ccc');
TraceIt('bbb');
some more code; TraceIt("adsfadfa");

But not like this:

//TraceIt("aaaa");
//TraceIt("bbb"); TraceIt("ccc")
// TraceIt("ddd");
// TraceIt("ddd");
// TraceIt("eee");

another line of code;

some code; //TraceIt('fff');
    //     // TraceIt("gggg");

platform_browser_dynamic_1.platformBrowserDynamic is not a function

I received the following error today in a project I’m working on (the relevant part is in bold):

MainModule.bundle.js:27136 Uncaught TypeError: platform_browser_dynamic_1.platformBrowserDynamic is not a function
   at Object.__decorate (MainModule.bundle.js:27136)
   at __webpack_require__ (Polyfills.bundle.js:55)
   at webpackJsonpCallback (Polyfills.bundle.js:26)
   at MainModule.bundle.js:1

You can see my response to a similar problem at https://github.com/angular/angular/issues/10732#issuecomment-417173741.

And my response here as well, for completeness:

I just had the same issue in a project I’m working on. We’re using Angular 5.0.2 and Webpack on Windows in an IIS/ASP.NET project, with our primary browser as Chrome.
My issue randomly appeared after rebooting my PC and loading up my dev environment again (no code or configuration changes).
I managed to resolve the issue after a couple of attempts at starting the project (in Visual Studio) by also browsing to the the app URL Microsoft Edge. It loaded find. I then did a hard refresh (Ctrl+F5) in Chrome and it came good too.
Note: we manually start and leave running Webpack in separate command window, but start the actual application from withing Visual Studio.
My guess is Webpack crapped out behind the scenes and needed some time and a hard refresh in the browser to get the correctly compiled code to the browser again.

JavaScript helper: getMaxLengthString [TypeScript]

Here’s a quick JavaScript helper snippet to return a trimmed string based on the calculated maximum length of a string to fit the width of a parent container element.

Written in TypeScript.

/**
 * Takes a full string and returns a trimmed version to fit within a parent space.
 * @param fullLengthString  The full length string to trim to a parent space.
 * @param font              String representing the font styling of the text (required to calculate text length).
 * @param maxLengthPixels   Max pixel width of the parent space to fit the text into.
 * @param removeExtraChars  The number of extr characters to remove from the trimmed space, if required. (Default: 0)
 */
private getMaxLengthString(fullLengthString: string, font: string, maxLengthPixels: number, removeExtraChars: number = 0): string {
	let maxLengthString: string = "";

	let canvas = document.createElement("canvas");
	let context = canvas.getContext("2d");
	context.font = font;

	for (let i = 0; i < fullLengthString.length; i++) { // Build the string maxLengthString = maxLengthString + fullLengthString.charAt(i); // Test the length of the string let metrics = context.measureText(maxLengthString); if (metrics.width > maxLengthPixels) {
			// Trim the string to a max length.

			// The number of characters to remove.
			let removeChars = 1;   // At this point the length is too long, so remove 1 character to get it under.
			if (removeExtraChars > 0) {
				removeChars = removeChars + removeExtraChars;
			}
			removeChars = removeChars * -1; // To make sure they're sliced from the end.

			// Remove characters from the string.
			maxLengthString = maxLengthString.slice(0, removeChars);

			return maxLengthString;
		}
	}

	// Full string is OK.
	return maxLengthString;
}