Category: "PHP"

Free .PNG and .JPG Banner Builder

Allows you to upload up to 10 images.

Assembles them into a banner 100px high. Adjusts height, retains aspect ratio.

Produces both a .PNG and .JPG.

Generic Class Wrapper - 2

The code in the previous post was converted to use Zend Framework’s Db component for database access. One of the first things I noticed was that the code got smaller.

There are many ways to use Zend_Db, for this implementation, this approach fits well. Note the use of Zend_Config_Ini as well.

object.class.php changed, and item.class.php was converted to store the properties in an object, rather than an array. classes/db.class.php was deleted.

classes/object.class.php

<?php

require_once 'Zend/Config/Ini.php';
require_once 'Zend/Db.php';

Class Object
{
        private $db;

        public function __construct()
        {
                $Config = new Zend_Config_Ini('ini.php','database');
                $this->db = Zend_Db::factory($Config->database);
        }

        protected function load($sTable,$sIdName,$sId)
        {
                $this->db->setFetchMode(Zend_Db::FETCH_OBJ);
                $oResult = $this->db->fetchRow('SELECT * FROM `'.$sTable.'` WHERE `'.$sIdName.'` = ?', $this->db->quote($sId));
                return $oResult;
        }

        protected function save($sTable,$sIdName,$oArgs)
        {
                $sId=$oArgs->{$sIdName};
                unset($oArgs->{$sIdName});
                $aArgs=get_object_vars($oArgs);
                foreach ($aArgs as $k => $v)
                        $aArgs[$k]=$this->db->quote($v);
                if ($this->load($sTable,$sIdName,$sId))
                        $this->db->update($sTable,$aArgs,$sIdName.'='.$this->db->quote($sId));
                else
                        $this->db->insert($sTable,$aArgs);
                return $this->load($sTable,$sIdName,$sId);
        }

        protected function remove($sTable,$sIdName,$sId)
        {
                return $this->db->delete($sTable,$sIdName.'='.$db->quote($sId));
        }

        public function __destruct()
        {
                $this->db->closeConnection();
        }
}
?>

ini.php

<?php /*
[database]
database.adapter=pdo_mysql
database.params.host=localhost
database.params.dbname=dbname
database.params.username=username
database.params.password=password
*/ ?>

Generic Class Wrapper

The objective of this wrapper is to provide a streamlined interface to data stored in a database.

Notes

  • Object properties are stored in an array. This makes managing them must easier. Upon instantiation, the array is loaded with empty strings.
  • Magic methods are used to set and return properties.
  • The properties have a one-to-one, exact mapping to the underlying database.
  • A generic object exists beneath the specific object. It accepts a table name, identifier column name, and an array of values.

item.class.php

<?php
/*mysql> show full columns from items;
+---------+------------------+-----------------+------+-----+---------+----------------+---------------------------------+---------+
| Field   | Type             | Collation       | Null | Key | Default | Extra          | Privileges                      | Comment |
+---------+------------------+-----------------+------+-----+---------+----------------+---------------------------------+---------+
| item_id | int(10) unsigned | NULL            |      | PRI | NULL    | auto_increment | select,insert,update,references |         |
| name    | varchar(64)      | utf8_unicode_ci |      |     |         |                | select,insert,update,references |         |
| url     | varchar(128)     | utf8_unicode_ci |      | MUL |         |                | select,insert,update,references |         |
| text    | text             | utf8_unicode_ci |      |     |         |                | select,insert,update,references |         |
+---------+------------------+-----------------+------+-----+---------+----------------+---------------------------------+---------+
4 rows in set (0.02 sec)
*/

require_once 'obj.class.php';
Class Item Extends Obj
{
        private $aData;

        public function __construct()
        {
                $this->aData=array_fill_keys(array('item_id','name','url','text'),'');
                parent::__construct();
        }

        public function __set($name, $value)
        {
                $this->aData[$name] = $value;
        }

        public function __get($name)
        {
                if (array_key_exists($name, $this->aData))
                        return $this->aData[$name];
        }

        public function load($id)
        {
                $this->aData=parent::load('items','item_id',$id);
        }

        public function save()
        {
                return parent::save('items','item_id',$this->aData);
        }
}

?>

obj.class.php

<?php
require_once 'db.class.php';

Class Obj
{
        private $db;

        public function __construct()
        {
                $this->db=Database::singleton();
        }

        protected function load($sTable,$sIdName,$sId)
        {
                $aResult=false;
                $sQuery='SELECT * FROM `'.$sTable.'` WHERE `'.$sIdName.'`=\''.$this->db->SQLescape($sId).'\'';
                $this->db->query($sQuery);
                $aResult=$this->db->fetch_assoc();
                $this->db->free_result();
                return $aResult;
        }

        protected function save($sTable,$sIdName,$aArgs)
        {
                $sWhere=' WHERE `'.$sIdName.'`=\''.$this->db->SQLescape($aArgs[$sIdName]).'\'';
                unset($aArgs[$sIdName]);
                $sSet=$this->db->sPair($aArgs);
                $sQuery='SELECT * FROM `'.$sTable.'`'.$sWhere;
                $this->db->query($sQuery);
                if ($this->db->num_rows()>0)
                        $sQuery='UPDATE `'.$sTable.'` SET '.$sSet.$sWhere;
                else
                        $sQuery='INSERT INTO `'.$sTable.'` SET '.$sSet;
                $this->db->free_result();
                return $this->db->query($sQuery);
        }

        protected function remove($sTable,$sIdName,$sId)
        {
                $sQuery='DELETE FROM `'.$sTable.'` WHERE `'.$sIdName.'`=\''.$this->db->SQLescape($sId).'\'';
                return $this->db->query($sQuery);
        }

        public function __destruct()
        {
        }
}
?>

