Location: PHPKode > projects > ETraxis > src/engine/utility.php
<?php

//------------------------------------------------------------------------------
//
//  eTraxis - Records tracking web-based system
//  Copyright (C) 2004-2010  Artem Rodygin
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//------------------------------------------------------------------------------

/**
 * Utility functions
 *
 * This module contains several wide-purpose utility functions.
 *
 * @package Engine
 */

/**#@+
 * Dependency.
 */
require_once('../engine/debug.php');
require_once('../engine/smtp.php');
/**#@-*/

//------------------------------------------------------------------------------
//  Definitions.
//------------------------------------------------------------------------------

/**
 * Maximum integer value.
 */
define('MAXINT', 0x7FFFFFFF);

/**#@+
 * Number of seconds.
 */
define('SECS_IN_DAY',  86400);   // 60 * 60 * 24
define('SECS_IN_WEEK', 604800);  // 60 * 60 * 24 * 7
/**#@-*/

/**
 * Allowed CSV-delimiters.
 */
define('CSV_DELIMITERS', '!#$%&\'()*+,-./:;<=>?@[\]^_`{|}~');

//------------------------------------------------------------------------------
//  Functions.
//------------------------------------------------------------------------------

/**
 * Unicode (UTF-8) analogue of standard {@link http://www.php.net/strlen strlen} PHP function.
 *
 * Returns the length of the given string.
 *
 * @param string $str The UTF-8 encoded string being measured for length.
 * @return int The length (amount of UTF-8 characters) of the string on success, and 0 if the string is empty.
 */
function ustrlen ($str)
{
    return mb_strlen($str, 'UTF-8');
}

/**
 * Unicode (UTF-8) analogue of standard {@link http://www.php.net/strpos strpos} PHP function.
 *
 * Find position of first occurrence of a case-sensitive UTF-8 encoded string.
 * Returns the numeric position (offset in amount of UTF-8 characters) of the first occurrence of <i>needle</i> in the <i>haystack</i> string.
 *
 * @param string $haystack The UTF-8 encoded string being searched in.
 * @param string $needle The UTF-8 encoded string being searched for.
 * @param int $offset The optional <i>offset</i> parameter allows you to specify which character in <i>haystack</i> to start searching.
 * The position returned is still relative to the beginning of <i>haystack</i>.
 * @return int Returns the position as an integer. If <i>needle</i> is not found, the function will return boolean FALSE.
 */
function ustrpos ($haystack, $needle, $offset = 0)
{
    return mb_strpos($haystack, $needle, $offset, 'UTF-8');
}

/**
 * Unicode (UTF-8) analogue of standard {@link http://www.php.net/stripos stripos} PHP function.
 *
 * Find position of first occurrence of a case-insensitive UTF-8 encoded string.
 * Returns the numeric position (offset in amount of UTF-8 characters) of the first occurrence of <i>needle</i> in the <i>haystack</i> string.
 *
 * @param string $haystack The UTF-8 encoded string being searched in.
 * @param string $needle The UTF-8 encoded string being searched for.
 * @param int $offset The optional <i>offset</i> parameter allows you to specify which character in <i>haystack</i> to start searching.
 * The position returned is still relative to the beginning of <i>haystack</i>.
 * @return int Returns the position as an integer. If <i>needle</i> is not found, the function will return boolean FALSE.
 */
function ustripos ($haystack, $needle, $offset = 0)
{
    $haystack = mb_strtolower($haystack, 'UTF-8');
    $needle   = mb_strtolower($needle,   'UTF-8');

    return mb_strpos($haystack, $needle, $offset, 'UTF-8');
}

/**
 * Unicode (UTF-8) analogue of standard {@link http://www.php.net/strrpos strrpos} PHP function.
 *
 * Find position of last occurrence of a case-sensitive UTF-8 encoded string.
 * Returns the numeric position (offset in amount of UTF-8 characters) of the last occurrence of <i>needle</i> in the <i>haystack</i> string.
 *
 * @param string $haystack The UTF-8 encoded string being searched in.
 * @param string $needle The UTF-8 encoded string being searched for.
 * @return int Returns the position as an integer. If <i>needle</i> is not found, the function will return boolean FALSE.
 */
function ustrrpos ($haystack, $needle)
{
    return mb_strrpos($haystack, $needle, 'UTF-8');
}

/**
 * Unicode (UTF-8) analogue of standard {@link http://www.php.net/strtolower strtolower} PHP function.
 *
 * Make a string lowercase.
 *
 * @param string $str The UTF-8 encoded string to be lowercased.
 * @return string Specified string with all alphabetic characters converted to lowercase.
 */
