PHP/MySQL Tutorials

on in Blog
Last modified on

Updated on October 6th, 2020, to include PHP 7+ compatibility and to better format the code snippets.

Table of Contents

How to calculate the percentage of a number from another number

Sometimes called the rule of three, this is how you calculate the percentage of a number from another number.

For example, you have 2 numbers, 34 and 116. Let’s say 116 is the total number (100%), and we want to find out what percentage is 34 out of 100%.

Here’s how:

<?php
function percentage($amount, $total, $decimal = 2) {
    if (0 === (int)$total) {
        return $total;
    }

    return number_format((((int)$amount / (int)$total) * 100), $decimal);
}

echo percentage(34, 116) . '%';

It’s simple math.

How to check your website for uptime

One of my clients asked me to monitor her site’s uptime. While this script is not yet complete, and it should use a CRON job and a mail() function, here’s how to check for uptime and current HTTP status code:

<?php
$toCheckURL = 'https://getbutterfly.com/'; // The domain name of the site you want to check

// This all sets up the CURL actions to check the page
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $toCheckURL);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_MAXREDIRS, 10); //follow up to 10 redirections - avoids loops
$data = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); // Get the HTTP Code

// Get final redirected URL, will be the same if URL is not redirected
$new_url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); 
curl_close($ch);

// Array of HTTP status codes. Trim down if you would like to.
$codes = [
    0 => 'Domain Not Found',
    100 => 'Continue',
    101 => 'Switching Protocols',
    200 => 'OK',
    201 => 'Created',
    202 => 'Accepted',
    203 => 'Non-Authoritative Information',
    204 => 'No Content',
    205 => 'Reset Content',
    206 => 'Partial Content',
    300 => 'Multiple Choices',
    301 => 'Moved Permanently',
    302 => 'Found',
    303 => 'See Other',
    304 => 'Not Modified',
    305 => 'Use Proxy',
    307 => 'Temporary Redirect',
    400 => 'Bad Request',
    401 => 'Unauthorized',
    402 => 'Payment Required',
    403 => 'Forbidden',
    404 => 'Not Found',
    405 => 'Method Not Allowed',
    406 => 'Not Acceptable',
    407 => 'Proxy Authentication Required',
    408 => 'Request Timeout',
    409 => 'Conflict',
    410 => 'Gone',
    411 => 'Length Required',
    412 => 'Precondition Failed',
    413 => 'Request Entity Too Large',
    414 => 'Request-URI Too Long',
    415 => 'Unsupported Media Type',
    416 => 'Requested Range Not Satisfiable',
    417 => 'Expectation Failed',
    500 => 'Internal Server Error',
    501 => 'Not Implemented',
    502 => 'Bad Gateway',
    503 => 'Service Unavailable',
    504 => 'Gateway Timeout',
    505 => 'HTTP Version Not Supported'
];

// Check if we have a valid HTTP code from the above list
if (isset($codes[$http_code])) {
    echo 'Website returned status code: ' . $http_code . ' - ' . $codes[$http_code] . '<br>';

    // Get the headers from the $data to the $matches variable
    preg_match_all("/HTTP\/1\.[1|0]\s(\d{3})/", $data, $matches);
    array_pop($matches[1]); // Remove the last status that we just displayed above which should be 200
    if (count($matches[1]) > 0) {
        // Loop through all other matches to see what other status codes we got
        foreach ($matches[1] as $c) {
            echo $c . ' - ' . $codes[$c] . '<br>';
        }
    }

    // Can use this to check if the URL is still the same as you checked
    // Or if the server has moved
    if ($toCheckURL != $new_url) {
        echo 'URL has changed to: ' . $new_url;
    }
}

// This is just to show you the headers of the website you are checking
echo '<pre>';
var_dump($data);
echo '</pre>';

Further steps would involve adding the data to a database and creating charts and graphs.

How to optimise images on a server using PHP

Let’s suppose you don’t use WordPress or another CMS that allows you to bulk optimise your JPEG images. Let’s suppose you have multiple folders and it’s nearly impossible to use an online optimizer due to directory restrictions or volume of work.

So, you create a PHP file called io.php and add the following code:

<?php
ini_set('memory_limit', '512M');
ini_set('max_execution_time', 600); // 10 minutes

$dir = "uploads/{*.jpg}";
// Open a known directory, and proceed to read its contents
$images = [];
foreach (glob($dir, GLOB_BRACE) as $key => $img) {
    echo 'Optimising <b>' . $img . '</b>...<br>';
    $image = imagecreatefromjpeg($img);
    imagejpeg($image, $img, 60); // from 0 to 100 (percentage)
}

Save it, upload it to your site root using your FTP client and run it – example.com/io.php. After several minutes, the listing stops and all your images have been optimised.

The only variables you need to modify are the directory path (uploads in the code above) and the level of compression (usually 60 to 75 is the most common).

How to get the average from an array of values

So, you pushed all your values to an array, and now you need to calculate the average value. Even if you have thousands of values, this function will iterate through all array items and return the average value:

<?php
function get_average($numbers, $decimals) {
    $a = $numbers;
    $b = 0;
    $c = 0;
    $d = 0;
    foreach ($a as $b) {
        $c = $c + $b;
        $d++;
    }

    return number_format($c/$d, $decimals);
}

This is how you display the average value:

<?php
$myarray = [];
// push elements to array
echo get_average($myarray, 2);

The function works both with integer values and with float values. Make sure you actually declare the variable as an array before pushing items into it. Some PHP versions might complain about this. Use var_dump($myarray) or print_r($myarray) to check if your array is well-formed.

As a bonus, this is how I use print_r():

<?php
function array_dump($array) {
    echo '<pre>';
        print_r($array);
    echo '</pre>';
}

Enjoy!

How to archive a folder using a form

Let’s suppose you want to create a small PHP function to archive various folders on your server. I had to code a plugin to generate archives from various folders for a site with no FTP access (don’t ask).

