Understanding Noah's Classifieds/Multiple Image Submission
Problem
editIn Noah's classifieds an advert can only submit one image, we want to submit n images.
Solution
editModify the source as listed bellow
Preliminary Coding
editBefore 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
Target Functionality
editNow 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.php
editBecause any and all object_vars are automatically saved to the database it is problematic 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.php
editChange the function getVisibility as shown
function getVisibility( $typ, $attr )
{
global $gorumroll;
if (!isset($typ["attributes"][$attr]))
return Form_hidden;
Modifying Categories
editFirstly 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 '<br/><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 Advertisements
editIn 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 something very similar, we will store the extensions of the files but have them comma separated. 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 incoming images
- showDetials()
- Shows the detailed view of the advertisement
delete()
editReplace 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()
editReplace everything in
elseif ($attr=="showpic")
{
..old source code...
}
</syntaxhighlight">
With. After which I briefly describe what we are doing.
<syntaxhighlight lang="php">
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)."<br />";
}
}
}
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 at least 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()
editAt 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()
editThis 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()
editThis 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"].'<br />';
continue;
}
if ($HTTP_POST_FILES[$ind]["tmp_name"]=="none")
continue;
if ($HTTP_POST_FILES[$ind]["size"] > $maxPicSize)
{
$infoText .= sprintf($lll["picFileSizeToLarge2"], $maxPicSize).'<br />';
continue;
}
$fname=$HTTP_POST_FILES[$ind]["tmp_name"];
$size = getimagesize($fname);
if (!$size)
{
$infoText.=$lll["notValidImageFile"].'<br />';
continue;
}
$type = $size[2];
global $g_Extensions; // Found in image.php
if (!isset($g_Extensions[$type]))
{
$infoText .=$lll["notValidImageFile"].'<br />';
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).'<br />';
$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).'<br />';
else $infoText.=$lll["imageFileNotLoaded"].'<br />';
$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()
editChange everything inside
if($this-'>'picture)
{
...
}
To be
if($this->picture)
{
$s.="<tr><td valign='top' align='left' rowspan='30' class='cell'>\n";
$s.=$this->showListVal("showpic");
$s.="</td>";
$s.="</tr>\n";
}
Finally More Functions
editAdd 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.