function ustrtolower ($str)
{
    return mb_strtolower($str, 'UTF-8');
}

/**
 * Unicode (UTF-8) analogue of standard {@link http://www.php.net/substr substr} PHP function.
 *
 * Returns the portion of string specified by the <i>start</i> and <i>length</i> parameters.
 *
 * @param string $str The UTF-8 encoded string.
 * @param int $start Start of portion to be returned. Position is counted in amount of UTF-8 characters from the beginning of <i>str</i>.
 * First character's position is 0. Second character position is 1, and so on.
 * @param int $length If <i>length</i> is given, the string returned will contain at most <i>length</i> characters beginning from <i>start</i> (depending on the length of string).
 * If <i>length</i> is omitted, the rest of string from <i>start</i> will be returned.
 * @return string The extracted UTF-8 encoded part of input string.
 */
function usubstr ($str, $start, $length = NULL)
{
    return mb_substr($str, $start, (is_null($length) ? mb_strlen($str, 'UTF-8') : $length), 'UTF-8');
}

/**
 * Unicode (UTF-8) analogue of standard {@link http://www.php.net/str-replace str_replace} PHP function.
 *
 * Replace all occurrences of the search string with the replacement string.
 *
 * @param string $search The UTF-8 encoded string being searched for.
 * @param string $replace The UTF-8 encoded string being replaced with.
 * @param string $subject The UTF-8 encoded string being searched in.
 * @return string The UTF-8 encoded string with the replaced values.
 */
function ustr_replace ($search, $replace, $subject)
{
    $from = 0;
    $len  = ustrlen($search);

    while (TRUE)
    {
        $from = ustripos($subject, $search, $from);

        if ($from === FALSE)
        {
            break;
        }

        $subject = usubstr($subject, 0, $from) . $replace . usubstr($subject, $from + $len);
        $from += ustrlen($replace);
    }

    return $subject;
}

/**
 * Trims UTF-8 encoded string and then cuts it to specified length.
 *
 * @param string $str The UTF-8 encoded string being cut.
 * @param int $maxlen New length of the string (amount of UTF-8 characters).
 * @param bool $trim Whether to trim string before cutting it.
 * @return string Cut string.
 */
function ustrcut ($str, $maxlen, $trim = TRUE)
{
    if ($trim)
    {
        $str = trim($str);
    }

    return mb_strcut($str, 0, $maxlen, 'UTF-8');
}

/**
 * The function accepts variable number of arguments and replaces each "%i" (where <i>i</i> is
 * a natural number) substring of input string with related argument.
 *
 * Passed arguments can be any type of; in case of string they should be UTF-8 encoded.
 *
 * @param string $str The UTF-8 encoded string being processed.
 * @param mixed Value, which each "%1" substring will be replaced with.
 * @param mixed Value, which each "%2" substring will be replaced with.
 * @param mixed ... (and so on)
 * @return string Processed string.
 *
 * <br/>Example:<br/>
 * <code>
 * ustrprocess("Name: %1\nSex: %3\nAge: %2", "Artem", 30, "male");
 * </code>
 * <br/>will output<br/>
 * <pre>
 * Name: Artem
 * Sex: male
 * Age: 30
 * </pre>
 */
function ustrprocess ($str)
{
    for ($i = func_num_args(); $i > 1; $i--)
    {
        $search  = '%' . ($i - 1);
        $replace = func_get_arg($i - 1);
        $str     = ustr_replace($search, $replace, $str);
    }

    return $str;
}

/**
 * Converts UTF-8 encoded string to integer (natural) value.
 *
 * If resulted integer value is less then specified <i>min</i> value, the <i>min</i> value will be returned.
 * If resulted integer value is greater then specified <i>max</i> value, the <i>max</i> value will be returned.
 *
 * @param string $str The UTF-8 encoded string being converted.
 * @param int $min Minimum allowed result value.
 * @param int $max Maximum allowed result value.
 * @return int Natural value of range from <i>min</i> to <i>max</i>.
 */
function ustr2int ($str, $min = 0, $max = MAXINT)
{
    $res = (ustrlen($str) == 0 ? $min : intval($str));

    if ($res < $min)
    {
        $res = $min;
    }
    elseif ($res > $max)
    {
        $res = $max;
    }

    return $res;
}

/**
 * Converts specified amount of minutes to its string representation in format "hh:mm".
 *
 * @param int $time Amount of minutes.
 * @return string String representation (e.g. for 127 it will be "2:07").
 */