Excluding the WordPress specific code, here’s the HTML form:

<form method="post" action="" name="zipMe">
    <p><input type="text" name="folderPath" id="folderPath" placeholder="Path to your directory..."> <label for="folderPath">Path to your directory</label></p>
    <p>input type="submit" name="zipMeNow" value="Zip and download"></p>
</form>

Here’s the PHP code:

<?php
if (isset($_POST['zipMeNow'])) {
    $folderPath = $_POST['folderPath']; // sanitize this value

    ini_set('max_execution_time', 3600); // increase PHP execution time for large files

    // get path for the folder // I have zipped the /themes/ folder as an example
    $rootPath = '/home/xxxxxxxx/public_html/wp-content/themes/';

    $zip = new ZipArchive();
    $zip->open('file.zip', ZipArchive::CREATE | ZipArchive::OVERWRITE); // change filename or make it dynamic

    $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($rootPath), RecursiveIteratorIterator::LEAVES_ONLY);

    foreach ($files as $name => $file) {
        if (!$file->isDir()) { // skip directories (they will be added automatically)
            // get real and relative path for current file
            $filePath = $file->getRealPath();
            //$relativePath = substr($filePath, strlen($rootPath) + 1); // use this if you get a corrupt archive
            $relativePath = substr($filePath, strlen($rootPath));

            // add current file to archive
            $zip->addFile($filePath, $relativePath);
        }
    }

    // Zip archive will be created only after closing object
    $zip->close();

    // send headers to force download the file // or send it to Dropbox or Google Drive
    header("Content-type: application/zip");
    header("Content-Disposition: attachment; filename=file.zip");
    //header("Content-length: " . filesize('file.zip')); // not necessary
    header("Pragma: no-cache");
    header("Expires: 0");
    readfile("file.zip");
    exit;
}

You don’t need to worry as the ZipArchive is a native PHP class. I am checking for its existence inside WordPress using this code:

<?php
if (class_exists('ZipArchive')) {
    echo 'ZipArchive class exists';
}

What you can improve:

  1. Ask user for filename
  2. Append text to the end of the filename (e.g. file-v2.0.zip)
  3. Generate a custom folder list or read the actual directory structure on your server

Once in a while you feel the need to remove all your domain related cookies in your browser, or force the user to remove them.

Sometimes you set the wrong cookies or set a too high expiration date for them and you can’t rename them and you can’t politely ask the user to delete them. But you can redirect the user to a special file. This is where Cookie Cleaner comes into play.

Cookie Cleaner uses both PHP and JavaScript in order to completely remove any cookie traces from your browser. The browser needs to be restarted in order for the process to complete.

Grab the code from GitHub and test it yourself.

Feel free to contribute and share your cookie monster sessions.

.htaccess, BOM and PHP auto prepend adventures

PHP has a feature that allows you to prepend a file at every request. This prepend file is the equivalent of having it include()ed at the top of every single PHP script on your site. It’s is done through a directive that is set either in php.ini or .htaccess. The directive is called auto_prepend_file. It’s also evil if you don’t know about its existence.

In a .htaccess file, you can use this directive to define a specific file that will be auto-prepended, in a directory:

php_value auto_prepend_file "prepend.php"

You can also use this directive to deactivate auto-prepending in a directory or root:

php_value auto_prepend_file none

Note: Use the special value “none“, as explained in the documentation of auto_prepend_file:

The special value none disables auto-prepending.

Note: You can set php_values in .htaccess only where PHP is run as an Apache module.

When I first encountered this issue, I blamed my text editor for byte order marks (BOM). So, I found a neat script that searches for BOM files inside your root and recursively throughout the folders:

<?php
error_reporting(E_ALL);
// Detect BOM sequence in a folder recursively
define('STR_BOM', "\xEF\xBB\xBF");
$file = null;
$directory = getcwd();

$rit = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory), RecursiveIteratorIterator::CHILD_FIRST);
try {
    foreach ($rit as $file) {
        if ($file->isFile()) {
            $path_parts = pathinfo($file->getRealPath());

            if ('php' == $path_parts['extension']) {
                $object = new SplFileObject($file->getRealPath());

                if (false !== strpos($object->getCurrentLine(), STR_BOM)) {
                    print $file->getRealPath()."\n";
                }
            }
        }
    }
} catch (Exception $e) {
    die ('Exception caught: '. $e->getMessage());
}

There! Issue fixed!

Auto paragraph function with URL parsing and smiley detection

This function emulates WordPress wpautop() function for custom PHP scripts, parses URL addresses and adds smilies/emoticons.

  • First of all, the function changes 2 line breaks (HTML <br>) into a paragraph.
  • Second, the function changes the remaining single line breaks into HTML <br>, making a SHIFT+ENTER new line.
  • Third, the function parses text URLs and adds hyperlinks and detects preset smilies/emoticons from a function array.

PHP dynamic badge application/script

The following script is a helper library for dynamic badge creation using either static data or MySQL information. Static data may include Facebook information or other social stats. The application sends a link via email leading to the generated badge. All badges are stored online on the host site.

Get it, fork it or contribute!

You need PHP with GD library and a MySQL database.

PHP contact form with spam check and honeypot features

This is a PHP contact form with spam check and honeypot features. Note that there’s plenty of room for improvement, so feel free to fork it or add a pull request.

PHP multilanguage site using arrays

I recently had to turn a static localized CRM into a multilanguage one. The obvious solution, without relying on gettext functions, was to use PHP arrays.

So, here we go:

First of all we need a configuration setting inside our script’s config.php file. For the sake of our example, we’ll add a manual variable change:

// language settings
$lng = 'en';

Next, our header file, dubbed header.php (could be head.php or top.php in your script) will contain these lines:

include 'includes/config.php';
include 'languages/' . $lng . '.php';

You now, obviously, have to create a languages directory and create a new file called en.php. This file will hold our array of words and expressions:

