Prevent Anytthing but Img Being Uploaded Php
17 May 2019: This article and the code were updated for PHP7 compatibility.
In my tutorial Build a CMS in an Afternoon with PHP and MySQL, I showed how to build a unproblematic but useful content management system with PHP and MySQL. I too showed how to extend the CMS to let article categories.
In this tutorial, y'all'll look at another way to extend the CMS. You'll take the original CMS lawmaking, and change it then that the administrator can upload an image for each article. Then, when a company views an commodity page, the CMS will display the image at the outset of the article. In addition, our CMS will generate a smaller thumbnail version of each article prototype, and display this thumbnail adjacent to each article headline in the homepage and commodity archive pages.
You can meet the finished issue by clicking the View Demo link above. Observe the thumbnail images next to each article headline. Click a headline or thumbnail to view the respective article, forth with the total-size commodity paradigm.
The plan
We'll start with the original CMS code from Build a CMS in an Afternoon with PHP and MySQL, and alter it to include the prototype upload feature. Here are the steps we'll demand to conduct out:
- Create a couple of folders to store the commodity images
- Modify various image-related settings to the CMS config file
- Modify the database to shop the image filename extensions
- Modify the
Commodity
class to handle images - Modify
admin.php
to handle image upload and deletion - Modify the forepart-end templates to display the article images and thumbnails
- Modify the back-terminate commodity edit grade to let the administrator upload an epitome, view the commodity image, and delete the article image
- Tweak the CMS stylesheet to manner the article images and the new elements in the article edit form
Ready? Let's get started!
Stride 1: Create the image folders
The showtime thing to practise is create a couple of folders in your website to store the full-size and thumbnail article images.
Open up the existing cms
folder and yous'll run across an images
folder containing the sample logo.jpg
image. Inside this images
folder, create an articles
folder. And so, inside the articles
folder, create two more folders:
-
fullsize
to store the total-size article images -
thumb
to store the smaller thumbnail versions of the article images
Next you demand to requite your web server user permission to create files in these 2 folders. Typically on a Linux or Mac arrangement, you need to change the permissions to 777, like this:
$ cd images/articles/ $ chmod 777 fullsize $ chmod 777 pollex
If your CMS is on a remote web server then you lot can usually set up these permissions using your FTP software.
Step 2: Edit the config file
The next step is to add some image-related constants to the CMS config file. Open up upwards the config.php
file in the meridian-level cms
binder, and add the new lines highlighted in the code below:
<?php ini_set( "display_errors", truthful ); date_default_timezone_set( "Australia/Sydney" ); // http://www.php.net/transmission/en/timezones.php define( "DB_DSN", "mysql:host=localhost;dbname=cms" ); define( "DB_USERNAME", "username" ); define( "DB_PASSWORD", "password" ); define( "CLASS_PATH", "classes" ); define( "TEMPLATE_PATH", "templates" ); define( "HOMEPAGE_NUM_ARTICLES", v ); define( "ADMIN_USERNAME", "admin" ); define( "ADMIN_PASSWORD", "mypass" ); define( "ARTICLE_IMAGE_PATH", "images/articles" ); define( "IMG_TYPE_FULLSIZE", "fullsize" ); define( "IMG_TYPE_THUMB", "pollex" ); ascertain( "ARTICLE_THUMB_WIDTH", 120 ); define( "JPEG_QUALITY", 85 ); require( CLASS_PATH . "/Article.php" ); function handleException( $exception ) { repeat "Sorry, a trouble occurred. Please effort later."; error_log( $exception->getMessage() ); } set_exception_handler( 'handleException' ); ?>
You've added the following constants:
-
ARTICLE_IMAGE_PATH
defines the path to the commodity images folder, relative to the top-level CMS folder. (If you desire to store your article images somewhere else, modify this constant accordingly.) -
IMG_TYPE_FULLSIZE
defines a abiding to correspond the "full-size" epitome type. We'll apply this in the lawmaking whenever we want to bespeak a full-size image. This value ("fullsize"
) is too used to locate the full-size images folder (images/articles/fullsize
), and so if you use a different folder name, you'll want to update this abiding also. -
IMG_TYPE_THUMB
does a like task toIMG_TYPE_FULLSIZE
, merely represents the "thumbnail" image blazon instead. -
ARTICLE_THUMB_WIDTH
defines the width to apply for the article thumbnail images, in pixels. Our prototype-handling lawmaking will use this value when generating the thumbnail versions of article images when they're uploaded. -
JPEG_QUALITY
defines the quality level to use when generating thumbnail versions of JPEG images. The value ranges from 0 to 100, where 100 is the all-time quality (but largest file size). 85 is a good compromise.
Pace 3: Alter the database
You need to make one small change to the articles
table in the CMS database. Open up the tables.sql
file from the original CMS, and add the line highlighted in the code below:
DROP TABLE IF EXISTS articles; CREATE TABLE articles ( id smallint unsigned Not Zippo auto_increment, publicationDate appointment NOT NULL, # When the article was published championship varchar(255) Non NULL, # Full title of the article summary text NOT NULL, # A short summary of the article content mediumtext Non NULL, # The HTML content of the commodity imageExtension varchar(255) Not NULL, # The filename extension of the commodity'southward total-size and thumbnail images PRIMARY KEY (id) );
This line adds a new field chosen imageExtension
to the articles
table. This field stores the filename extension of each article's uploaded image. For example, if the administrator uploads a PNG image then we'll store the value ".png"
in the imageExtension
field.
What if you already take articles in your CMS?
If yous load the above tables.sql
file into MySQL then information technology will delete any existing articles
tabular array in your cms
database, and recreate the articles
table from scratch. This will delete whatsoever articles already in your CMS, which is obviously not what you desire.
So if y'all already have articles in your CMS database, you want to alter the articles
table while retaining the existing information in the table. To do this, modify your tables.sql
file to the post-obit:
Alter TABLE articles ADD imageExtension varchar(255) NOT Zero AFTER content;
Applying the changes
To actually create your articles
table (or add the new imageExtension
field to your existing articles
table, as appropriate), you need to load the tables.sql
file into MySQL. To exercise this, follow the procedure described in Applying the changes in my last tutorial.
To check that your changes have been made, first login to MySQL:
mysql -u username -p cms
Then apply the Testify TABLES
and Explicate
commands to check your tabular array schemas in MySQL:
mysql> evidence tables; +---------------+ | Tables_in_cms | +---------------+ | manufactures | +---------------+ ane row in set (0.00 sec) mysql> explain articles; +-----------------+----------------------+------+-----+---------+----------------+ | Field | Type | Nada | Primal | Default | Actress | +-----------------+----------------------+------+-----+---------+----------------+ | id | smallint(5) unsigned | NO | PRI | Nix | auto_increment | | publicationDate | appointment | NO | | Nada | | | title | varchar(255) | NO | | NULL | | | summary | text | NO | | NULL | | | content | mediumtext | NO | | Zero | | | imageExtension | varchar(255) | NO | | Naught | | +-----------------+----------------------+------+-----+---------+----------------+ half-dozen rows in set (0.00 sec) mysql>
Notice the new imageExtension
field within the articles
table.
You lot've prepare your CMS database so that it'due south prepare to handle image uploads. At present you can kickoff modifying the CMS code.
Pace 4: Modify the Commodity
grade
Next, we need to modify the Article
course to handle article images. Here'southward the updated Article.php
class file. I've highlighted the lines of code and then you lot tin meet what's been added and changed. Replace the code in your existing cms/classes/Article.php
file with this new code:
<?php /** * Form to handle manufactures */ class Article { // Properties /** * @var int The article ID from the database */ public $id = null; /** * @var int When the article is to exist / was starting time published */ public $publicationDate = null; /** * @var string Full title of the article */ public $title = null; /** * @var string A short summary of the article */ public $summary = nil; /** * @var string The HTML content of the article */ public $content = aught; /** * @var string The filename extension of the article's full-size and thumbnail images (empty string ways the article has no image) */ public $imageExtension = ""; /** * Sets the object's properties using the values in the supplied array * * @param assoc The property values */ public function __construct( $information=array() ) { if ( isset( $data['id'] ) ) $this->id = (int) $data['id']; if ( isset( $data['publicationDate'] ) ) $this->publicationDate = (int) $data['publicationDate']; if ( isset( $data['title'] ) ) $this->title = preg_replace ( "/[^\.\,\-\_\'\"\@\?\!\:\$ a-zA-Z0-9()]/", "", $information['title'] ); if ( isset( $data['summary'] ) ) $this->summary = preg_replace ( "/[^\.\,\-\_\'\"\@\?\!\:\$ a-zA-Z0-9()]/", "", $information['summary'] ); if ( isset( $data['content'] ) ) $this->content = $data['content']; if ( isset( $data['imageExtension'] ) ) $this->imageExtension = preg_replace ( "/[^\.\,\-\_\'\"\@\?\!\$ a-zA-Z0-nine()]/", "", $data['imageExtension'] ); } /** * Sets the object's properties using the edit form mail service values in the supplied array * * @param assoc The form post values */ public part storeFormValues( $params ) { // Store all the parameters $this->__construct( $params ); // Parse and store the publication date if ( isset($params['publicationDate']) ) { $publicationDate = explode ( '-', $params['publicationDate'] ); if ( count($publicationDate) == 3 ) { list ( $y, $m, $d ) = $publicationDate; $this->publicationDate = mktime ( 0, 0, 0, $grand, $d, $y ); } } } /** * Stores any prototype uploaded from the edit form * * @param assoc The 'image' element from the $_FILES array containing the file upload data */ public function storeUploadedImage( $image ) { if ( $paradigm['error'] == UPLOAD_ERR_OK ) { // Does the Article object have an ID? if ( is_null( $this->id ) ) trigger_error( "Article::storeUploadedImage(): Attempt to upload an paradigm for an Article object that does not have its ID property set.", E_USER_ERROR ); // Delete whatever previous image(s) for this article $this->deleteImages(); // Get and store the image filename extension $this->imageExtension = strtolower( strrchr( $epitome['name'], '.' ) ); // Shop the image $tempFilename = trim( $image['tmp_name'] ); if ( is_uploaded_file ( $tempFilename ) ) { if ( !( move_uploaded_file( $tempFilename, $this->getImagePath() ) ) ) trigger_error( "Article::storeUploadedImage(): Couldn't move uploaded file.", E_USER_ERROR ); if ( !( chmod( $this->getImagePath(), 0666 ) ) ) trigger_error( "Commodity::storeUploadedImage(): Couldn't fix permissions on uploaded file.", E_USER_ERROR ); } // Get the epitome size and type $attrs = getimagesize ( $this->getImagePath() ); $imageWidth = $attrs[0]; $imageHeight = $attrs[1]; $imageType = $attrs[2]; // Load the image into retention switch ( $imageType ) { instance IMAGETYPE_GIF: $imageResource = imagecreatefromgif ( $this->getImagePath() ); break; example IMAGETYPE_JPEG: $imageResource = imagecreatefromjpeg ( $this->getImagePath() ); pause; example IMAGETYPE_PNG: $imageResource = imagecreatefrompng ( $this->getImagePath() ); pause; default: trigger_error ( "Commodity::storeUploadedImage(): Unhandled or unknown image type ($imageType)", E_USER_ERROR ); } // Re-create and resize the image to create the thumbnail $thumbHeight = intval ( $imageHeight / $imageWidth * ARTICLE_THUMB_WIDTH ); $thumbResource = imagecreatetruecolor ( ARTICLE_THUMB_WIDTH, $thumbHeight ); imagecopyresampled( $thumbResource, $imageResource, 0, 0, 0, 0, ARTICLE_THUMB_WIDTH, $thumbHeight, $imageWidth, $imageHeight ); // Save the thumbnail switch ( $imageType ) { case IMAGETYPE_GIF: imagegif ( $thumbResource, $this->getImagePath( IMG_TYPE_THUMB ) ); break; case IMAGETYPE_JPEG: imagejpeg ( $thumbResource, $this->getImagePath( IMG_TYPE_THUMB ), JPEG_QUALITY ); interruption; case IMAGETYPE_PNG: imagepng ( $thumbResource, $this->getImagePath( IMG_TYPE_THUMB ) ); pause; default: trigger_error ( "Article::storeUploadedImage(): Unhandled or unknown image blazon ($imageType)", E_USER_ERROR ); } $this->update(); } } /** * Deletes any images and/or thumbnails associated with the commodity */ public function deleteImages() { // Delete all fullsize images for this article foreach (glob( ARTICLE_IMAGE_PATH . "/" . IMG_TYPE_FULLSIZE . "/" . $this->id . ".*") equally $filename) { if ( !unlink( $filename ) ) trigger_error( "Article::deleteImages(): Couldn't delete image file.", E_USER_ERROR ); } // Delete all thumbnail images for this article foreach (glob( ARTICLE_IMAGE_PATH . "/" . IMG_TYPE_THUMB . "/" . $this->id . ".*") as $filename) { if ( !unlink( $filename ) ) trigger_error( "Article::deleteImages(): Couldn't delete thumbnail file.", E_USER_ERROR ); } // Remove the image filename extension from the object $this->imageExtension = ""; } /** * Returns the relative path to the article's full-size or thumbnail image * * @param string The type of image path to call up (IMG_TYPE_FULLSIZE or IMG_TYPE_THUMB). Defaults to IMG_TYPE_FULLSIZE. * @return cord|false The image's path, or faux if an prototype hasn't been uploaded */ public role getImagePath( $type=IMG_TYPE_FULLSIZE ) { return ( $this->id && $this->imageExtension ) ? ( ARTICLE_IMAGE_PATH . "/$blazon/" . $this->id . $this->imageExtension ) : faux; } /** * Returns an Article object matching the given article ID * * @param int The article ID * @render Article|imitation The article object, or false if the record was not constitute or there was a problem */ public static role getById( $id ) { $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD ); $sql = "SELECT *, UNIX_TIMESTAMP(publicationDate) AS publicationDate FROM manufactures WHERE id = :id"; $st = $conn->prepare( $sql ); $st->bindValue( ":id", $id, PDO::PARAM_INT ); $st->execute(); $row = $st->fetch(); $conn = null; if ( $row ) return new Article( $row ); } /** * Returns all (or a range of) Article objects in the DB * * @param int Optional The number of rows to return (default=all) * @render Array|fake A two-element array : results => assortment, a list of Commodity objects; totalRows => Full number of manufactures */ public static office getList( $numRows=1000000 ) { $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD ); $sql = "SELECT SQL_CALC_FOUND_ROWS *, UNIX_TIMESTAMP(publicationDate) As publicationDate FROM articles Club Past publicationDate DESC LIMIT :numRows"; $st = $conn->prepare( $sql ); $st->bindValue( ":numRows", $numRows, PDO::PARAM_INT ); $st->execute(); $list = array(); while ( $row = $st->fetch() ) { $article = new Article( $row ); $list[] = $article; } // Now get the full number of articles that matched the criteria $sql = "SELECT FOUND_ROWS() AS totalRows"; $totalRows = $conn->query( $sql )->fetch(); $conn = null; render ( array ( "results" => $list, "totalRows" => $totalRows[0] ) ); } /** * Inserts the current Article object into the database, and sets its ID belongings. */ public function insert() { // Does the Article object already have an ID? if ( !is_null( $this->id ) ) trigger_error ( "Article::insert(): Try to insert an Article object that already has its ID property gear up (to $this->id).", E_USER_ERROR ); // Insert the Article $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD ); $sql = "INSERT INTO manufactures ( publicationDate, title, summary, content, imageExtension ) VALUES ( FROM_UNIXTIME(:publicationDate), :championship, :summary, :content, :imageExtension )"; $st = $conn->prepare ( $sql ); $st->bindValue( ":publicationDate", $this->publicationDate, PDO::PARAM_INT ); $st->bindValue( ":title", $this->championship, PDO::PARAM_STR ); $st->bindValue( ":summary", $this->summary, PDO::PARAM_STR ); $st->bindValue( ":content", $this->content, PDO::PARAM_STR ); $st->bindValue( ":imageExtension", $this->imageExtension, PDO::PARAM_STR ); $st->execute(); $this->id = $conn->lastInsertId(); $conn = null; } /** * Updates the current Commodity object in the database. */ public part update() { // Does the Article object take an ID? if ( is_null( $this->id ) ) trigger_error ( "Article::update(): Attempt to update an Article object that does not have its ID property gear up.", E_USER_ERROR ); // Update the Article $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD ); $sql = "UPDATE articles SET publicationDate=FROM_UNIXTIME(:publicationDate), title=:title, summary=:summary, content=:content, imageExtension=:imageExtension WHERE id = :id"; $st = $conn->prepare ( $sql ); $st->bindValue( ":publicationDate", $this->publicationDate, PDO::PARAM_INT ); $st->bindValue( ":championship", $this->title, PDO::PARAM_STR ); $st->bindValue( ":summary", $this->summary, PDO::PARAM_STR ); $st->bindValue( ":content", $this->content, PDO::PARAM_STR ); $st->bindValue( ":imageExtension", $this->imageExtension, PDO::PARAM_STR ); $st->bindValue( ":id", $this->id, PDO::PARAM_INT ); $st->execute(); $conn = null; } /** * Deletes the current Article object from the database. */ public role delete() { // Does the Commodity object have an ID? if ( is_null( $this->id ) ) trigger_error ( "Article::delete(): Effort to delete an Article object that does not have its ID property gear up.", E_USER_ERROR ); // Delete the Commodity $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD ); $st = $conn->gear up ( "DELETE FROM articles WHERE id = :id LIMIT ane" ); $st->bindValue( ":id", $this->id, PDO::PARAM_INT ); $st->execute(); $conn = null; } } ?>
Here's a list of the changes nosotros've made to the Article
course:
A new $imageExtension
property
This corresponds to the imageExtension
field you added to the articles
table in Step 3. This property is used to shop the filename extension for the commodity's full-size and thumbnail images — for example, ".jpg"
or ".png"
.
We also modified the constructor method, __construct()
, to store the new $imageExtension
property value in newly-created Article
objects.
A new storeUploadedImage()
method
We'll call this method from the admin.php
script whenever the user uploads a new commodity image using the article edit class. Its job is to move the uploaded paradigm to the fullsize
images folder we created in Step 1, also equally generate a thumbnail version of the paradigm and store it in the thumb
binder.
The method accepts a single $image
parameter. This should be the element in the PHP $_FILES
superglobal assortment that contains all the information most the uploaded epitome file.
Here's how the storeUploadedImage()
method works:
- Check for an upload error
The kickoff thing the method does is check that the'error'
element in the$image
array equals the constantUPLOAD_ERR_OK
. This indicates that the user uploaded an prototype, and that the upload was successful. If the upload went OK then the method starts to process the uploaded image; otherwise it does cypher. - Does the
Commodity
object accept an ID?
Assuming the file was uploaded OK, the method so makes sure that theArticle
object has an ID; in other words, that it has been saved to the database. This is important, considering nosotros're going to rename the epitome file using the article'south ID in a moment, so that nosotros can hands associate the image with the article. If the article doesn't have an ID and so the method callstrigger_error()
to display an fault message and leave. - Delete any previous paradigm(s) for this article
Adjacent the method calls theCommodity::deleteImages()
method to delete whatsoever existing full-size and thumbnail image files associated with the article. (We'll write this method in a moment.) Nosotros do this in society to keep the paradigm folders clean, without whatsoever one-time, unused article images lying about. For example, if the article currently has a.png
image uploaded, and the user uploads a new.jpg
image, nosotros desire to make sure that the now-unused.png
images are deleted from the folders. - Get and store the image filename extension
As you saw before, the$imageExtension
property needs to shop the filename extension of the article image. Here the method uses thestrrchr()
function to extract the filename extension — that is, everything after (and including) the final dot in the filename — and stores the consequence in$imageExtension
, converted to lowercase withstrtolower()
for consistency. - Store the image
At present the method moves the actual uploaded image into theimages/manufactures/fullsize
folder. To do this, it first retrieves the path to the uploaded file from the$_FILES['fieldname']['tmp_name']
array element and stores it in$tempFilename
. Typically this value is the path to the uploaded file in the server's temporary folder, such equally/tmp/
.Then the method calls
is_uploaded_file()
to check that the file in the temporary folder is indeed a file uploaded by PHP. This is a good security precaution to prevent sensitive system files accidentally beingness fabricated public.Finally, the method calls the
move_uploaded_file()
function to motion the uploaded paradigm from the temporary binder to theimages/articles/fullsize
folder. This part takes two arguments: the path to the file in the temporary folder, and the path to move the file to. Information technology'due south a good idea to usemove_uploaded_file()
to move uploaded files, since the function performs additional security checks on the file before moving it.Once the file's in place, the method calls the
chmod()
function to set the file'southward permissions to 0666. This ensures that the file can be read and written by anyone, including the spider web server user and whatever FTP user that may need to change or delete the article images. (As always, if you're on a shared web server so you might want to use more restrictive permissions than this.) - Go the image size and blazon
The next job forstoreUploadedImage()
is to create the smaller thumbnail version of the uploaded image. Get-go it callsgetimagesize()
, paassing in the path to the uploaded image, in order to get the prototype'southward width, meridian and format (GIF, JPEG or PNG), which it so stores in$imageWidth
,$imageHeight
and$imageType
respectively. - Load the image into retentiveness
Now that the method knows the type of image it's dealing with, information technology callsimagecreatefromgif()
,imagecreatefromjpeg()
orimagecreatefrompng()
every bit appropriate to load the prototype into an image resource variable,$imageResource
, for processing. - Copy and resize the image to create the thumbnail
Now it's time to create the thumbnail image. To do this, the method starting time computes the thumbnail top,$thumbHeight
, based on the total-size image height ($imageHeight
), the full-size epitome width ($imageWidth
), and the desired thumbnail width (ARTICLE_THUMB_WIDTH
).Adjacent it calls
imagecreatetruecolor()
to create a blank image resource for storing the thumbnail prototype data, passing in the width and top of the image to create. Information technology stores this resource in a$thumbResource
variable.Finally, it calls
imagecopyresampled()
to create the smaller version of the uploaded image and store the effect in the$thumbResource
variable. It passes the following arguments toimagecopyresampled()
:- The image resource to store the resized prototype in (
$thumbResource
) - The uploaded image resource (
$imageResource
) - The (x,y) coordinates of top-left corner of the rectangle in
$thumbResource
to copy the image information to (0,0
— that is, the acme left corner of the thumbnail) - The (ten,y) coordinates of pinnacle-left corner of the rectangle in
$imageResource
to copy the image data from (0,0
— that is, the elevation left corner of the uploaded image) - The width and peak of the rectangle in
$thumbResource
to copy the image data to (ARTICLE_THUMB_WIDTH
and$thumbHeight
— that is, the unabridged width and height of the thumbnail) - The width and elevation of the rectangle in
$imageResource
to copy the image data from ($imageWidth
and$imageHeight
— that is, the entire width and tiptop of the uploaded image)
- The image resource to store the resized prototype in (
- Salve the thumbnail
Now that the method has created the thumbnail image data and stored it in$thumbResource
, information technology needs to write the new thumbnail image to disk. To practice this, it callsimagegif()
,imagejpeg()
orimagepng()
, depending on the image type. It passes in both$thumbResource
and the path to use for the thumbnail prototype. To get the path, it calls thegetImagePath()
method (which we'll await at in a moment), passing in ourIMG_TYPE_THUMB
constant to indicate that we want the path to the thumbnail. - Update the commodity record
Finally, since theArticle
object'due south$imageExtension
property may well take changed as a result of uploading the paradigm, the method calls$this->update()
to update the commodity tape in the database.
A new deleteImages()
method
The deleteImages()
method is responsible for clearing out any image files associated with the current article. It'due south called by storeUploadedImage()
before uploading a new image (as you saw in the previous department). In addition, it's called if the ambassador specifically asks to delete the article's image and thumbnail via the Edit Commodity grade. Finally, deleteImages()
is likewise chosen when the article itself needs to be deleted.
deleteImages()
calls PHP's glob()
function to retrieve a listing of all image files in both the images/articles/fullsize
and images/articles/thumb
folders that are named after the article's ID. For instance, if the article'south ID is 3, the call to glob()
volition return any prototype files called "iii.gif"
, "iii.jpg"
or "3.png"
.
For each filename in the array returned past glob()
, the method attempts to delete the file by calling PHP's unlink()
role. If in that location's a trouble deleting the file and then it raises an error and exits.
Once all the image files have been deleted, deleteImages()
sets the Article
object's $imageExtension
property to an empty string (""
) to indicate that the article no longer has an uploaded image.
A new getImagePath()
method
The terminal new method we've added to the Article
course is getImagePath()
, which returns the path to one of the 2 article images.
The method takes a single, optional statement, $type
, that indicates whether it should return the path to the total-size paradigm (IMG_TYPE_FULLSIZE
, the default), or the thumbnail (IMG_TYPE_THUMB
). It so uses the commodity's ID, along with the value stored in the article's $imageExtension
property, to compute the path to the prototype file inside the images/manufactures/fullsize
or images/articles/thumb
folder.
For example, if getImagePath()
is passed IMG_TYPE_THUMB
as an statement, the commodity's ID is 3, and its $imageExtension
property contains ".jpg"
, so the method will return the value "images/articles/thumb/3.jpg"
.
Changes to the insert()
and update()
methods
The final modifications to Article.php
are inside the insert()
and update()
methods toward the end of the file. As you can see, nosotros've modified the SQL INSERT
and UPDATE
statements to adapt the new $imageExtension
property so that the image extension is stored in the articles
tabular array. Nosotros've too added extra bindValue()
calls to pass the belongings'southward value to the SQL statements.
Step v: Alter the admin.php
script
We at present need to make some changes to admin.php
, the back-end admin script, so that it can handle paradigm uploads. Fortunately, we've already washed most of the hard piece of work in our Article
class, and then there aren't many changes that we need to make to this script.
Here's the modified admin.php
file with the changes highlighted. Supplant the code in your existing cms/admin.php
file with this code:
<?php require( "config.php" ); session_start(); $action = isset( $_GET['action'] ) ? $_GET['activity'] : ""; $username = isset( $_SESSION['username'] ) ? $_SESSION['username'] : ""; if ( $activeness != "login" && $action != "logout" && !$username ) { login(); exit; } switch ( $action ) { case 'login': login(); break; case 'logout': logout(); suspension; example 'newArticle': newArticle(); break; case 'editArticle': editArticle(); suspension; case 'deleteArticle': deleteArticle(); break; default: listArticles(); } role login() { $results = array(); $results['pageTitle'] = "Admin Login | Widget News"; if ( isset( $_POST['login'] ) ) { // User has posted the login form: attempt to log the user in if ( $_POST['username'] == ADMIN_USERNAME && $_POST['password'] == ADMIN_PASSWORD ) { // Login successful: Create a session and redirect to the admin homepage $_SESSION['username'] = ADMIN_USERNAME; header( "Location: admin.php" ); } else { // Login failed: display an error bulletin to the user $results['errorMessage'] = "Incorrect username or password. Delight endeavour again."; crave( TEMPLATE_PATH . "/admin/loginForm.php" ); } } else { // User has not posted the login form yet: brandish the form require( TEMPLATE_PATH . "/admin/loginForm.php" ); } } part logout() { unset( $_SESSION['username'] ); header( "Location: admin.php" ); } function newArticle() { $results = array(); $results['pageTitle'] = "New Commodity"; $results['formAction'] = "newArticle"; if ( isset( $_POST['saveChanges'] ) ) { // User has posted the article edit form: save the new article $article = new Article; $commodity->storeFormValues( $_POST ); $article->insert(); if ( isset( $_FILES['epitome'] ) ) $commodity->storeUploadedImage( $_FILES['image'] ); header( "Location: admin.php?status=changesSaved" ); } elseif ( isset( $_POST['abolish'] ) ) { // User has cancelled their edits: return to the article list header( "Location: admin.php" ); } else { // User has not posted the article edit class all the same: display the form $results['article'] = new Article; crave( TEMPLATE_PATH . "/admin/editArticle.php" ); } } function editArticle() { $results = array(); $results['pageTitle'] = "Edit Commodity"; $results['formAction'] = "editArticle"; if ( isset( $_POST['saveChanges'] ) ) { // User has posted the article edit form: salve the article changes if ( !$article = Article::getById( (int)$_POST['articleId'] ) ) { header( "Location: admin.php?error=articleNotFound" ); return; } $article->storeFormValues( $_POST ); if ( isset($_POST['deleteImage']) && $_POST['deleteImage'] == "yeah" ) $commodity->deleteImages(); $commodity->update(); if ( isset( $_FILES['image'] ) ) $article->storeUploadedImage( $_FILES['image'] ); header( "Location: admin.php?status=changesSaved" ); } elseif ( isset( $_POST['cancel'] ) ) { // User has cancelled their edits: return to the article list header( "Location: admin.php" ); } else { // User has not posted the article edit grade yet: brandish the form $results['article'] = Article::getById( (int)$_GET['articleId'] ); require( TEMPLATE_PATH . "/admin/editArticle.php" ); } } function deleteArticle() { if ( !$commodity = Article::getById( (int)$_GET['articleId'] ) ) { header( "Location: admin.php?error=articleNotFound" ); return; } $article->deleteImages(); $commodity->delete(); header( "Location: admin.php?status=articleDeleted" ); } function listArticles() { $results = array(); $data = Article::getList(); $results['articles'] = $information['results']; $results['totalRows'] = $data['totalRows']; $results['pageTitle'] = "All Articles"; if ( isset( $_GET['error'] ) ) { if ( $_GET['error'] == "articleNotFound" ) $results['errorMessage'] = "Error: Article not found."; } if ( isset( $_GET['status'] ) ) { if ( $_GET['status'] == "changesSaved" ) $results['statusMessage'] = "Your changes have been saved."; if ( $_GET['status'] == "articleDeleted" ) $results['statusMessage'] = "Article deleted."; } require( TEMPLATE_PATH . "/admin/listArticles.php" ); } ?>
Let'south work through these changes to admin.php
:
- Changes to
newArticle()
We've added a single line of code to thenewArticle()
function to handle image uploads. It checks that the'image'
element exists in the$_FILES
assortment and, if it does exist, it calls theCommodity
object'sstoreUploadedImage()
method, passing in the$_FILES['prototype']
chemical element, to shop the prototype and create the thumbnail. - Changes to
editArticle()
Every bit withnewArticle()
, we've added a line of code that checks for an uploaded prototype and calls$article->storeUploadedImage()
to store it and create the thumbnail. We've too added a line of code that checks if the user selected the "delete image" checkbox in the Edit Article class. If they did then we call$article->deleteImages()
to delete whatsoever existing images associated with the article. - Changes to
deleteArticle()
Finally, nosotros've added a single line of code to thedeleteArticle()
function that calls$commodity->deleteImages()
. This ensures that any images associated with the article besides go deleted.
Step 6: Alter the front-end templates
Our database and PHP code tin can now handle article images, merely we need to make some changes both to the front-stop templates that visitors meet, and the dorsum-stop admin templates.
Let'south showtime by altering the front-end templates to display the article images.
one. homepage.php
The homepage.php
template displays the site'southward abode page, including a listing of recent articles. We'll modify this template to display each article'south thumbnail next to the article in the list.
Hither'southward the modified file with the new lines highlighted — supplant your old cms/templates/homepage.php
file with this code:
<?php include "templates/include/header.php" ?> <ul id="headlines"> <?php foreach ( $results['articles'] as $article ) { ?> <li> <h2> <span class="pubDate"><?php echo date('j F', $article->publicationDate)?></span><a href=".?action=viewArticle&articleId=<?php repeat $article->id?>"><?php echo htmlspecialchars( $article->title )?></a> </h2> <p class="summary"> <?php if ( $imagePath = $article->getImagePath( IMG_TYPE_THUMB ) ) { ?> <a href=".?action=viewArticle&articleId=<?php repeat $article->id?>"><img course="articleImageThumb" src="<?php echo $imagePath?>" alt="Article Thumbnail" /></a> <?php } ?> <?php echo htmlspecialchars( $article->summary )?> </p> </li> <?php } ?> </ul> <p><a href="./?action=archive">Commodity Annal</a></p> <?php include "templates/include/footer.php" ?>
Here we've added some code inside the "summary" paragraph for each article. The lawmaking calls the article'southward getImagePath()
method, passing in IMG_TYPE_THUMB
to betoken that nosotros desire the path to the article'due south thumbnail. It then stores the path in the $imagePath
variable. If this path is a non-false
value and then the commodity has a thumbnail image, so the code then constructs a link to view the article, wrapped around an img
element that contains the thumbnail's path. We've added an articleImageThumb
grade to the thumbnail image so that we can style it in the stylesheet.
If $imagePath
's value is false
then the article doesn't have a thumbnail, so no markup is constructed.
two. archive.php
archive.php
displays the article annal page — that is, a list of all the articles in the database. Nosotros need to modify information technology in the aforementioned way equally homepage.php
, so that it displays thumbnails side by side to the article summaries.
Hither'due south the modified archive.php
file — replace your old cms/templates/annal.php
file with this one:
<?php include "templates/include/header.php" ?> <h1>Commodity Archive</h1> <ul id="headlines" grade="archive"> <?php foreach ( $results['manufactures'] as $article ) { ?> <li> <h2> <span class="pubDate"><?php repeat engagement('j F Y', $article->publicationDate)?></span><a href=".?action=viewArticle&articleId=<?php echo $commodity->id?>"><?php echo htmlspecialchars( $commodity->championship )?></a> </h2> <p course="summary"> <?php if ( $imagePath = $commodity->getImagePath( IMG_TYPE_THUMB ) ) { ?> <a href=".?action=viewArticle&articleId=<?php repeat $article->id?>"><img class="articleImageThumb" src="<?php repeat $imagePath?>" alt="Article Thumbnail" /></a> <?php } ?> <?php echo htmlspecialchars( $commodity->summary )?> </p> </li> <?php } ?> </ul> <p><?php echo $results['totalRows']?> article<?php repeat ( $results['totalRows'] != 1 ) ? 'due south' : '' ?> in full.</p> <p><a href="./">Return to Homepage</a></p> <?php include "templates/include/footer.php" ?>
As y'all can see, this is the same change that we made to homepage.php
. If an commodity has an image, its thumbnail volition now announced next to the article summary in the archive page.
3. viewArticle.php
The viewArticle.php
template displays an individual article page, containing the commodity's headline, summary and content. Merely as we modified homepage.php
and archive.php
to display thumbnails next to the article summaries, we also demand to heighten viewArticle.php
so that it displays the total-size article images in the article pages.
Here's the changed template. Equally always, I've highlighted the new code. Save this code over your quondam cms/templates/viewArticle.php
file:
<?php include "templates/include/header.php" ?> <h1 style="width: 75%;"><?php echo htmlspecialchars( $results['article']->championship )?></h1> <div style="width: 75%; font-style: italic;"><?php echo htmlspecialchars( $results['article']->summary )?></div> <div style="width: 75%; min-height: 300px;"> <?php if ( $imagePath = $results['article']->getImagePath() ) { ?> <img id="articleImageFullsize" src="<?php echo $imagePath?>" alt="Article Image" /> <?php } ?> <?php repeat $results['article']->content?> </div> <p course="pubDate">Published on <?php echo appointment('j F Y', $results['article']->publicationDate)?></p> <p><a href="./">Return to Homepage</a></p> <?php include "templates/include/footer.php" ?>
This new code works in essentially the same fashion as the code added to the homepage and archive templates. It calls the article's getImagePath()
method to go the path to the total-size article paradigm. If the path isn't false
then the commodity has an image, and the lawmaking inserts the advisable img
element into the markup. The img
element is given an id
of articleImageFullsize
so that we tin can style it using CSS.
Footstep 7: Change the back-stop templates
There's actually merely one dorsum-end admin template that we need to change, and that'southward the editArticle.php
article edit form. Here'due south the new template with changes highlighted — save information technology over your existing cms/templates/admin/editArticle.php
file:
<?php include "templates/include/header.php" ?> <script> // Prevents file upload hangs in Mac Safari // Inspired by http://airbladesoftware.com/notes/note-to-cocky-preclude-uploads-hanging-in-safari function closeKeepAlive() { if ( /AppleWebKit|MSIE/.test( navigator.userAgent) ) { var xhr = new XMLHttpRequest(); xhr.open( "Go", "/ping/shut", false ); xhr.send(); } } </script> <div id="adminHeader"> <h2>Widget News Admin</h2> <p>You are logged in equally <b><?php echo htmlspecialchars( $_SESSION['username']) ?></b>. <a href="admin.php?activeness=logout"?>Log out</a></p> </div> <h1><?php echo $results['pageTitle']?></h1> <form action="admin.php?activeness=<?php repeat $results['formAction']?>" method="post" enctype="multipart/form-data" onsubmit="closeKeepAlive()"> <input type="hidden" name="articleId" value="<?php echo $results['article']->id ?>"/> <?php if ( isset( $results['errorMessage'] ) ) { ?> <div form="errorMessage"><?php echo $results['errorMessage'] ?></div> <?php } ?> <ul> <li> <label for="title">Article Title</characterization> <input blazon="text" proper noun="title" id="title" placeholder="Name of the article" required autofocus maxlength="255" value="<?php echo htmlspecialchars( $results['article']->title )?>" /> </li> <li> <label for="summary">Commodity Summary</label> <textarea name="summary" id="summary" placeholder="Brief description of the article" required maxlength="chiliad" style="top: 5em;"><?php repeat htmlspecialchars( $results['commodity']->summary )?></textarea> </li> <li> <label for="content">Commodity Content</label> <textarea name="content" id="content" placeholder="The HTML content of the commodity" required maxlength="100000" style="peak: 30em;"><?php repeat htmlspecialchars( $results['commodity']->content )?></textarea> </li> <li> <label for="publicationDate">Publication Appointment</label> <input type="date" proper name="publicationDate" id="publicationDate" placeholder="YYYY-MM-DD" required maxlength="10" value="<?php repeat $results['article']->publicationDate ? date( "Y-m-d", $results['article']->publicationDate ) : "" ?>" /> </li> <?php if ( $results['article'] && $imagePath = $results['article']->getImagePath() ) { ?> <li> <label>Current Epitome</label> <img id="articleImage" src="<?php echo $imagePath ?>" alt="Article Image" /> </li> <li> <label></label> <input type="checkbox" name="deleteImage" id="deleteImage" value="yes"/ > <label for="deleteImage">Delete</label> </li> <?php } ?> <li> <label for="epitome">New Epitome</label> <input type="file" proper name="image" id="paradigm" placeholder="Choose an epitome to upload" maxlength="255" /> </li> </ul> <div class="buttons"> <input type="submit" name="saveChanges" value="Save Changes" /> <input blazon="submit" formnovalidate proper noun="cancel" value="Cancel" /> </div> </form> <?php if ( $results['article']->id ) { ?> <p><a href="admin.php?action=deleteArticle&articleId=<?php repeat $results['commodity']->id ?>" onclick="render ostend('Delete This Article?')">Delete This Article</a></p> <?php } ?> <?php include "templates/include/footer.php" ?>
Permit'due south take a wait at each of these changes in turn:
- The
closeKeepAlive()
JavaScript function
For some reason, Safari on the Mac has suffered from a long-standing issue whereby file uploads hang occasionally. (Detect out more near this result hither and here.) Since this is quite abrasive if you lot employ Safari (equally I do), I've added this fiddling function that makes an Ajax request to a non-existent URL on the server, forcing Safari to close its connection to the server. This seems to fix the problem. My function is similar to this script, except that my part doesn't require the Paradigm.js library. - Changes to the
<class>
tag
Nosotros've added the attributeenctype="multipart/form-data"
to theform
element. This attribute is required whenever you create a form containing a file upload field. It lets the browser know that it needs to encode the form data as a multipart MIME stream containing different media types (in this case, text and image data).We've likewise attached the
closeKeepAlive()
role as asubmit
event handler to the form, so that the function runs when the form is submitted. - The article image and "delete" checkbox
The next addition to the class displays the full-size paradigm currently associated with the article (if whatever). Every bit with the front-stop templates, the PHP code calls the article'sgetImagePath()
method to remember the prototype's path. If an image path was returned, we add anli
chemical element to the page containing a field label ("Current Image"), forth with animg
element linking to the image. We also include anotherli
element containing adeleteImage
checkbox. This lets the administrator delete any image(s) currently associated with the article. - The image upload field
Last, simply by no means least, we add the<input type="file">
upload field that allows the administrator to upload an image for this article. We requite it aname
aspect of"image"
, which means that we're able to access the uploaded file from our PHP lawmaking using$_FILES['image']
(see Step 4 earlier in the tutorial).
Stride viii: Change the stylesheet
Next we'll make some additions and changes to our CMS's stylesheet, way.css
, in society to style the commodity images and thumbnails on the front end, as well equally the new elements in the Edit Article form.
Hither's the new way.css
file with the changes highlighted. Supervene upon your existing fashion.css
file in your cms
binder with this file:
/* Mode the body and outer container */ body { margin: 0; color: #333; background-color: #00a0b0; font-family unit: "Trebuchet MS", Arial, Helvetica, sans-serif; line-elevation: i.5em; } #container { width: 960px; background: #fff; margin: 20px machine; padding: 20px; -moz-border-radius: 5px; -webkit-border-radius: 5px; edge-radius: 5px; } /* The logo and footer */ #logo { display: cake; width: 300px; padding: 0 660px 20px 0; edge: none; edge-lesser: 1px solid #00a0b0; margin-lesser: 40px; } #footer { border-summit: 1px solid #00a0b0; margin-top: 40px; padding: 20px 0 0 0; font-size: .8em; } /* Headings */ h1 { color: #eb6841; margin-bottom: 30px; line-height: 1.2em; } h2, h2 a { color: #edc951; } h2 a { text-decoration: none; } /* Commodity headlines */ #headlines { list-way: none; padding-left: 0; width: 75%; } #headlines li { margin-bottom: 2em; overflow: hidden; } .pubDate { font-size: .8em; color: #eb6841; text-transform: upper-case letter; } #headlines .pubDate { display: inline-cake; width: 100px; font-size: .5em; vertical-align: middle; } #headlines.archive .pubDate { width: 130px; } #headlines .articleImageThumb { width: 120px; bladder: left; margin: 4px 20px 0 0; edge: 1px solid #00a0b0; } .summary { padding-left: 100px; } #headlines.annal .summary { padding-left: 130px; } /* Commodity pages */ #articleImageFullsize { width: 250px; float: left; margin: 4px 20px 10px 0; border: 1px solid #00a0b0; } /* "You are logged in..." header on admin pages */ #adminHeader { width: 940px; padding: 0 10px; border-lesser: 1px solid #00a0b0; margin: -30px 0 40px 0; font-size: 0.8em; } /* Style the grade with a coloured groundwork, forth with curved corners and a drop shadow */ form { margin: 20px auto; padding: 40px 20px; overflow: car; background: #fff4cf; border: 1px solid #666; -moz-border-radius: 5px; -webkit-edge-radius: 5px; border-radius: 5px; -moz-box-shadow: 0 0 .5em rgba(0, 0, 0, .8); -webkit-box-shadow: 0 0 .5em rgba(0, 0, 0, .8); box-shadow: 0 0 .5em rgba(0, 0, 0, .8); } /* Give course elements consistent margin, padding and line height */ form ul { list-style: none; margin: 0; padding: 0; overflow: subconscious; } course ul li { margin: .9em 0 0 0; padding: 0; } form * { line-height: 1em; } /* The field labels */ label { brandish: block; float: left; articulate: left; text-align: right; width: xv%; padding: .4em 0 0 0; margin: .15em .5em 0 0; } /* The fields */ input, select, textarea { display: block; margin: 0; padding: .4em; width: 80%; } input, textarea, .engagement { border: 2px solid #666; -moz-edge-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; background: #fff; } input { font-size: .9em; } input[type="checkbox"] { brandish: inline-block; padding: 0; margin: 0 0 .8em 0; width: auto; } select { padding: 0; margin-bottom: 2.5em; position: relative; top: .7em; } textarea { font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; font-size: .9em; height: 5em; line-height: one.5em; } textarea#content { font-family: "Courier New", courier, stock-still; } #articleImage { edge: 2px solid #666; } #deleteImage { clear: both; } label[for="deleteImage"] { bladder: none; display: inline; } input[type="file"] { float: left; } /* Identify a edge around focused fields */ form *:focus { border: 2px solid #7c412b; outline: none; } /* Display correctly filled-in fields with a dark-green groundwork */ input:valid, textarea:valid { groundwork: #efe; } /* Submit buttons */ .buttons { text-marshal: middle; margin: 40px 0 0 0; } input[type="submit"] { display: inline; margin: 0 20px; width: 12em; padding: 10px; border: 2px solid #7c412b; -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; -moz-box-shadow: 0 0 .5em rgba(0, 0, 0, .8); -webkit-box-shadow: 0 0 .5em rgba(0, 0, 0, .8); box-shadow: 0 0 .5em rgba(0, 0, 0, .8); colour: #fff; background: #ef7d50; font-weight: bold; -webkit-appearance: none; } input[type="submit"]:hover, input[type="submit"]:active { cursor: pointer; groundwork: #fff; colour: #ef7d50; } input[type="submit"]:active { background: #eee; -moz-box-shadow: 0 0 .5em rgba(0, 0, 0, .8) inset; -webkit-box-shadow: 0 0 .5em rgba(0, 0, 0, .8) inset; box-shadow: 0 0 .5em rgba(0, 0, 0, .8) inset; } /* Tables */ tabular array { width: 100%; border-plummet: collapse; } tr, th, td { padding: 10px; margin: 0; text-marshal: left; } table, th { border: 1px solid #00a0b0; } thursday { border-left: none; edge-correct: none; background: #ef7d50; color: #fff; cursor: default; } tr:nth-child(odd) { background: #fff4cf; } tr:nth-child(even) { groundwork: #fff; } tr:hover { groundwork: #ddd; cursor: arrow; } /* Status and fault boxes */ .statusMessage, .errorMessage { font-size: .8em; padding: .5em; margin: 2em 0; -moz-edge-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; -moz-box-shadow: 0 0 .5em rgba(0, 0, 0, .viii); -webkit-box-shadow: 0 0 .5em rgba(0, 0, 0, .8); -box-shadow: 0 0 .5em rgba(0, 0, 0, .8); } .statusMessage { background-color: #2b2; border: 1px solid #080; color: #fff; } .errorMessage { groundwork-color: #f22; border: 1px solid #800; color: #fff; }
Equally you can see, we've added various rulesets to manner the thumbnails in the homepage and annal pages; the full-size images in commodity pages; and the "delete image" checkbox, article image and file upload field inside the "Edit Article" form (which you added in Step 7).
On line 89, we've set the thumbnail width to 120 pixels to friction match the ARTICLE_THUMB_WIDTH
setting in config.php
. Similarly, on line 107 nosotros've ready the width of the full-size images in the article pages to 250 pixels. If yous want to employ dissimilar widths for the thumbnails and images so yous demand to modify these two values, as well as the ARTICLE_THUMB_WIDTH
setting.
Try it out!
Slap-up stuff! You now take a CMS that tin can handle prototype uploads. To test your new CMS, follow these steps:
- Log in
Open your browser and visit the base URL of your CMS — for case,http://localhost/cms/
. Click the Site Admin link in the footer, and log in. - Upload some images
Click an article in the All Articles listing, or add together a new article by clicking the Add a New Article link at the bottom of the folio. In the New Commodity / Edit Article grade, click the push button next to the New Paradigm label at the bottom of the form. Choose a file to upload, and then click Save Changes to save your commodity edits and upload the paradigm. - View your images
Click the Widget News logo at the superlative of the page to view the site. You should meet thumbnail images next to the manufactures in the list. If y'all click the Article Archive link at the bottom of the page then you should too run into the thumbnails there. Click a thumbnail (or article headline) to view the total commodity page, along with the full-size article prototype. - Changing and deleting images
Only to brand sure everything works, try editing an commodity with an image and uploading a new image. This should then supplant the previous article prototype and thumbnail. (Depending on your server and browser setup, you may demand to clear your browser's enshroud and reload the page to see the new image.) You lot can likewise attempt clicking the Delete checkbox to remove an existing image from an article.
Y'all tin can too try out the demo on our server too! The demo is read-but, so y'all can't upload or delete images, but you can see how the images expect on the front end-finish, likewise as in the back-end article edit class.
Summary
In this tutorial nosotros've added an image upload characteristic to the content direction system from my original tutorial. Here's what we did:
- Created some folders within your CMS to store the article images and thumbnails
- Added some constants to the config file to specify the path to the images binder, the width to use for commodity thumbnails, and other useful settings
- Modified the MySQL database to add an
imageExtension
field, which tracks the filename extension of the image uploaded for each article - Modified the
Commodity
class to add the$imageExtension
property, besides as methods to handle image uploads, image deletion, and retrieving image paths - Extended the
admin.php
admin script to let uploading and deleting of article images - Contradistinct the front-end templates,
homepage.php
,archive.php
andviewArticle.php
, to display the article thumbnails and full-size images equally appropriate - Enhanced the commodity edit grade,
editArticle.php
, to include the prototype upload field, every bit well as the currently-uploaded prototype and a "delete" image" checkbox, and - Tweaked the stylesheet,
manner.css
, to style the article images and thumbnails, every bit well as the article edit form elements.
Yous tin can now utilize your CMS to publish articles illustrated with images. At present all yous need to exercise is create some beautiful images! Savor. 🙂
[Flickr photo credits for sample images: Man of affairs, Cogs, Willy Wonka, Newspapers, Dollars.]
Source: https://www.elated.com/add-image-uploading-to-your-cms/
0 Response to "Prevent Anytthing but Img Being Uploaded Php"
Post a Comment