function time2ustr ($time)
{
    return intval(floor($time / 60)) . ':' . str_pad($time % 60, 2, '0', STR_PAD_LEFT);
}

/**
 * Strips HTML-tags from UTF-8 encoded string.
 *
 * @param string $str The UTF-8 encoded string.
 * @return string HTML-safe UTF-8 encoded string.
 */
function ustr2html ($str)
{
    if (is_null($str)) return NULL;

    $str = mb_ereg_replace("([\x00-\x08]|[\x0B-\x0C]|[\x0E-\x1F])", NULL, $str);
    return @htmlspecialchars($str, ENT_COMPAT, 'UTF-8');
}

/**
 * Strips quotes from UTF-8 encoded string.
 *
 * @param string $str The UTF-8 encoded string.
 * @return string JavaScript-safe UTF-8 encoded string.
 */
function ustr2js ($str)
{
    return ustr_replace('"', '\"', $str);
}

/**
 * Strips apostrophes from UTF-8 encoded string.
 *
 * @param string $str The UTF-8 encoded string.
 * @return string SQL-safe UTF-8 encoded string.
 */
function ustr2sql ($str)
{
    if ((DATABASE_DRIVER == 1) ||
        (DATABASE_DRIVER == 4))
    {
        $str = ustr_replace('\\', '\\\\', $str);
    }

    $str = ustr_replace('\'', '\'\'', $str);

    return $str;
}

/**
 * Convert UTF-8 encoded string to CSV format.
 *
 * @param string $str The UTF-8 encoded string.
 * @param string $delimiter CSV delimiter.
 * @param string $enclosure CSV enclosure.
 * @return string CSV string (UTF-8 encoded).
 */
function ustr2csv ($str, $delimiter = ',', $enclosure = '"')
{
    $str = ustr_replace($enclosure, $enclosure . $enclosure, $str);

    if (ustrpos($str, $enclosure) !== FALSE ||
        ustrpos($str, $delimiter) !== FALSE ||
        ustrpos($str, "\n")       !== FALSE)
    {
        $str = $enclosure . $str . $enclosure;
    }

    return $str;
}

/**
 * Parse UTF-8 encoded CSV string into an array.
 *
 * @param string $str The UTF-8 encoded CSV string.
 * @param string $delimiter CSV delimiter.
 * @param string $enclosure CSV enclosure.
 * @return array Indexed array containing the fields read (UTF-8 encoded), or NULL on error.
 */
function ustr_getcsv ($str, $delimiter = ',', $enclosure = '"')
{
    $csv = mb_split($delimiter, $str);

    for ($i = 0; $i < count($csv); $i++)
    {
        if (usubstr($csv[$i], 0, ustrlen($enclosure)) == $enclosure)
        {
            while ($i < count($csv) &&
                   usubstr($csv[$i], ustrlen($csv[$i]) - ustrlen($enclosure)) != $enclosure)
            {
                $csv[$i] .= $delimiter . $csv[$i + 1];
                array_splice($csv, $i + 1, 1);
            }

            $csv[$i] = usubstr($csv[$i], ustrlen($enclosure), ustrlen($csv[$i]) - ustrlen($enclosure) * 2);
        }
    }

    return $csv;
}

/**
 * Converts boolean value to integer for use in SQL queries.
 *
 * @param bool $value Boolean value.
 * @return int '1' on TRUE, or '0' on FALSE.
 */
function bool2sql ($value)
{
    return ($value ? 1 : 0);
}

/**
 * Returns value of user HTML-form request, if it exists; otherwise returns specified default value.
 *
 * @param string $request Name of user HTML-form request.
 * @param mixed $value Default value.
 * @return mixed User HTML-form request, or default value if specified request cannot be found.
 */
function try_request ($request, $value = NULL)
{
    global $_REQUEST;
    return (isset($_REQUEST[$request]) ? $_REQUEST[$request] : $value);
}

/**
 * Exchanges values of two variables.
 *
 * @param mixed &$value1 First variable.
 * @param mixed &$value2 Second variable.
 */
function swap (&$value1, &$value2)
{
    $temp   = $value1;
    $value1 = $value2;
    $value2 = $temp;
}

/**
 * Finds whether the given UTF-8 encoded string contains valid integer value.
 *
 * @param string $str The UTF-8 encoded string being evaluated.
 * @return bool TRUE if <i>str</i> contains valid integer value, FALSE otherwise.
 */
