Last modified on 18 February 2010, at 04:05

Understanding Noah's Classifieds/Multiple Image Submission

ProblemEdit

In Noah's classifieds an advert can only submit one image, we want to submit n images.

SolutionEdit

Modify the source as listed bellow

Preliminary CodingEdit

Before we do anything I would like to wrap Noah's image operations into a class or at least a bunch of functions. The person/people who coded it initially did not do this. This in my opinion is a big mistake as it greatly complicates the code.

The following is derived from the Noah source code and my own genius common sense. All of these will be placed in a new file which we will call image.php. The idea is to create an image management library that we can use with other projects without much recoding

Image.php Source

Target FunctionalityEdit

Now let's first talk about our target. We want the following features:

  • Modify the maximum amount of images saved from the set category screen (so each category can allow for a variable number of images)
  • Modify the Maximum Image Size and Thumb size from category screen
  • The first image uploaded in the series will be used as the display image when listing all the adverts
  • Images and their thumbs must be removed when an advertisement is removed
  • Each different category must be able to insert custom html text which will be used as a spacer.
  • Dynamically create the upload fields so submission is not cluttered

Modifying gorum/dbproperty.phpEdit

Because any and all object_vars are automatically saved to the database it is problomatic to use $this->data to pass data from one function to the next. To get around this the following modification needs to be made to dbproperty.php

