Author Archives: Stanislav Khromov

About Stanislav Khromov

Web Developer at Aftonbladet (Schibsted Media Group)
Any opinions on this blog are my own and do not reflect the views of my employer.
LinkedIn
Twitter
WordPress.org Profile
Visit my other blog

Fix 404 errors when running apt-get update on Debian Wheezy

If you are getting errors similar to the ones below, keep reading for a fix.

Err http://http.us.debian.org wheezy/main mipsel Packages
  404  Not Found [IP: 64.50.233.100 80]
Err http://http.us.debian.org wheezy/contrib mipsel Packages
  404  Not Found [IP: 64.50.233.100 80]
Err http://http.us.debian.org wheezy/non-free mipsel Packages
  404  Not Found [IP: 64.50.233.100 80]
W: Failed to fetch http://http.us.debian.org/debian/dists/wheezy/main/binary-mipsel/Packages  404  Not Found [IP: 64.50.233.100 80]

W: Failed to fetch http://http.us.debian.org/debian/dists/wheezy/contrib/binary-mipsel/Packages  404  Not Found [IP: 64.50.233.100 80]

W: Failed to fetch http://http.us.debian.org/debian/dists/wheezy/non-free/binary-mipsel/Packages  404  Not Found [IP: 64.50.233.100 80]

E: Some index files failed to download. They have been ignored, or old ones used instead.

For normal servers

Edit /etc/apt/sources.list and replace the current servers in the file with http://archive.debian.org/debian.

Example – before

...
deb http://http.us.debian.org/debian wheezy main contrib non-free

Example – after

...
deb http://archive.debian.org/debian wheezy main contrib non-free

For Ubiquity EdgeOS routers

SSH into the console and write:

config
set system package repository wheezy url http://archive.debian.org/debian
commit ; save
apt-get update

Enable logging of DNS queries in Unbound DNS resolver

In order to enable logging in the Unbound DNS resolver, you have to add the following lines to your /etc/unbound/unbound.conf configuration file:

server:
    chroot: ""
    logfile: /var/log/unbound.log
    verbosity: 1
    log-queries: yes
    ...

Then, create the file and make sure it’s owned by the unbound process:

touch /var/log/unbound.log
chown unbound:unbound /var/log/unbound.log

Finally, restart Unbound:

/etc/init.d/unbound restart

Now you should be able to see the log:

tail -f /var/log/unbound.log
[1553775590] unbound[32655:0] info: 127.0.0.1 googlemail.l.google.com. A IN
[1553775609] unbound[32655:0] info: 127.0.0.1 acp-ss-ew1.adobe.io. A IN
[1553775695] unbound[32655:0] info: 127.0.0.1 clients4.google.com. A IN
...

The reason you have to add chroot: "" is because by default unbound runs in a chroot and can’t write to /var/log.

This post was tested on OpenWRT.

Redeploy all Convox apps in a rack using CLI and set RedirectHttps=No for selected apps

Convox recently started redirecting http to https but allows you to keep this behaviour by setting the apps param RedirectHttps=No. This script automates redeploying all applications in your current rack and lets you apply the RedirectHttps parameter to certain apps. (UPGRADE_HTTP_APPS)

Save as upgrade.php and run using php upgrade.php. (Authenticated Convox CLI is required)

<?php

define('RACK', 'org/rack');
define('UPGRADE_HTTP_APPS', ['my-http-app', 'my-second-http-app']);

exec('convox switch ' . RACK, $ret);

$appsCommandResult = [];
exec('convox apps', $appsCommandResult);

$apps = [];

// Get list of apps
foreach($appsCommandResult as $result) {
  $matches = null;
  preg_match('/([\w-_]*).*/', $result, $matches);

  if(isset($matches[1]) && $matches[1] !== 'APP') {
    $apps[$matches[1]] = '';
  }
}

// For each app, find active release
foreach($apps as $app => $release) {
  $releasesCommandResult = [];
  exec('convox releases -a ' . $app, $releasesCommandResult);

  foreach($releasesCommandResult as $result) {
    $matches = null;
    preg_match('/([\w-_]*).*active.*/', $result, $matches);

    if(isset($matches[1])) {
      $apps[$app] = $matches[1];
    }
  }
}

foreach($apps as $app => $release) {
  //Promote each app
  $promoteCommand = "convox releases promote {$release} -a {$app} --wait";
  echo $promoteCommand;
  echo "\n";
  $deployCommandResult = '';
  exec($promoteCommand, $deployCommandResult);
  var_dump($deployCommandResult);

  //If in UPGRADE_HTTP_APPS, set RedirectHttps=No
  if(in_array($app, UPGRADE_HTTP_APPS)) {
    $noHttpCommand = "convox apps params set RedirectHttps=No -a {$app} --wait";
    echo $noHttpCommand;
    echo "\n";
    $noHttpCommandResult = '';
    exec($noHttpCommand, $noHttpCommandResult);
    var_dump($noHttpCommandResult);
  }
}

Restrict viewing of uploaded attachments to logged-in users using WordPress and Nginx

Even if you lock down your WordPress site so outside visitors can’t see it, all uploaded attachment such as media, images and PDFs are available to anyone as long as they know the direct link to those files, or can find it by other means such as search engines. Sometimes that’s not acceptable and you want to make sure only logged in users can see uploaded media files.