function is_intvalue ($str)
{
    mb_regex_encoding('UTF-8');
    return mb_eregi('^(\+|\-)*([0-9])+$', $str);
}

/**
 * Finds whether the given UTF-8 encoded string contains valid login
 * (only latin characters, digits, and underline are allowed).
 *
 * @param string $str The UTF-8 encoded string being evaluated.
 * @return bool TRUE if <i>str</i> contains valid login, FALSE otherwise.
 */
function is_username ($str)
{
    mb_regex_encoding('UTF-8');
    return mb_eregi('^([_0-9a-z\.\-])+$', $str);
}

/**
 * Finds whether the given UTF-8 encoded string contains valid email address.
 *
 * @param string $str The UTF-8 encoded string being evaluated.
 * @return bool TRUE if <i>str</i> contains valid email address, FALSE otherwise.
 */
function is_email ($str)
{
    mb_regex_encoding('UTF-8');

    $atom   = '[-a-z0-9!#$%&\'*+/=?^_`{|}~]';      // allowed characters for part before "at" character
    $domain = '([a-z0-9]([-a-z0-9]*[a-z0-9]+)?)';  // allowed characters for part after "at" character

    return mb_eregi("^{$atom}+(\\.{$atom}+)*@({$domain}{1,63}\\.)+{$domain}{2,63}$", $str);
}

/**
 * Sends email notification.
 *
 * @param string $sender Name of sender.
 * @param string $from Email address of sender.
 * @param string $to Email addresses of recipients (comma-separated).
 * @param string $subject Subject of the notification.
 * @param string $message Body of the notification.
 * @param int $attachment_id ID of attachment if it should be included in email, NULL otherwise.
 * @param string $attachment_name Name of attachment if it should be included in email, NULL otherwise.
 * @param string $attachment_type MIME type of attachment if it should be included in email, NULL otherwise.
 * @param int $attachment_size Size of attachment if it should be included in email, NULL otherwise.
 * @return bool TRUE if the mail was successfully accepted for delivery, FALSE otherwise.
 */
function sendmail ($sender, $from, $to, $subject, $message, $attachment_id = NULL, $attachment_name = NULL, $attachment_type = NULL, $attachment_size = NULL)
{
    debug_write_log(DEBUG_TRACE, '[sendmail]');

    if (strtolower(substr(PHP_OS, 0, 3)) == 'win')
    {
        $eol = "\r\n";

        // When PHP is talking to a SMTP server directly, if a full stop is found on the start of
        // a line, it is removed. To counter-act this, replace these occurrences with a double dot.
        $message = ustr_replace("\n.", "\n..", $message);
    }
    elseif (strtolower(substr(PHP_OS, 0, 3)) == 'mac')
    {
        $eol = "\r";
    }
    else
    {
        $eol = "\n";
    }

    $sender        = '=?utf-8?b?' . base64_encode($sender) . '?=';
    $is_attachment = ($attachment_size <= EMAIL_ATTACHMENTS_MAXSIZE * 1024 && !is_null($attachment_id));
    $boundary      = 'eTraxis-boundary:' . md5(uniqid(time()));

    $headers = implode($eol, array('Date: ' . date('r'),
                                   'From: ' . $sender . ' <' . (EMAIL_NOTIFICATIONS_ENABLED == SMTP_CLIENT_BUILDIN ? SMTP_MAILFROM : $from) . '>',
                                   'Reply-To: ' . $from,
                                   'Return-Path: ' . $from,
                                   'Message-ID: <' . md5(uniqid(time())) . '@' . $_SERVER['SERVER_NAME'] . '>',
                                   'X-Priority: 3',
                                   'X-Mailer: eTraxis ' . VERSION,
                                   'MIME-Version: 1.0',
                                   ($is_attachment ? 'Content-Type: multipart/mixed; boundary="' . $boundary . '"'
                                                   : 'Content-Type: text/html; charset="utf-8"')
                                   ));

    if ($is_attachment)
    {
        $message = implode($eol, array('This is a multi-part message in MIME format.',
                                       NULL,
                                       '--' . $boundary,
                                       'Content-Type: text/html; charset="utf-8"',
                                       NULL,
                                       $message,
                                       NULL,
                                       '--' . $boundary,
                                       'Content-Type: ' . $attachment_type . '; name="' . $attachment_name . '"',
                                       'Content-Transfer-Encoding: base64',
                                       'Content-Disposition: attachment; filename="=?utf-8?b?' . base64_encode($attachment_name) . '?="',
                                       NULL,
                                       chunk_split(base64_encode(gzfile_get_contents(ATTACHMENTS_PATH . $attachment_id, $attachment_size))),
                                       NULL,
                                       '--' . $boundary . '--'));
    }

    debug_write_log(DEBUG_DUMP, '[sendmail] $to = ' . $to);
    debug_write_log(DEBUG_DUMP, '[sendmail] $subject = ' . $subject);
    debug_write_log(DEBUG_DUMP, "[sendmail] \$headers =\n{$headers}");
    debug_write_log(DEBUG_DUMP, "[sendmail] \$message =\n{$message}");

    $subject = '=?utf-8?b?' . base64_encode($subject) . '?=';

    switch (EMAIL_NOTIFICATIONS_ENABLED)
    {
        case SMTP_CLIENT_PHP:
            return @mail($to, $subject, $message, $headers);
        case SMTP_CLIENT_BUILDIN:
            return smtp_send_mail($to, $subject, $message, $headers);
        default:
            debug_write_log(DEBUG_WARNING, '[sendmail] Email notifications are disabled.');
    }

    return FALSE;
}