/*
 * @Package: My multilanguage script
 * @Language: English (English)
*/

$lang = [];

$lang['REGISTER'] = 'Register';
$lang['USERNAME'] = 'Username';
$lang['PASSWORD'] = 'Password';
$lang['LOGIN'] = 'Log in';
$lang['LOGOUT'] = 'Log out';
$lang['DASHBOARD'] = 'Dashboard';

Notice how I tried to keep the array index name as close to translation as possible. For example, you’ll have the string “Separate tags with commas” as $lang['SEPARATE_TAGS_COMMAS']. It’s easier after a couple of months when you’ll make changes.

Also, try to keep consistent naming of your language files, such as fr.php, de.php, ru.php.

Now, call in you script <?php echo $lang['REGISTER'];?>. It will display “Register”, just as you translated it in your language file.

How to build a nice date feature, Facebook-style

One of the latest features I’ve added to my sports fishing portal was the nice date feature. Instead of showing PHP formatted date and time, I wanted to display time since the action took place, like “4 hours ago”, “2 days ago”, “1 minute ago” and so on.

<?php
function nicetime($date) {
    if (empty($date)) {
        return "ERROR: No date provided";
    }

    $periods = ["second", "minute", "hour", "day", "week", "month", "year", "decade"];
    $lengths = ["60", "60", "24", "7", "4.35", "12", "10"];
    $now = time();
    $unix_date = strtotime($date);

    // check validity of date
    if (empty($unix_date)) {
        return "ERROR: Invalid date";
    }

    // is it future date or past date
    if ($now > $unix_date) {
        $difference = $now - $unix_date;
        $tense = "ago";
    } else {
        $difference = $unix_date - $now;
        $tense = "from now";
    }
    for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths)-1; $j++) {
        $difference /= $lengths[$j];
    }
    $difference = round($difference);
    if ($difference != 1) {
        // $periods[$j] .= "s"; // plural for English language
        $periods = ["seconds", "minutes", "hours", "days", "weeks", "months", "years", "decades"]; // plural for international words
    }

    return "$difference $periods[$j] {$tense}";
}

Use <?php echo nicetime($date); ?> to call the function, where $date is a variable holding your desired date, properly formatted in PHP/MySQL. You can also pass a date directly using the standard format – date('Y-m-d G:i:s'), but, for obvious reasons, it’s better to prior assign it to a fixed variable.

PHP/MySQL date and time notes

I had to calculate past dates using 2 separate date and time columns. MySQL uses NOW() to output a date format such as "2011-03-10 14:30:00". My date column was DATE()"2011-03-10" and my time column was TIME()"14:30:00". So I had to combine them before making the comparison with NOW().

The MySQL function for this comparison is:

SELECT * FROM mytable WHERE DATE_ADD(mydate, INTERVAL mytime HOUR_SECOND) <= NOW()

This function will combine both columns into a TIMESTAMP value, formatted by DATE_ADD, ready to compare with NOW() format.

My documentation included more examples such as: SELECT TO_SECONDS('2011-03-10 14:30:00'); and SELECT TO_SECONDS(NOW());. Calculating the time difference in seconds is easy using these two MySQL functions, though time values such as 00:00:00 will fail.

This is how you format dates any way you want:

date('d/m/Y', strtotime($mydaterow))
date('H:i', strtotime($mytimerow))

This line will display the difference in minutes between two dates:

$toTime = strtotime('2010-10-10 13:48:00');
$fromTime = strtotime('2010-10-10 13:22:00');
echo round(abs($to_time - $from_time) / 60,2) . " minute";

Imagine you can replace the date strings with your own date/time column(s).

And another MySQL line that will select rows within a range of time:

SELECT * FROM table WHERE DATE_ADD(date, INTERVAL time HOUR_SECOND) BETWEEN '2010-01-01 16:30:00' AND '2010-01-02 17:00:00';

How to build a flat-file online visitor counter

If, for any given reason, you are not able to implement a MySQL counter, here is the easiest solution for creating a flat file online users counter. Create a file called online.php, make it writable, and insert the following code where you want the counter to appear:

<?php
$file_name = 'online.php';
$c_time = time();
$timeout = 60;
$time = $c_time - $timeout;
$ip = getenv("REMOTE_ADDR");

if (!file_exists($file_name)) {
    $fp = fopen($file_name, "w");
    fwrite($fp, " \n");
    fclose($fp);
}

$fp = fopen($file_name, "a");
$write = $ip . "||" . $c_time . "\n";
fwrite($fp, $write);
fclose($fp);

$file_array = file($file_name);
$online_array = [];
for ($x = 1; $x < count($file_array); $x++) {
    list($ip, $ip_time) = explode("||", $file_array[$x]);
    if ($ip_time >= $time) {
        array_push($online_array, $ip);
    }
}

$online = array_unique($online_array);
$online = count($online);
if ($online == "1") {
    print "$online user online";
} else {
    print "$online users online";
}
?>

That’s it!

If you are receiving warnings or even errors about your homepage being accessible both via example.com and example.com/index.php, there are two solutions:

Add to your header file, or to your index.php file the following snippet:

<?php
$cpage = $_SERVER['REQUEST_URI'];
if ((string) $cpage === '/' || (string) $cpage === '/index.php') {
    echo '<link rel="canonical" href="https://getbutterfly.com/">';
}

This way, Google (and the other search engines out there) will consider both your root domain and your specified search page as one canonical URL link.

The second solution is using .htaccess (if available) to redirect index.php to root, like this:

Options +FollowSymLinks
DirectoryIndex index.php

RewriteEngine On
RewriteBase /
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /index\.php\ HTTP/
RewriteRule ^index\.php$ https://www.example.com/ [R=301,L]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]

That’s it!

Harvest email addresses from a text file

You might have a text file with a lot of emails, extracted from a mail client or a CSV file created with some script.

Nonetheless, you want to extract all the valid email addresses (user@domain.ext) from the file. Pass this function a file (you can rename it to .php, or .txt, or .csv) and it will extract all addresses:

function extract_emails_from($string) {
    preg_match_all("/[._a-zA-Z0-9-]+@[._a-zA-Z0-9-]+/i", $string, $matches);

    return $matches[0];
}

// Usage
extract_emails_from('myfile.txt');

That’s it!

I’ve been trying to enhance one of my current themes with a dynamic header. The header was supposed to show different images with different links to section of the blog. This is what I ended up with. It works perfectly. Feel free to add as many arrays as you want and as many images inside the arrays as you want.

<?php
display_random_img($array) {
    $key = rand(0, count($array) - 1);
    $link_url = $array[$key]['url'];
    $alt_tag = $array[$key]['alt'];
    $random_img_url = $array[$key]['img_url'];
    list($img_width, $img_height) = getimagesize($random_img_url);

    return '';
}

// Edit the following values accordingly
$ads_array = [
    [
        'url' => 'https://www.google.com/',
        'alt' => 'Google',
        'img_url' => '/path/to/images/1.png',
    ],
    [
        'url' => 'https://www.yahoo.com/',
        'alt' => 'Yahoo!',
        'img_url' => '/path/to/images/2.png',
    ],
    [
        'url' => 'https://www.msn.com/',
        'alt' => 'MSN',
        'img_url' => '/path/to/images/3.png',
    ],
];

echo display_random_img($ads_array);

That’s it!

How to add a “Page loaded in X seconds” feature

Have you ever wondered how some web sites show a “loaded in x seconds” or “page created in x seconds” in the footer? Well, it’s a simple PHP script to add.

Add the following code snippet in the page head. For more accurate results, place it above the <!doctype> declaration:

<!-- put this at the top of the page -->
<?php
$mtime = microtime();
$mtime = explode(' ', $mtime);
$mtime = $mtime[1] + $mtime[0];
$starttime = $mtime

Then, add the following code snippet where you want your timer to show up (usually in the footer):

<!-- put this code at the bottom of the page -->
<?php
$mtime = microtime();
$mtime = explode(' ', $mtime);
$mtime = $mtime[1] + $mtime[0];
$endtime = $mtime;
$totaltime = ($endtime - $starttime);
echo 'Page loaded in ' . $totaltime . ' seconds.';

PHP’s microtime() function returns the current Unix timestamp with microseconds. And the explode() function returns an array of strings, each of which is a substring of string formed by splitting it on boundaries formed by the string separator. If limit is set, the returned array will contain a maximum of limit elements with the last element containing the rest of string.

42 tips for optimizing your PHP code

There are many tiny details that can improve and speed up your PHP script functioning. Here is a list of 42 of them.

  1. If a method can be static, declare it static. Speed improvement is by a factor of 4.
  2. echo is faster than print.
  3. Use echo‘s multiple parameters instead of string concatenation.
  4. Set the maximum value for your for loops before and not in the loop.
  5. Unset your variables to free memory, especially large arrays.
  6. Avoid magic like __get, __set, __autoload.
  7. require_once is expensive
  8. Use full paths in includes and requires, less time spent on resolving the OS paths.
  9. If you need to find out the time when the script started executing, $_SERVER['REQUEST_TIME'] is preferred to time().
  10. See if you can use strncasecmp, strpbrk and stripos instead of regex.
  11. str_replace is faster than preg_replace, but strtr is faster than str_replace by a factor of 4.
  12. If the function, such as string replacement function, accepts both arrays and single characters as arguments, and if your argument list is not too long, consider writing a few redundant replacement statements, passing one character at a time, instead of one line of code that accepts arrays as search and replace arguments.
  13. It’s better to use select statements than multi if/else if statements.
  14. Error suppression with @ is very slow.
  15. Turn on apache’s mod_deflate.
  16. Close your database connections when you’re done with them.
  17. $row['id'] is 7 times faster than $row[id].
  18. Error messages are expensive.
  19. Do not use functions inside of for loop, such as for ($x=0; $x < count($array); $x). The count() function gets called each time.
  20. Incrementing a local variable in a method is the fastest. Nearly the same as calling a local variable in a function.
  21. Incrementing a global variable is 2 times slow than a local variable.
  22. Incrementing an object property (e.g. $this->prop++) is 3 times slower than a local variable.
  23. Incrementing an undefined local variable is 9-10 times slower than a pre-initialized one.
  24. Just declaring a global variable without using it in a function also slows things down (by about the same amount as incrementing a local variable). PHP probably does a check to see if the global exists.
  25. Method invocation appears to be independent of the number of methods defined in the class because I added 10 more methods to the test class (before and after the test method) with no change in performance.
  26. Methods in derived classes run faster than ones defined in the base class.
  27. A function call with one parameter and an empty function body takes about the same time as doing 7-8 $localvar++ operations. A similar method call is of course about 15 $localvar++ operations.
  28. Surrounding your string by ' instead of " will make things interpret a little faster since PHP looks for variables inside "..." but not inside '...'. Of course you can only do this when you don’t need to have variables in the string.
  29. When echoing strings, it’s faster to separate them by comma instead of dot. Note: This only works with echo, which is a function that can take several strings as arguments.
  30. A PHP script will be served at least 2-10 times slower than a static HTML page by Apache. Try to use more static HTML pages and fewer scripts.
  31. Your PHP scripts are recompiled every time unless the scripts are cached. Install a PHP caching product to typically increase performance by 25-100% by removing compile times.
  32. Cache as much as possible. Use memcached – memcached is a high-performance memory object caching system intended to speed up dynamic web applications by alleviating database load. OP code caches are useful so that your script does not have to be compiled on every request.
  33. When working with strings and you need to check that the string is either of a certain length you’d understandably would want to use the strlen() function. This function is pretty quick since it’s operation does not perform any calculation but merely return the already known length of a string available in the zval structure (internal C struct used to store variables in PHP). However, because strlen() is a function, it is still somewhat slow because the function call requires several operations such as lowercase & hashtable lookup followed by the execution of said function. In some instance you can improve the speed of your code by using an isset() trick.Example:
    if (strlen($foo) < 5) { echo 'Foo is too short'; }

    vs.

    if (!isset ($foo{5})) { echo 'Foo is too short'; }

    Calling isset() happens to be faster than strlen() because, unlike strlen(), isset() is a language construct and not a function, meaning that it’s execution does not require function lookups and lowercase. This means you have virtually no overhead on top of the actual code that determines the string’s length.

  34. When incrementing or decrementing the value of the variable, $i++ happens to be a tad slower than ++$i. This is something PHP specific and does not apply to other languages, so don’t go modifying your C or Java code thinking it’ll suddenly become faster, it won’t. ++$i happens to be faster in PHP because instead of 4 opcodes used for $i++ you only need 3. Post incrementation actually causes the creation of a temporary var that is then incremented. While pre-incrementation increases the original value directly.
  35. Not everything has to be OOP, often it is too much overhead, each method and object call consumes a lot of memory.
  36. Do not implement every data structure as a class, arrays are useful, too.
  37. Don’t split methods too much, think, which code you will really re-use.
  38. You can always split the code of a method later, when needed.
  39. Make use of the countless predefined functions.
  40. If you have very time consuming functions in your code, consider writing them as C extensions.
  41. Profile your code. A profiler shows you which parts of your code consumes how much time. The XDebug debugger already contains a profiler.
  42. mod_gzip, which is available as an Apache module, compresses your data on the fly and can reduce the data to transfer up to 80%.