function getCreateSetStr($base, $typ , $create=TRUE)
{
    $object_vars = get_object_vars($base);
    $firstField = TRUE;
    $query = "";
    $typ = $base->getTypeInfo();
    foreach( $object_vars as $attribute=>$value )
    {
 // NEW DAWID
 if (!isset($typ["attributes"][$attribute]))
 continue;
 // NEW DAWID

Modifying gorum/object.phpEdit

Change the function getVisibility as shown

function getVisibility( $typ, $attr )
{
    global $gorumroll;
 
 if (!isset($typ["attributes"][$attr]))
 return Form_hidden;

Modifying CategoriesEdit

Firstly we need to add 6 new rows to our category database (Table:classifieds_classifiedscategory)

imagesAllowed, Type int(4), default = 10, Unsigned
ThumbSizeX, Type int(10), default = 64, Unsigned
ThumbSizeY, Type int(10), default = 100, Unsigned
imageMaxSizeX, Type int(12), default = 800, Unsigned
imageMaxSizeY, Type int(12), default = 600, Unsigned
imageSpacer, Type varchar(250), default = 'HTML TEXT'

These are the new rows that I added. From the information you can see that imagesAllowed will have a range of 0-16, thumbSize 0-1024 and imageSize 0-2048. If these are too little or too much adjust accordingly so that it doesn't hog database space.

Here is the SQL Script for adding these

ALTER TABLE `classifieds_classifiedscategory` ADD `imagesAllowed` INT( 4 ) UNSIGNED NOT NULL DEFAULT '10',
ADD `thumbSizeX` INT( 10 ) UNSIGNED NOT NULL DEFAULT '64',
ADD `thumbSizeY` INT( 10 ) UNSIGNED NOT NULL DEFAULT '100',
ADD `imageMaxSizeX` INT( 12 ) UNSIGNED NOT NULL DEFAULT '800',
ADD `imageMaxSizeY` INT( 12 ) UNSIGNED NOT NULL DEFAULT '600';
ADD `imageSpacer` VARCHAR( 250 ) NOT NULL DEFAULT '
<img src="i/spacer.gif" alt="." width="$thumbWidth" height="20" />';

Modifying the Source of category.php

Because of the lovely framework the Noah uses we can easily just add more fields to the categories. Simply add these lines at the top of file.

$category_typ["attributes"]["picture"] =  
            array(
                "type"=>"VARCHAR",
                "file",
                "max" =>"250"
             );

/* DAWID JOUBERT IMAGE MODIFICATIONS ADDITIONS */
$category_typ["attributes"]["imagesAllowed"] =  
            array(
                "type"=>"INT",
                "text",
                "max" =>"16"
             );  
$category_typ["attributes"]["thumbSizeX"] =  
            array(
                "type"=>"INT",
                "text",
                "max" =>"1024"
             );    
$category_typ["attributes"]["thumbSizeY"] =  
            array(
                "type"=>"INT",
                "text",
                "max" =>"1024"
             ); 
$category_typ["attributes"]["imageMaxSizeX"] =  
            array(
                "type"=>"INT",
                "text",
                "max" =>"2048"
             );
$category_typ["attributes"]["imageMaxSizeY"] =  
            array(
                "type"=>"INT",
                "text",
                "max" =>"2048"
             ); 
$category_typ["attributes"]["imageSpacer"] =  
            array(
                "type"=>"VARCHAR",
                "text",
                "max" =>"250"
             );        

class ClassifiedsCategory extends Category


Adding the new texts to the language file(s)

Now we also need to add the following to lang_en.php (and all the other languages you plan on using). For consistency add it after the comment that says "Category"

// Dawid Joubert Image Editing
$lll["imagesAllowed"]="Number of Images Allowed";
$lll["thumbSizeX"]="Generated Thumbnails' size X";
$lll["thumbSizeY"]="Generated Thumbnails' size Y";
$lll["imageMaxSizeX"]="Max Image Size X";
$lll["imageMaxSizeY"]="Max Image Size Y";
$lll["imageSpacer"]="HTML Text to place after each image (use as spacer)";
$lll["pictureTemp"]='Pictures';
$lll["imageFileNotLoaded"]='Image file not saved/created!';

Modifying AdvertisementsEdit

In table classifieds_advertisement we won't be adding any rows. Instead we will modify an existing row called picture.

Picture is a STRING (VARCHAR) of size 250. At the moment Noah stores the extension of the 1 image that is uploaded into picture. We will be doing somethign very similiar, we will store the extensions of the files but have them comma seperated. So the first extension is the first image and the second extension the second and so on.

Now we must modify the source of advertisements.php This is going to be heavy modification so lets first agree on the current functionality.

Images are saved under "$adAttDir/$this->id".".".$ext and their thumbnails "$adAttDir/th_$this->id".".".$ext. This format is not a problem for storing single images but is for storing multiple images. So we will be modifying it.

First to maintain backward compatibality we will keep the current format for the first image in the sequence however the second, third and nth image will have this pattern.

Full Image:"$adAttDir/$this->id"."-$i.".$this->picture

Thumbnails:"$adAttDir/th_$this->id"."-$i.".$this->picture

Where $i is the image number starting from 0 so if a user had 6 jpeg images they will be named as follows:

pictures/listings/5.jpg // First image. Shown in advert listing
pictures/listings/5-0.jpg // Second Image. Shown in details view (when viewing the advert)
pictures/listings/5-1.jpg // ""
pictures/listings/5-2.jpg // ""
pictures/listings/5-3.jpg // ""
pictures/listings/5-4.jpg // ""

We need a function that splits a string like "jpg,gif,jpg" into array("jpg","gif","jpg"). Such a function has been created in image.php called getImageExtensionsFromString($string)


So we will need to modify 4 functions I believe:

  • delete()
    This function is called when you remove the advertisement
  • showListVal()
    This function is called for every piece of information that is shown both in the advert listing and in the advert's details itself. We need this function so that it now lists all the pictures when viewing the advert details
  • activateVariableFields()
    This function needs to be modified so that it displays options for uploading n amount of images
  • storeAttatchment()
    Modify so that it actually stores all incomming images
  • showDetials()
    Shows the detailed view of the advertisement

delete()Edit

Replace everything inside

if ($this->picture) 
{
.. Old Code ..
};

With this so that the entire block looks like (Now any1 just dare tell me this doesn't look cleaner!!!)

 if( $this->picture )
 {
 $this->getImageExtensions();
 $limit = count($this->imageExtensions); 
 for ($i=0;$i < $limit;$i++)
 {
 cleanDelete($this->getImageName($i));
 cleanDelete($this->getThumbName($i)); 
 }
 }

showListVal()Edit

Replace everything in

elseif ($attr=="showpic") 
{
 ..old source code...
}

With. After which I breifly describe what we are doing.

elseif ($attr=="showpic") 
 {
 $s="<img src='$adAttDir/no.png'>"; // Will be overwritten later
 $this->getImageLimits();
 
 if($this->picture && $this->imagesAllowed) // Do we have any extensions to work with?
 {
 // Create the image extensions for use
 $this->getImageExtensions(); 
 $this->Debug(); 
 // Are we in the details view?
 if ($gorumroll->method == "showdetails")
 {
 $limit = count($this->imageExtensions); 
 $s = ; 
 for ($i=0;$i < $limit;$i++)
 {
 if (strlen($this->imageExtensions[$i]) > 2)
 { 
 $s .= $this->getImageHTML($i)."
"; } } } else { // Just make the output equal to a single image $s= $this->getImageHTML(0); } } }

$s is the output buffer of the function. All HTML must be written to it. The first thing we do is make $s equal to the default image icon. This is so that if no images were found atleast the default icon is shown. Next we see if picture has any text inside it (remember in php "" or 0 or "0" are all considered 'false'). So if there are pictures in it we progress. Extracting the image extensions using getImageExtensions();. Then depending on the whether this is a listing or a detailed view we print either the first or all of the images to the buffer.


activateVariableFields()Edit

At the top of advertisement.php is $item_typ["attributes"]["picture"] = {..}; Add this to the array "form invisible", this will make sure that it is not added to the form when modifying/creating adds. The above mentioned declaration will now look like this.

$item_typ["attributes"]["picture"] =  
            array(
                "type"='>'"VARCHAR",
                "file",
                "max" ='>'"250",
 "form invisible"
             );

Now the next part will ensure that when the methods CREATE_FORM and MODIFY_FORM are called the image fields are added to the forms. Add this just after the global definitions.

global $gorumroll, $expiration;
// NEW CODE DAWID
$this-'>'getImageLimits(); 
// Create new input boxes for each picture, but only if we are creating a new advert or modifying an existing one
if (($gorumroll-'>'method == "modify_form") || ($gorumroll-'>'method == "create_form"))
{
 for ($i = 0;$i '<' $this-'>'imagesAllowed;$i++)
 {
 $lll["picture$i"]=str_replace('$i',$i,$lll["pictureTemp"]); // Creates language thingies
 $item_typ["attributes"]["picture$i"] =  
 array(
 "type"='>'"VARCHAR",
 "file",
 "max" ='>'"250"
  ); 
 };
};
// NEW CODE DAWID

hasAdminRights($isAdm);

abba

valid()Edit

This function validates the single image that is uploaded before the image is stored. The rest of the validation is done by the class this one inherits from. Just delete the entire thing.


storeAttachment()Edit

This function has been modified almost entirely, just copy over the new function.

function storeAttachment()
{
    global $HTTP_POST_FILES;
    global $whatHappened, $lll , $infoText;
    global $adAttDir, $applName, $itemClassName;
    global $maxPicSize;
 
 // New code
    // Load this class
 $this->getImageLimits(); // Get the dimensions and stuff allowed
 $this->getImageExtensions();
 $parsedAtleastOne = false;
 $count = 0;
 // Loop for every image allowed
 for ($i = 0;$i < $this->imagesAllowed;$i++)
 { 
 if ((isset($this->imageExtensions[$count]) == false))
 $this->imageExtensions[$count] = ".";
 
 if (strlen($this->imageExtensions[$count]) > 2) 
 {
 $count++;
 $hasImage = true;
 } else $hasImage = false;
 
 $ind = "picture$i";
     if (!isset($HTTP_POST_FILES[$ind]["name"]) || $HTTP_POST_FILES[$ind]["name"]=="")
 continue;

 if (!file_exists($HTTP_POST_FILES[$ind]["tmp_name"]))
 {
 // This should never happen
 handleError("Temp Image doesn't exists?? WTF"); 
 continue;
 }

 if (strstr($HTTP_POST_FILES[$ind]["name"]," "))
 {
         $infoText .= $lll["spacenoatt"].'
'; continue; } if ($HTTP_POST_FILES[$ind]["tmp_name"]=="none") continue; if ($HTTP_POST_FILES[$ind]["size"] > $maxPicSize) { $infoText .= sprintf($lll["picFileSizeToLarge2"], $maxPicSize).'
'; continue; } $fname=$HTTP_POST_FILES[$ind]["tmp_name"]; $size = getimagesize($fname); if (!$size) { $infoText.=$lll["notValidImageFile"].'
'; continue; } $type = $size[2]; global $g_Extensions; // Found in image.php if (!isset($g_Extensions[$type])) { $infoText .=$lll["notValidImageFile"].'
'; continue; } // We are checking dimensions anymore as we might as well resize the image /* if( $size[0]>$this->imageMaxSizeX || $size[1]>$this->imageMaxSizeY ) { $infoText .=sprintf($lll["picFileDimensionToLarge"], $this->imageMaxSizeX, $this->imageMaxSizeY).'
'; $whatHappened = "invalid_form"; continue; } */ if ($hasImage) $count--; // Instanciate a new image $image = new myImage; // Read the image $image->ReadImage($HTTP_POST_FILES[$ind]["tmp_name"]); // Remove old pictures and thumbnails cleanDelete($this->getImageName($count)); cleanDelete($this->getThumbName($count)); // Save the image extension $this->imageExtensions[$count] = $g_Extensions[$type]; // Now create our image if ($image->CreateLocal($this->getImageName($count),$this->imageMaxSizeX,$this->imageMaxSizeY)) { // Create the thumb $image->CreateLocal($this->getThumbName($count),$this->thumbSizeX,$this->thumbSizeY,true); $parsedAtleastOne = true; $count++; } else { // Why wasn't it created, could it be because the file format doesn't support resizing if ($image->supported == false) $infoText .=sprintf($lll["picFileDimensionToLarge"], $this->imageMaxSizeX, $this->imageMaxSizeY).'
'; else $infoText.=$lll["imageFileNotLoaded"].'
'; $this->imageExtensions[$count] = "."; } } $HTTP_POST_FILES["picture"]["name"]=""; if ($parsedAtleastOne) { $this->picture = addslashes(createImageExtensionsStringFromArray($this->imageExtensions)); $this->Debug(); // Create the extensions string $query = "UPDATE $applName"."_$itemClassName SET picture='$this->picture'". " WHERE id=$this->id"; executeQuery( $query ); } return ok; }

showDetails()Edit

Change everything inside

if($this-'>'picture)
{
 ...
}    

To be

 if($this->picture)
 { 

$s.=""; $s.="\n"; }

Finally More FunctionsEdit

Add these new functions to the top of the file

/**********************************/
// Advertisement specific image functions
function cleanDelete($name)
{
 if (file_exists($name))
 {
 // Attempt to delete
     $ret=@unlink($name); 
 if(!$ret) $infoText=sprintf($lll["cantdelfile"],$name);
 return $ret;
 }
 return true;
};

function getImageExtensionsFromString($string)
{
 return split('[,]', $string);
};

function createImageExtensionsStringFromArray($arr)
{
 $out = "";
 foreach ($arr as $value)
 {
 $out .= $value.',';
 }
 return $out;
}

/**********************************/

class Advertisement extends Item
{

/**********************************/
// Advertisement specific image functions

 function getImageExtensions()
 {
 if (isset($this->picture))
 $this->imageExtensions = getImageExtensionsFromString($this->picture);
 }
 
 function getImageName($i)
 {
 if (!isset($this->imageExtensions[$i])) return ""; 
 global $adAttDir;
 if ($i != 0) 
 {
 return $fileName = "$adAttDir/$this->id"."-$i.".$this->imageExtensions[$i];
 }
 else return $fileName = "$adAttDir/$this->id".".".$this->imageExtensions[$i];
 }
 
 function getThumbName($i)
 {
 if (!isset($this->imageExtensions[$i])) return ""; 
 global $adAttDir;
 if ($i != 0) 
 {
 return $thumbName = "$adAttDir/th_$this->id"."-$i.".$this->imageExtensions[$i];
 }
 else return $thumbName = "$adAttDir/th_$this->id".".".$this->imageExtensions[$i];
 }
 
 function getImageHTML($i)
 {
 if (!isset($this->imageExtensions[$i])) return "";
 if (strlen($this->imageExtensions[$i]) < 2) return "";
 $picName = $this->getImageName($i); // Get the first image
 $thName = $this->getThumbName($i); // Get the first thumb
 
 // If the thumbnail doesn't exists use the original picture's name instead.
 if (!file_exists($thName)) $thName = $picName;
 if (!file_exists($picName)) return "";
 
 // Okay now we need to find the dimensions of the thumbnail and scale them for viewing.
 // If it really is a thumbnail we won't have to scale using html. If it isn't then we have no
 // other choice as the reason no thumb exists is because this format is not supported by php
 $size = getimagesize( $thName );
 $size = RatioImageSize($size,$this->thumbSizeX,$this->thumbSizeY,true);
 return "<a href='$picName' target='_blank'><img src='$thName' width='$size[0]' height='$size[1]' border='0'></a>$this->imageSpacer"; 
 }

 // Get the image settings from this advert's category 
 function getImageLimits()
 {
 //if (!isset($this->imageLimitsLoaded)) return;
 
     global $categoryClassName;
        $c = new $categoryClassName;
        $c->id = $this->cid;
        load($c);
        $this->imagesAllowed = $c->imagesAllowed;
        $this->thumbSizeX = $c->thumbSizeX;
        $this->thumbSizeY = $c->thumbSizeY;
        $this->imageMaxSizeX= $c->imageMaxSizeX; 
        $this->imageMaxSizeY= $c->imageMaxSizeY; 
 $this->imageLimitsLoaded = true;
 // Construct the image spacer
 $this->imageSpacer = 
 str_replace(array('$thumbWidth','$thumbHeight'),
 array($c->thumbSizeX,$c->thumbSizeY),$c->imageSpacer);
 
 // Quick validation test
 if ($c->imagesAllowed)
 {
 if ($c->thumbSizeX * $c->thumbSizeY * $c->imageMaxSizeX * $c->imageMaxSizeY <= 0)
 die('Image dimensions impossible');
 }
 }
 
 function Debug()
 {
 if (false)
 {
 if (isset($this->imageExtensions))
 echo array_to_str($this->imageExtensions); 
 if (isset($this->picture)) 
 echo array_to_str($this->picture); 
 }
 }
/**********************************/

Notice one half above the class and the other inside the class.

Image.php Source

\n";
 $s.=$this->showListVal("showpic");
$s.="