A search function will be added.

db.class.php

<?php
Class Database
{
        private static $instance;

        private $rLink;
        private $db;
        private $ini;
        private $rResult;

        public function __construct()
        {
                $this->ini = parse_ini_file('config.ini.php','true');
                $this->rLink = mysql_connect
                                ($this->ini['db']['host'],
                                 $this->ini['db']['user'],
                                 $this->ini['db']['password']);
                if ($this->rLink === false)
                        trigger_error(mysql_error());
                $this->db = mysql_select_db ($this->ini['db']['database'],$this->rLink);
                if ($this->db === false)
                        trigger_error(mysql_error());
                return true;
        }

        public static function singleton()
        {
                if (!isset(self::$instance)) {
                        $c = __CLASS__;
                        self::$instance = new $c;
                }
                return self::$instance;
        }

        public function SQLescape($s)
        {
                return mysql_real_escape_string($s,$this->rLink);
        }

        public function host_information()
        {
                return mysql_get_host_info($this->rLink);
        }

        public function query($sQuery)
        {
                $this->rResult=mysql_query($sQuery,$this->rLink);
                if ($this->rResult === false)
                        trigger_error(mysql_error());
                return $this->rResult;
        }

        public function fetch_assoc()
        {
                return mysql_fetch_assoc($this->rResult);
        }

        public function sPair($aPair)
        {
                $sReturn='';
                foreach ($aPair as $k => $v)
                        $sReturn .= "`$k`='".$this->SQLescape($v).'\', ';
                $sReturn=substr($sReturn,0,-2);
                return $sReturn;
        }

        public function sWhere($aWhere)
        {
                $sReturn='';
                foreach ($aWhere as $k => $v)
                        $sReturn .= " `$k`='".$this->SQLescape($v).'\' AND ';
                return substr($sReturn,0,-4);
        }

        public function sRegExp($aRegExp)
        {
                $sReturn='';
                foreach ($aRegExp as $k => $v)
                        $sReturn .= " `$k` REGEXP '".$this->SQLescape($v).'\' AND ';
                return substr($sReturn,0,-4);
        }

        public function num_rows()
        {
                return mysql_num_rows($this->rResult);
        }

        public function free_result()
        {
                mysql_free_result($this->rResult);
        }

        public function __destruct()
        {
                if (isset($this->rLink))
                        if ($this->rLink !== false)
                                mysql_close($this->rLink);
        }

        public function __clone()
        {
                trigger_error('Clone is not allowed.', E_USER_ERROR);
        }

}
?>

PHP Rating Demo

The basic idea of this rating script is to count the number of votes and the ratings and store them in a text file (a database could also be used).

The first step was to create some images to represent the ratings. The easiest approach was to use ImageMagick to create colorful asterisks.

Next, the javascript was written. There was one minor browser compatibility issue with IE, the margin was not included in the offset. A simple fix was to use PHP to synchronize the left margin in the style tag and to add it in later in the javascript, if it was IE.

Finally, the PHP was written and tested. It is listed below.

rating.php5

$sRatingFile='rating.txt';
if (is_file($sRatingFile))
        $sRating=file_get_contents($sRatingFile);
else
        $sRating="0,0";

$aRating=explode(',',$sRating);

if (isset($_GET['r']))
{
        $r=(int)$_GET['r'];
        if (($r<0)||($r>5)) die('Bad rating');
        else
        {
                $aRating[0]++;
                $aRating[1]+=$r;
                file_put_contents($sRatingFile,implode(',',$aRating));
        }
}
$v=$aRating[0];
if ($aRating[0]!=0)
        $r=(int)($aRating[1]/$aRating[0]);
else
        $r=0;

echo<<<INPUT
<p>
<label>Rating</label>
<input name="iRating" id="iRating" type="image" src="images/$r.rating.png" alt="Rating" onclick="ratingClick(event)" />
$v votes
</p>
<div style="display:none">
<img src="images/$r.rating.png" id="imgRating" />
</div>
INPUT;

In a production environment, some CAPTCHA or other tool should be used to prevent automated submissions.

PHP5 .tgz Download Counter

To add a download counter, you can use a RewriteRule to route requests through a script to deliver the file and update the counter. You may also be able to use a header(’location:file.tgz’) call as well.

.htaccess

RewriteEngine On
RewriteRule ^(.*\.tgz)$ download.php5?path=$1 [L]

download.php5

<?php
// downloading a file
$filename = $_GET['path'];
if (is_file($filename))
{
// fix for IE catching or PHP bug issue
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
header("Content-Description: File Transfer");
// browser must download file from server instead of cache

// force download dialog
header("Content-Type: application/x-tgz");

// use the Content-Disposition header to supply a recommended filename and
// force the browser to display the save dialog.
header("Content-Disposition: attachment; filename=".basename($filename).";");

/*
The Content-transfer-encoding header should be binary, since the file will be read
directly from the disk and the raw bytes passed to the downloading computer.
The Content-length header is useful to set for downloads. The browser will be able to
show a progress meter as a file downloads. The content-lenght can be determines by
filesize function returns the size of a file.
*/
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($filename));

@readfile($filename);
$sCountFile=$filename.'.count.txt';
$iCount=file_get_contents($sCountFile);
file_put_contents($sCountFile,$iCount+1);
}
?>

Display Logic

<p><strong><?php include 'tri.tgz.count.txt'; ?></strong> downloads.</p>

This can be added to a site without changing the download URLs.

Many thanks to the PHP header link above, where the header code came from.