How to clean a PHP trojan

While updating one WordPress site this morning, I found a nice plugin, disguised as an .htaccess redirection towards an index_backup.php file both in the /plugins/ and the /themes/ directories.

The .htaccess looked similar to this one:

<IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{HTTP_REFERER} ^.*(google|ask|yahoo|yandex|ya|baidu|youtube|wikipedia|qq|excite|altavista|msn|netscape|aol|hotbot|goto|infoseek|mamma|alltheweb|lycos|search|metacrawler|bing|dogpile|facebook|twitter|blog|live|myspace|linkedin|flickr|filesearch|yell|openstat|metabot|gigablast|entireweb|amfibi|dmoz|yippy|walhello|webcrawler|jayde|findwhat|teoma|euroseek|wisenut|about|thunderstone|ixquick|terra|lookle|metaeureka|searchspot|slider|topseven|allthesites|libero|clickey|galaxy|brainysearch|pocketflier|verygoodsearch|bellnet|freenet|fireball|flemiro|suchbot|acoon|devaro|fastbot|netzindex|abacho|allesklar|suchnase|schnellsuche|sharelook|sucharchiv|suchbiene|suchmaschine|infospace|web|websuche|witch|wolong|oekoportal|freenet|arcor|alexana|tiscali|kataweb|voila|sfr|startpagina|kpnvandaag|ilse|wanadoo|telfort|hispavista|passagen|spray|eniro|telia|bluewin|sympatico|nlsearch|atsearch|klammeraffe|sharelook|suchknecht|ebay|abizdirectory|alltheuk|bhanvad|daffodil|click4choice|exalead|findelio|gasta|gimpsy|globalsearchdirectory|hotfrog|jobrapido|kingdomseek|mojeek|searchers|simplyhired|splut|thisisouryear|ukkey|uwe|friendsreunited|jaan|qp|rtl|apollo7|bricabrac|findloo|kobala|limier|express|bestireland|browseireland|finditireland|iesearch|kompass|startsiden|confex|finnalle|gulesider|keyweb|finnfirma|kvasir|savio|sol|startsiden|allpages|america|botw|chapu|claymont|clickz|clush|ehow|findhow|icq|westaustraliaonline)\.(.*) RewriteCond %{HTTP_USER_AGENT} ^.*(msie|opera) [NC] RewriteCond %{REQUEST_FILENAME} !/index_backup.php RewriteRule (.*) /index_backup.php?query=$1 [QSA,L] </IfModule>

Finding more occurrences of this trojan is accomplished by using one of my PHP functions. Create a new file in your WordPress root, add the code below and run it. It will show one or more files containing potentially malicious code (note that it will also show some WordPress files, which are not infected). Check each file to see how the code looks. Most of the detections will be inside the themes and plugins directories and, sometimes, they will be in the uploads directory.

The trojan has been classified as a PHP agent, PHP trojan or PHP Spambot by various antivirus packages.

Read more about this type of PHP trojan here (link in Russian).

How to set up a PHP redirection system

This is an advanced solution used to redirect old links using 301 (or 302, or 504 or whatever code you need) to new links using native PHP functions. If they don’t work, the redirection falls back to an HTML meta refresh. Enjoy!

<?php
/**
 * PHP Redirects
 *
 * @copyright Copyright (c) 2016 getbutterfly.com getButterfly (https://getbutterfly.com)
 * @license https://getbutterfly.com New BSD License
 */

/**
 * Checks and cleans a URL.
 *
 * A number of characters are removed from the URL.
 *
 * @param string $url The URL to be cleaned.
 * @return string The cleaned $url.
 */
function php_deep_replace($search, $subject) {
    $subject = (string) $subject;

    $count = 1;

    while($count) {
        $subject = str_replace($search, '', $subject, $count);
    }

    return $subject;
}