This approach works by proxying all files in /wp-content/uploads through a script (dl-file.php) which should be placed in the root of your WordPress installation. The script checks whether the user is logged in before serving the file. If the user is not logged in a redirect is performed so that the user can log in before viewing the file.

Because this approach uses the same check as WordPress itself, it should be considered a very safe way of protecting attachments. You could also extend the proxy script to check for different access levels (for example if you have a membership site) and much more!

The Nginx config

# Hotlink protection
location ~ ^/wp-content/uploads/(.*) {
    try_files /dl-file.php =403;
    include fastcgi_params;
    fastcgi_pass php7;
}

This config should be placed in a server block and the example is for servers running EasyEngine, but it should work with minor modification on any php-fpm server.

The proxy script

<?php
/*
 * dl-file.php
 *
 * Protect uploaded files with login.
 *
 * Based on http://wordpress.stackexchange.com/questions/37144/protect-wordpress-uploads-if-user-is-not-logged-in
 * 
 * @author hakre <http://hakre.wordpress.com/> / khromov
 * @license GPL-3.0+
 * @registry SPDX
 */

require_once('wp-load.php');

//Check if we are logged in before attempting to serve the file
is_user_logged_in() || auth_redirect();

//FIXME ... simplify this
list($basedir) = array_values(array_intersect_key(wp_upload_dir(), array('basedir' => 1)))+array(NULL);

//Check if file exists
$getFile = isset($_GET[ 'file' ]) ? $_GET[ 'file' ] : $_SERVER['REQUEST_URI'];

//Normalize file path
$getFile = str_replace('..', '', $getFile);
$getFile = str_replace('wp-content/uploads/', '', $getFile);

//This provides a notice in the log
trigger_error('Loading protected file ' . $getFile);

$file =  trailingslashit($basedir) . $getFile;

//If file is missing, 404 it
if (!$basedir || !is_file($file)) {
    status_header(404);
    die('404 &#8212; File not found.');
}

$mime = wp_check_filetype($file);
if( false === $mime[ 'type' ] && function_exists( 'mime_content_type' ) )
    $mime[ 'type' ] = mime_content_type( $file );

if( $mime[ 'type' ] )
    $mimetype = $mime[ 'type' ];
else
    $mimetype = 'image/' . substr( $file, strrpos( $file, '.' ) + 1 );

header( 'Content-Type: ' . $mimetype ); // always send this

// If we made it this far, just serve the file
readfile( $file );

Similar StackOverflow question

Photo by Micah Williams on Unsplash

Fix broken / overlapping Instagram embed for WordPress

At some point recently (February 2018), Instagram broke their oEmbed implementation which causes JavaScript errors and embeds that overlap each other. Use the snippet below to mitigate this issue and hopefully Instagram will fix it in the future. The snippet makes sure only one

/**
 * Remove Instagram embed.js script on each embed
 */
add_filter('embed_oembed_html', function($html, $url, $attr, $post_id) {
  $regex =    '/<script.*instagram\.com\/embed.js.*\s?script>/U';
  $regex_2 =  '/<script.*platform\.instagram\.com\/.*\/embeds\.js.*script>/U';

  if(preg_match($regex, $html) || preg_match($regex_2, $html)) {
    add_filter('kh_has_instagram_embed', '__return_true');

    $html = preg_replace($regex, '', $html);
    $html = preg_replace($regex_2, '', $html);

    return $html;
  }

  return $html;
}, 100, 4);

/**
 * Enqueue the embed.js script once at the bottom of the page, if at least one Instagram embed is enqueued
 */
add_filter('wp_footer', function() {
  if(apply_filters('kh_has_instagram_embed', false)) :
    ?>
      <script async defer src="//www.instagram.com/embed.js"></script>
    <?php
  endif;
}, 999);

Shorthand caching pattern in PHP

Here’s a short and simple caching pattern you can use to cache an expensive function call:

if(!$result = Cache::get('expensive-function-cache-key')) {
  $result = expensive_function();
  Cache::set('expensive-function-cache-key', $result);
}

//The result variable will always contain the proper value, whether the call was cached or not.
echo $result;

Deploy any Git branch to your Heroku application

It’s pretty common on Heroku to have a staging app and a production app, with a Pipeline to promote changes from staging to production.

Typically if you use a pipeline, you write git push heroku master to push the latest master branch to your staging app and then promote it to production through the Heroku web UI.

But sometimes you may wish to temporarily test a different branch than master on your staging app. To do this, run:

git push heroku your-feature-branch:master

Where your-feature-branch is the name of the branch you want to deploy.

Remember that you shouldn’t promote this version to production. What you do is to merge the branch with master first after you’ve tested it and run git push heroku master to deploy the master branch to staging again before you finally promote your changes to production.

Source

List authors by post count in WordPress using MySQL

A query to list authors / users by post count:

SELECT wp_users.ID, wp_users.user_nicename, COUNT(*) as count FROM wp_posts, wp_users WHERE wp_posts.post_type='post' AND wp_posts.post_status='publish' AND wp_posts.post_author = wp_users.ID GROUP BY post_author ORDER BY count DESC LIMIT 5 ;

Resulting table:

ID,user_nicename,count
29,"user-a",18
66,"user-b",16
26,"user-c",10
24,"user-f",9
48,"user-z",6