/**
 * Round specified timestamp down to midnight.
 *
 * @param int $timestamp Unix timestamp (see {@link http://en.wikipedia.org/wiki/Unix_time})
 * @return int Unix timestamp (see {@link http://en.wikipedia.org/wiki/Unix_time}) for midnight of the same date.
 */
function date_floor ($timestamp)
{
    $date = getdate($timestamp);
    return mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year']);
}

/**
 * Returns timestamp, shifted from specified for particular number of days.
 * Negative offset shifts to past, positive - to future.
 *
 * @param int $timestamp Unix timestamp (see {@link http://en.wikipedia.org/wiki/Unix_time}) to be shifted from
 * @param int $offset Number of days to be shifted for
 * @return int Unix timestamp (see {@link http://en.wikipedia.org/wiki/Unix_time}) for resulting shifted date.
 */
function date_offset ($timestamp, $offset)
{
    $now      = date_floor($timestamp);
    $min_date = date_floor(0);
    $max_date = date_floor(MAXINT);

    // Determine maximum allowed shifts to both past and future to avoid byte overflow error
    // (any timestamp must be in range from 0x00000000 to 0x7FFFFFFF)
    $max_backward = round(($min_date - $now) / SECS_IN_DAY);
    $max_forward  = round(($max_date - $now) / SECS_IN_DAY);

    // If specified offset looks to far in the past, adjust it to the maximum allowed offset.
    if ($offset < $max_backward)
    {
        $offset = $max_backward;
    }

    // If specified offset looks to far in the future, adjust it to the maximum allowed offset.
    if ($offset > $max_forward)
    {
        $offset = $max_forward;
    }

    return strtotime("{$offset} days", $timestamp);
}

/**
 * Generates Basic HTTP Authentication realm, based on client browser.
 *
 * @return string Generated realm.
 */
function get_http_auth_realm ()
{
    $realm = 'eTraxis';

    if (stripos($_SERVER['HTTP_USER_AGENT'], 'Konqueror') !== FALSE ||
        stripos($_SERVER['HTTP_USER_AGENT'], 'Opera')     !== FALSE ||
        stripos($_SERVER['HTTP_USER_AGENT'], 'Safari')    !== FALSE)
    {
        $realm .= '-' . time();
    }

    return $realm;
}

/**
 * Gzips specified file, keeping same name.
 *
 * @param string $srcName Name of the input file.
 */
function compressfile ($srcName)
{
    if (extension_loaded('zlib'))
    {
        $dstName = "{$srcName}.gz";

        $fp = fopen($srcName, 'rb');
        $data = fread($fp, filesize($srcName));
        fclose($fp);

        $zp = gzopen($dstName, 'wb6');
        gzwrite($zp, $data);
        gzclose($zp);

        unlink($srcName);
        rename($dstName, $srcName);
    }
}

/**
 * Equivalent of standard {@link http://www.php.net/file-get-contents file_get_contents} function for a gzipped attachment.
 *
 * @param string $srcName Name of the gzipped file.
 * @param int $uncompressedSize Uncompressed size of the input file.
 * @return string Uncompressed file contents.
 */
function gzfile_get_contents ($srcName, $uncompressedSize)
{
    if (extension_loaded('zlib'))
    {
        $fp = gzopen($srcName, 'rb');
        $data = gzread($fp, $uncompressedSize);
        gzclose($fp);

        return $data;
    }
    else
    {
        return file_get_contents($srcName);
    }
}

?>
Return current item: ETraxis