function php_esc_url($url) {
    $original_url = $url;

    if('' == $url)
        return $url;

    $url = str_replace( ' ', '%20', $url );
    $url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\[\]\\x80-\\xff]|i', '', $url);

    if('' === $url) {
        return $url;
    }

    if(0 !== stripos($url, 'mailto:')) {
        $strip = ['%0d', '%0a', '%0D', '%0A'];
        $url = php_deep_replace($strip, $url);
    }

    $url = str_replace(';//', '://', $url);
    /* If the URL doesn't appear to contain a scheme, we
     * presume it needs http:// prepended (unless a relative
     * link starting with /, # or ? or a php file).
     */
    if(strpos($url, ':') === false && !in_array($url[0], array('/', '#', '?')) && !preg_match('/^[a-z0-9-]+?\.php/i', $url))
        $url = 'http://' . $url;

    // Replace ampersands and single quotes only when displaying.
    $url = str_replace('&amp;', '&', $url);
    $url = str_replace("'", ''', $url);

    /**
     * Filter a string cleaned and escaped for output as a URL.
     *
     * @param string $_context
     */
    return $url;
}

function php_redirect($url) {
    // Sanitize URI
    $url = php_esc_url($url);

    // Get and append query string
    if(!empty($_SERVER['QUERY_STRING'])) {
        $url .= '?' . $_SERVER['QUERY_STRING'];
    }

    $content = sprintf('<!doctype html><html class="no-js"><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=%1$s"><title>Redirecting to %1$s...</title></head><body>Redirecting to <a href="%1$s">%1$s</a>...</body></html>', htmlspecialchars($url, ENT_QUOTES, 'UTF-8'));

    header('Location: ' . $url, true, 301);
    die($content);
}

/**
* Get client URI including arguments.
*/
$getUri = php_esc_url($_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);

if(strpos($getUri, 'example.com/old-uri-1') !== false) {
    php_redirect('https://www.example.com/new-uri-1');
} else if(strpos($getUri, 'example.com/old-uri-2') !== false) {
    php_redirect('https://www.example.com/new-uri-2');
} else if(strpos($getUri, 'example.com/old-uri-3') !== false) {
    php_redirect('https://www.example.com/new-uri-3');
}

And just go on with the loop. If you need to integrate it into your CMS, you can use a foreach() function and loop through your database.

Simple PHP/MySQL contact form tutorial

One client requested a simple contact form, without AJAX or other options. So, I used the PHP mail() function, wrapped in a simple HTML form.

First, we create the HTML form (contact.php):

<h1>Simple Contact Form</h1>
<form method="post" action="send_contact.php">
    <p><input name="subject" type="text" placeholder="Subject"</p>
    <p><textarea name="detail" cols="50" rows="4" placeholder="Detail"></textarea></p>
    <p><input name="name" type="text" placeholder="Name"></p>
    <p><input name="email" type="email" placeholder="Email"></p>
    <p><input type="submit" name="Submit" value="Submit"></p>
</form>

Next, we create the email sending engine (send_contact.php):

<?php
$name = filter_var((string) trim(addslashes($_POST['name'])), FILTER_SANITIZE_STRING);
$subject = filter_var((string) trim(addslashes($_POST['subject'])), FILTER_SANITIZE_STRING);
$message = filter_var((string) trim(addslashes($_POST['detail'])), FILTER_SANITIZE_STRING);
$mailFrom = filter_var((string) trim($_POST['email']), FILTER_VALIDATE_EMAIL);
$headers = "From: $name <$mailFrom>";

// Your email address
$to = 'someone@somewhere.com';
$sendAction = mail($to, $subject, $message, $headers);

// If message was successfully sent, display a message
if ($sendAction) {
    echo 'Thank you for your interest. We will get back to you as soon as possible.';
} else {
    echo 'Error: The email was not sent. Check your server.';
}
?>

That is all. Note that more code sanitization is required.

How to switch languages on a multilingual web site

1. First create a languages/ directory.

2. Create two language files, let’s say english.php and spanish.php and populate them with variables:

english.php

$txt['headtext'] = 'Welcome to our site';

spanish.php

$txt['headtext'] = 'Bienvenido a nuestro sitio';

3. Create an includes/ directory.

4. Create a configuration file:

config.php

<?php
$languages = array(
    'en' => 'english',
    'es' => 'spanish',
);
if (isset($_GET['lang']) && array_key_exists($_GET['lang'], $languages)) {
    include './languages/' . $languages[$_GET['lang']] . '.php';
} else {
    include './languages/english.php';
}
?>

5. Create a file called index.php and fill the next lines in:

include_once 'includes/config.php';

echo $txt['headtext'];
?>

<form action="" method="get">
    <select name="lang" size="1" onchange="this.form.submit();">
        <option value="">Select your language...</option>
        <option value="ro">Spanish</option>
        <option value="en">English</option>
    </select>
</form>

<a href="otherfile.php?lang=<?php echo $lang; ?>">otherfile.php</a>

6. That’s all. It’s really that simple. Just remember to refer to all links site wide as file.php?lang=<?php echo $lang; ?>:

<a href="otherfile.php?lang=<?php echo $lang; ?>">otherfile.php</a>

How to create a simple flat-file counter

1. First create a counter data file, we will call it counter.txt.

2. Create the counter script, as follows:

counter.php

<div>Visitors:
    <?php
    $file = 'counter.txt';</code></p>
    if (!file_exists($file)) {
        $handle = fopen($file, 'w');
        fwrite($handle, 0);
        fclose($handle);
    }

    $count = file_get_contents($file);
    $count++;

    if (is_writable($file)) {
        $handle = fopen($file, 'w+');
        fwrite($handle, $count);
        fclose($handle);
    } else {
        echo 'Unable to increment the counter!<br />';
    }

    echo number_format($count);
    ?>
</div>

3. That’s all. Remember to chmod your data file – counter.txt – to 777. You can now include this script in your page with the following line:

<?php include 'counter.php'; ?>

9 methods to read files in a folder using PHP

Method 1.

This is the simplest way to list files in a folder:

<?php
$dir = '.';
$dh = opendir($dir);
while (($file = readdir($dh)) !== false) {
    echo $file . '<br>';
}
closedir($dh);
?>

In order to add links to the files in the folder, we modify the code as below.

<?php
$dir = '.';
$dh = opendir($dir);
while (($file = readdir($dh)) !== false) {
    echo '<a href="' . $file . '">' . $file . '</a><br>';
}
closedir($dh);
?>

Method 2.

Another way to read the files would be:

<?php
$dir = '.';
$dh = opendir($dir);
$count = 0;
while (($file = readdir($dh)) !== false) {
    $count++;
    if (substr($file, 0, 1) !== '.') {
        echo $file . '<br>';
    }
}
?>

The line if (substr($file, 0, 1) !== '.') ensures that no .htaccess or .htpasswd file will be read.

Method 3.

The third method of reading and linking files in a directory would be:

<?php
$current = getcwd();
$directory = basename($current);

$dir = '.';
$dh = opendir($dir);
while (false !== ($filename = readdir($dh))) {
    $files[] = $filename;
}

// Let's get rid of the '.' and the '..' parent and ancestor files
$t = array_slice($files, 2);

// Let's get rid of the index.php file which does the reading
$f = array_search('index.php', $t);
unset($t[$f]);

print('<a href=".">Back</a><br>');
foreach($t as $key => $value) {
    echo('<a href="' . $value . '">' . $value . "</a><br>");
}
closedir($dh);
?>

Method 4.

The fourth method is the shortest of them all:

<?php
$dir = '.';
$files = scandir($dir);
//print_r($files);

$num = count($files);
for ($n=0; $n<$num; $n++) {
    echo $files[$n] . '<br>';
}
?>

I left print_r($files); in place but commented in order to show the directory array.

Method 5.

This method resembles phpMyAdmin’s way of listing files with an icon for folders and files. Further customization could show all file types, such as images, archives, php files, html files, css files, and so on. Notice how the ls_recursive() function can be nicely tucked away in a functions file.

<?php
function ls_recursive2($dir) {
    if (is_dir($dir)) {
        $files = scandir($dir);
        foreach ($files as $file) {
            $currentfile = $dir . '/' . $file;
            $last_dir = '';
            $count = substr_count($currentfile, '/');
            $minus_count = substr_count($_SERVER['DOCUMENT_ROOT'], '/');
            $count -= ($minus_count + 2);

            for($p = 0; $p<$count; $p++) {
                $last_dir .= "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
            }

            if (is_dir($currentfile)) {
                if ($file != '.' && $file != '..') {
                    $last_dir .= "<img src='images/folder.gif' alt='' width='16' height='16'>&nbsp;<a href=\"" . $currentfile . "\">" . substr($currentfile, strrpos($currentfile, '/')) . "</a><br>";
                    echo $last_dir;
                    ls_recursive2($currentfile);
                }
            } else {
                $last_dir .= "<img src='images/file.gif' alt='' width='16' height='16'>&nbsp;<a href=\"" . $currentfile . "\">" . substr($currentfile, strrpos($currentfile, '/')) . "</a><br>";
                echo $last_dir;
            }
        }
    }
}
ls_recursive2('.');
?>

Method 6.

Again a nice function for displaying both files in folders and files outside, a remake of the first method:

<?php
function listFolder($path) {
    $dir_handle = opendir($path) or die("Unable to open $path");

    $dirname = end(explode("/", $path));

    echo ("<li>$dirname\n");
    echo "<ul>\n";
        while (false !== ($file = readdir($dir_handle))) {
            if ($file!="." && $file!="..") {
                if (is_dir($path."/".$file)) {
                    listFolder($path."/".$file);
                } else {
                    echo "<li>$file</li>";
                }
            }
        }
    echo "</ul>\n";
    echo "</li>\n";

    closedir($dir_handle);
}
echo '<ul>';
    listFolder('.');
echo '</ul>';
?>

Method 7.

Unlike the methods above, this one counts files of a certain extension (in this case, gifs):

<?php
$d = opendir('.');
$count = 0;
while (($f = readdir($d)) !== false)
    if(ereg('.gif

Method 8.

This method shows how many files (not folders) are in a directory:

<?php
function num_files($directory) {
    return count(glob($directory."*.*"));
}
echo num_files('');
?>

Method 9.

The ninth method adds an interesting feature, file upload. While reading the files in a folder, the script has the capacity to upload a new file in the current directory.

<?php
if (isset($_POST['submit'])) {
    copy($_FILES['file']['tmp_name'], $_FILES['file']['name']);
}

$dir=opendir(".");
$files=array();
while (($file=readdir($dir)) !== false) {
    if($file != "." and $file != ".." and $file != "index.php") {
        array_push($files, $file);
    }
}
closedir($dir);
sort($files);

foreach ($files as $file)
    print "<a href='$file'>$file</a><br />";
?>
<form action="" enctype="multipart/form-data" method="POST">
    Add a new file: <input type="file" name="file">
    <input type="submit" name="submit" value="Upload file">
</form>

That’s all!

Notice that all examples use '.', which means the current directory. By using '/' these functions would scan the parent directory.

How to set up an affiliate system

This is a basic affiliate system and it works by setting up a cookie and a database entry, and updating the record if a successful payment has gone through.

First of all, we need to detect if a link with an affiliate parameter has been clicked. So, we place the following code in the header of our template:

<?php
/**
* Custom affiliate conversion tracking code
*/
include WEBSITE_PATH . '/Affiliate.php';

if(isset($_GET['affid'])) {
    // Get affiliate ID
    $affid = filter_var(trim($_GET['affid']), FILTER_SANITIZE_NUMBER_INT);

    affiliateGetVisit($affid);
}

if($url === '/payment-page') {
    if(isset($_COOKIE['AffiliateURL'])) {
        affiliateConvertVisit($_COOKIE['AffiliateID'], $_COOKIE['AffiliateURL'], $conversionValue, $transactionId, $transactionNumber);
    }
}

Note that you will need to make some changes in order to accommodate the code above, namely the $url variable, which holds the current page URL and the $conversionValue, $transactionId and $transactionNumber variables, which hold details related to your own funnel system.

Next, we set up our Affiliate.php file. This file holds lots of helper functions (device detection and database connection) and our main database insert and update routines.

<?php
/**
* Library for affiliate processing
*
* @copyright getButterfly.com 2016
*
*
* Add a parameter to any URL to test custom affiliate tracking
* $url .= (parse_url($url, PHP_URL_QUERY) ? '&' : '?') . 'affid=1';
*
*/

/**
* Get affiliate device (basic).
*
* @return string
*/
function clickIsMobile() {
    return preg_match("/(android|webos|avantgo|iphone|ipad|ipod|blackberry|iemobile|bolt|bost|cricket|docomo|fone|hiptop|mini|opera mini|kitkat|mobi|palm|phone|pie|tablet|up\.browser|up\.link|webos|wos)/i", $_SERVER["HTTP_USER_AGENT"]);
}

/**
* Get affiliate device (extended).
*
* @return string
*/
function clickGetDevice() {
    $userAgent = $_SERVER["HTTP_USER_AGENT"];
    $devicesTypes = array(
        "computer" => array("edge", "msie 11", "msie 10", "msie 9", "msie 8", "windows.*firefox", "windows.*chrome", "x11.*chrome", "x11.*firefox", "macintosh.*chrome", "macintosh.*firefox", "opera"),
        "tablet" => array("tablet", "android", "ipad", "tablet.*firefox"),
        "mobile" => array("mobile ", "android.*mobile", "iphone", "ipod", "opera mobi", "opera mini"),
        "bot" => array("googlebot", "mediapartners-google", "adsbot-google", "duckduckbot", "msnbot", "bingbot", "ask", "facebook", "yahoo", "addthis")
    );
    foreach($devicesTypes as $deviceType => $devices) {
        foreach($devices as $device) {
            if(preg_match("/" . $device . "/i", $userAgent)) {
                $deviceName = $deviceType;
            }
        }
    }

    return ucfirst($deviceName);
}

/**
* Get database connection.
*
* @return object
*/
function clickPdo() {
    $db = new PDO('mysql:host=localhost;dbname=database;charset=utf8', 'user', 'password');
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

    return $db;
}

/**
* Store affiliate visit details in log table.
*
* @param int $affiliateId
* @param string $target
* @return bool
*/
function affiliateGetVisit($affiliateId) {
    $clickUrl = strtok("https://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]", '?');
    $affiliateClickUrl = $affiliateClickUrl . '?affid=' . $affiliateId;

    setcookie("AffiliateID", $affiliateId, strtotime('+30 days')); // 30 days
    setcookie("AffiliateURL", $clickUrl, strtotime('+30 days')); // 30 days

    $clickIp = $_SERVER['REMOTE_ADDR'];
    $clickReferrer = $_SERVER['HTTP_REFERER'];
    $clickTimestamp = date('Y-m-d H:i:s');

    if(clickIsMobile()) {
        $clickDevice = 'mobile';
    } else {
        $clickDevice = 'desktop';
    }

    $clickGetDevice = clickGetDevice();

    $clickConversion = 0;
    $clickTransactionId = 0;

    $stmt = clickPdo()->prepare("INSERT INTO affiliate_log (
        affiliateId,
        clickReferrer,
        clickTimestamp,
        clickIp,
        clickDevice,
        clickUrl,
        clickConversion,
        clickTransactionId
    ) VALUES (
        '$affiliateId',
        '$clickReferrer',
        '$clickTimestamp',
        '$clickIp',
        '$clickDevice ($clickGetDevice)',
        '$clickUrl',
        '$clickConversion',
        '$clickTransactionId'
    )");
    $stmt->execute();
}

/**
* Update affiliate visit details in log table and remove all cookies. Sugar is bad.
*
* @param int $affiliateId
* @param string $affiliateUrl
* @param int $conversionValue
* @param int $transactionId
* @param string $transactionNumber
* @return bool
*/
function affiliateConvertVisit($affiliateId, $affiliateUrl, $conversionValue, $transactionId, $transactionNumber) {
    $holidayUrl = strtok(urldecode($affiliateUrl), '?');
    $clickIp = $_SERVER['REMOTE_ADDR'];

    $stmt = clickPdo()->prepare("UPDATE affiliate_log SET
        clickConversion = 1,
        clickTransactionId = '$transactionId',
        clickTransactionNo = '$transactionNumber',
        clickTransactionValue = '$conversionValue'
    WHERE
        clickUrl = '$holidayUrl'
        AND affiliateId = $affiliateId
        AND clickIp = '$clickIp'
    LIMIT 1");
    $stmt->execute();

    // Cookie has done its job, remove it
    setcookie('AffiliateID', '', time() - 3600, '/');
    unset($_COOKIE['AffiliateID']);

    setcookie('AffiliateURL', '', time() - 3600, '/');
    unset($_COOKIE['AffiliateURL']);
}
?>

This is my own approach and implementation. It’s basic and it needs more work, but it’s the first step if you want to code your own affiliate tracking system.

, $f)) ++$count; closedir($d); print "Files=$count"; ?>

Method 8.

This method shows how many files (not folders) are in a directory:

 

Method 9.

The ninth method adds an interesting feature, file upload. While reading the files in a folder, the script has the capacity to upload a new file in the current directory.

 
 

That’s all!

Notice that all examples use '.', which means the current directory. By using '/' these functions would scan the parent directory.

How to set up an affiliate system

This is a basic affiliate system and it works by setting up a cookie and a database entry, and updating the record if a successful payment has gone through.

First of all, we need to detect if a link with an affiliate parameter has been clicked. So, we place the following code in the header of our template:

 

Note that you will need to make some changes in order to accommodate the code above, namely the $url variable, which holds the current page URL and the $conversionValue, $transactionId and $transactionNumber variables, which hold details related to your own funnel system.

Next, we set up our Affiliate.php file. This file holds lots of helper functions (device detection and database connection) and our main database insert and update routines.

 

This is my own approach and implementation. It’s basic and it needs more work, but it’s the first step if you want to code your own affiliate tracking system.

Related posts