Metatag Warning: Be Sure to Upload Your Image to Your Cms or Host.
17 May 2019: This commodity and the lawmaking were updated for PHP7 compatibility.
In my tutorial Build a CMS in an Afternoon with PHP and MySQL, I showed how to build a simple simply useful content direction system with PHP and MySQL. I likewise showed how to extend the CMS to allow article categories.
In this tutorial, yous'll expect at another way to extend the CMS. You'll take the original CMS code, and change it so that the administrator can upload an epitome for each article. So, when a visitor views an article page, the CMS volition display the image at the start of the article. In addition, our CMS will generate a smaller thumbnail version of each commodity image, and display this thumbnail next to each commodity headline in the homepage and article archive pages.
Yous can see the finished result by clicking the View Demo link higher up. Discover the thumbnail images next to each article headline. Click a headline or thumbnail to view the respective article, along with the full-size article image.
The plan
Nosotros'll kickoff with the original CMS lawmaking from Build a CMS in an Afternoon with PHP and MySQL, and modify it to include the prototype upload characteristic. Here are the steps we'll demand to acquit out:
- Create a couple of folders to store the article images
- Alter various paradigm-related settings to the CMS config file
- Modify the database to store the image filename extensions
- Modify the
Article
class to handle images - Change
admin.php
to handle image upload and deletion - Modify the front-stop templates to display the article images and thumbnails
- Modify the back-end article edit form to let the administrator upload an image, view the article image, and delete the article image
- Tweak the CMS stylesheet to style the article images and the new elements in the article edit grade
Ready? Let's become started!
Stride 1: Create the paradigm folders
The kickoff matter to do is create a couple of folders in your website to store the full-size and thumbnail article images.
Open the existing cms
folder and you'll run into an images
folder containing the sample logo.jpg
image. Inside this images
folder, create an articles
folder. And then, inside the articles
folder, create two more folders:
-
fullsize
to store the full-size article images -
thumb
to store the smaller thumbnail versions of the commodity images
Next you lot demand to give your spider web server user permission to create files in these 2 folders. Typically on a Linux or Mac organization, you need to change the permissions to 777, similar this:
$ cd images/articles/ $ chmod 777 fullsize $ chmod 777 thumb
If your CMS is on a remote web server then you can normally prepare 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 upwards the config.php
file in the acme-level cms
folder, and add together the new lines highlighted in the code below:
<?php ini_set( "display_errors", true ); date_default_timezone_set( "Commonwealth of australia/Sydney" ); // http://world wide web.php.net/manual/en/timezones.php ascertain( "DB_DSN", "mysql:host=localhost;dbname=cms" ); ascertain( "DB_USERNAME", "username" ); ascertain( "DB_PASSWORD", "password" ); define( "CLASS_PATH", "classes" ); define( "TEMPLATE_PATH", "templates" ); define( "HOMEPAGE_NUM_ARTICLES", five ); define( "ADMIN_USERNAME", "admin" ); define( "ADMIN_PASSWORD", "mypass" ); define( "ARTICLE_IMAGE_PATH", "images/manufactures" ); define( "IMG_TYPE_FULLSIZE", "fullsize" ); define( "IMG_TYPE_THUMB", "thumb" ); define( "ARTICLE_THUMB_WIDTH", 120 ); define( "JPEG_QUALITY", 85 ); require( CLASS_PATH . "/Article.php" ); function handleException( $exception ) { echo "Deplorable, a problem occurred. Please try later."; error_log( $exception->getMessage() ); } set_exception_handler( 'handleException' ); ?>
Y'all've added the following constants:
-
ARTICLE_IMAGE_PATH
defines the path to the article images folder, relative to the height-level CMS binder. (If you desire to store your commodity images somewhere else, change this constant accordingly.) -
IMG_TYPE_FULLSIZE
defines a abiding to represent the "full-size" paradigm blazon. We'll use this in the code whenever we want to indicate a full-size image. This value ("fullsize"
) is as well used to locate the full-size images binder (images/articles/fullsize
), so if you use a different binder name, you'll desire to update this constant likewise. -
IMG_TYPE_THUMB
does a similar job toIMG_TYPE_FULLSIZE
, only represents the "thumbnail" image type instead. -
ARTICLE_THUMB_WIDTH
defines the width to use for the article thumbnail images, in pixels. Our image-handling code 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 best quality (but largest file size). 85 is a proficient compromise.
Step 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:
Drib Tabular array IF EXISTS articles; CREATE TABLE articles ( id smallint unsigned Not Nix auto_increment, publicationDate date NOT Zippo, # When the article was published championship varchar(255) NOT NULL, # Full title of the article summary text NOT NULL, # A brusque summary of the commodity content mediumtext NOT Nil, # The HTML content of the article imageExtension varchar(255) NOT Nil, # The filename extension of the article's full-size and thumbnail images PRIMARY KEY (id) );
This line adds a new field called imageExtension
to the articles
table. This field stores the filename extension of each commodity's uploaded prototype. For case, if the administrator uploads a PNG image so nosotros'll store the value ".png"
in the imageExtension
field.
What if you already have manufactures in your CMS?
If you load the above tables.sql
file into MySQL then information technology will delete any existing articles
table in your cms
database, and recreate the articles
table from scratch. This will delete whatever articles already in your CMS, which is obviously not what you want.
So if y'all already have articles in your CMS database, you want to alter the manufactures
table while retaining the existing information in the tabular array. To exercise this, alter your tables.sql
file to the following:
Modify TABLE manufactures Add together imageExtension varchar(255) Not Zippo After content;
Applying the changes
To really create your articles
table (or add the new imageExtension
field to your existing articles
tabular array, as appropriate), you demand to load the tables.sql
file into MySQL. To do this, follow the procedure described in Applying the changes in my final tutorial.
To cheque that your changes have been made, first login to MySQL:
mysql -u username -p cms
And then utilise the SHOW TABLES
and EXPLAIN
commands to check your table schemas in MySQL:
mysql> prove tables; +---------------+ | Tables_in_cms | +---------------+ | manufactures | +---------------+ i row in gear up (0.00 sec) mysql> explain articles; +-----------------+----------------------+------+-----+---------+----------------+ | Field | Blazon | Nil | Key | Default | Extra | +-----------------+----------------------+------+-----+---------+----------------+ | id | smallint(5) unsigned | NO | PRI | NULL | auto_increment | | publicationDate | date | NO | | Naught | | | title | varchar(255) | NO | | Cipher | | | summary | text | NO | | NULL | | | content | mediumtext | NO | | Cipher | | | imageExtension | varchar(255) | NO | | Aught | | +-----------------+----------------------+------+-----+---------+----------------+ 6 rows in fix (0.00 sec) mysql>
Notice the new imageExtension
field within the articles
table.
You've set up your CMS database so that it's prepare to handle image uploads. Now y'all can showtime modifying the CMS code.
Step 4: Modify the Commodity
form
Side by side, we need to change the Article
class to handle article images. Here'south the updated Commodity.php
class file. I've highlighted the lines of lawmaking and then you can encounter what's been added and inverse. Replace the code in your existing cms/classes/Article.php
file with this new lawmaking:
<?php /** * Class to handle manufactures */ class Commodity { // Properties /** * @var int The article ID from the database */ public $id = null; /** * @var int When the article is to be / was first published */ public $publicationDate = null; /** * @var string Full title of the article */ public $title = null; /** * @var string A short summary of the commodity */ public $summary = null; /** * @var string The HTML content of the article */ public $content = null; /** * @var string The filename extension of the article'south full-size and thumbnail images (empty string means the commodity has no prototype) */ public $imageExtension = ""; /** * Sets the object'south backdrop using the values in the supplied array * * @param assoc The holding values */ public part __construct( $information=array() ) { if ( isset( $data['id'] ) ) $this->id = (int) $data['id']; if ( isset( $information['publicationDate'] ) ) $this->publicationDate = (int) $information['publicationDate']; if ( isset( $information['title'] ) ) $this->title = preg_replace ( "/[^\.\,\-\_\'\"\@\?\!\:\$ a-zA-Z0-9()]/", "", $data['title'] ); if ( isset( $information['summary'] ) ) $this->summary = preg_replace ( "/[^\.\,\-\_\'\"\@\?\!\:\$ a-zA-Z0-9()]/", "", $data['summary'] ); if ( isset( $data['content'] ) ) $this->content = $data['content']; if ( isset( $information['imageExtension'] ) ) $this->imageExtension = preg_replace ( "/[^\.\,\-\_\'\"\@\?\!\$ a-zA-Z0-9()]/", "", $information['imageExtension'] ); } /** * Sets the object's backdrop using the edit form post values in the supplied array * * @param assoc The course mail service values */ public function storeFormValues( $params ) { // Shop all the parameters $this->__construct( $params ); // Parse and store the publication date if ( isset($params['publicationDate']) ) { $publicationDate = explode ( '-', $params['publicationDate'] ); if ( count($publicationDate) == three ) { list ( $y, $m, $d ) = $publicationDate; $this->publicationDate = mktime ( 0, 0, 0, $yard, $d, $y ); } } } /** * Stores any paradigm uploaded from the edit form * * @param assoc The 'paradigm' element from the $_FILES array containing the file upload data */ public function storeUploadedImage( $prototype ) { if ( $image['error'] == UPLOAD_ERR_OK ) { // Does the Article object have an ID? if ( is_null( $this->id ) ) trigger_error( "Article::storeUploadedImage(): Attempt to upload an image for an Article object that does non have its ID belongings gear up.", E_USER_ERROR ); // Delete any previous epitome(southward) for this commodity $this->deleteImages(); // Get and store the image filename extension $this->imageExtension = strtolower( strrchr( $image['proper noun'], '.' ) ); // Store 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( "Article::storeUploadedImage(): Couldn't set permissions on uploaded file.", E_USER_ERROR ); } // Get the prototype size and blazon $attrs = getimagesize ( $this->getImagePath() ); $imageWidth = $attrs[0]; $imageHeight = $attrs[1]; $imageType = $attrs[ii]; // Load the image into memory switch ( $imageType ) { case IMAGETYPE_GIF: $imageResource = imagecreatefromgif ( $this->getImagePath() ); break; case IMAGETYPE_JPEG: $imageResource = imagecreatefromjpeg ( $this->getImagePath() ); break; case IMAGETYPE_PNG: $imageResource = imagecreatefrompng ( $this->getImagePath() ); break; default: trigger_error ( "Article::storeUploadedImage(): Unhandled or unknown image blazon ($imageType)", E_USER_ERROR ); } // Copy 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 ); pause; case IMAGETYPE_PNG: imagepng ( $thumbResource, $this->getImagePath( IMG_TYPE_THUMB ) ); suspension; default: trigger_error ( "Commodity::storeUploadedImage(): Unhandled or unknown image type ($imageType)", E_USER_ERROR ); } $this->update(); } } /** * Deletes any images and/or thumbnails associated with the article */ public part deleteImages() { // Delete all fullsize images for this article foreach (glob( ARTICLE_IMAGE_PATH . "/" . IMG_TYPE_FULLSIZE . "/" . $this->id . ".*") every bit $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 . ".*") every bit $filename) { if ( !unlink( $filename ) ) trigger_error( "Commodity::deleteImages(): Couldn't delete thumbnail file.", E_USER_ERROR ); } // Remove the epitome filename extension from the object $this->imageExtension = ""; } /** * Returns the relative path to the article's full-size or thumbnail image * * @param string The blazon of image path to recall (IMG_TYPE_FULLSIZE or IMG_TYPE_THUMB). Defaults to IMG_TYPE_FULLSIZE. * @return string|false The epitome'southward path, or false if an epitome hasn't been uploaded */ public function getImagePath( $type=IMG_TYPE_FULLSIZE ) { return ( $this->id && $this->imageExtension ) ? ( ARTICLE_IMAGE_PATH . "/$blazon/" . $this->id . $this->imageExtension ) : false; } /** * Returns an Article object matching the given article ID * * @param int The article ID * @render Article|faux The article object, or false if the record was not found or there was a trouble */ public static function getById( $id ) { $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD ); $sql = "SELECT *, UNIX_TIMESTAMP(publicationDate) AS publicationDate FROM articles WHERE id = :id"; $st = $conn->set( $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) * @return Array|faux A 2-element assortment : results => array, a list of Article objects; totalRows => Full number of manufactures */ public static function getList( $numRows=1000000 ) { $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD ); $sql = "SELECT SQL_CALC_FOUND_ROWS *, UNIX_TIMESTAMP(publicationDate) AS publicationDate FROM manufactures Club By 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 ); $listing[] = $article; } // Now get the total number of articles that matched the criteria $sql = "SELECT FOUND_ROWS() Every bit totalRows"; $totalRows = $conn->query( $sql )->fetch(); $conn = cipher; return ( assortment ( "results" => $listing, "totalRows" => $totalRows[0] ) ); } /** * Inserts the current Article object into the database, and sets its ID property. */ public office insert() { // Does the Article object already have an ID? if ( !is_null( $this->id ) ) trigger_error ( "Article::insert(): Attempt to insert an Article object that already has its ID holding ready (to $this->id).", E_USER_ERROR ); // Insert the Article $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD ); $sql = "INSERT INTO articles ( publicationDate, title, summary, content, imageExtension ) VALUES ( FROM_UNIXTIME(:publicationDate), :title, :summary, :content, :imageExtension )"; $st = $conn->gear up ( $sql ); $st->bindValue( ":publicationDate", $this->publicationDate, PDO::PARAM_INT ); $st->bindValue( ":title", $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->execute(); $this->id = $conn->lastInsertId(); $conn = null; } /** * Updates the current Article object in the database. */ public role update() { // Does the Article object have an ID? if ( is_null( $this->id ) ) trigger_error ( "Article::update(): Attempt to update an Article object that does not have its ID belongings set.", E_USER_ERROR ); // Update the Article $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD ); $sql = "UPDATE articles Prepare publicationDate=FROM_UNIXTIME(:publicationDate), championship=:title, summary=:summary, content=:content, imageExtension=:imageExtension WHERE id = :id"; $st = $conn->set ( $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 Commodity object from the database. */ public function delete() { // Does the Commodity object accept an ID? if ( is_null( $this->id ) ) trigger_error ( "Article::delete(): Attempt to delete an Article object that does not take its ID holding set.", E_USER_ERROR ); // Delete the Commodity $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD ); $st = $conn->prepare ( "DELETE FROM articles WHERE id = :id LIMIT 1" ); $st->bindValue( ":id", $this->id, PDO::PARAM_INT ); $st->execute(); $conn = zilch; } } ?>
Hither'south a list of the changes we've fabricated to the Article
course:
A new $imageExtension
holding
This corresponds to the imageExtension
field you added to the articles
table in Step three. This property is used to store the filename extension for the commodity'due south full-size and thumbnail images — for example, ".jpg"
or ".png"
.
Nosotros also modified the constructor method, __construct()
, to store the new $imageExtension
property value in newly-created Article
objects.
A new storeUploadedImage()
method
Nosotros'll call this method from the admin.php
script whenever the user uploads a new commodity image using the article edit form. Its task is to move the uploaded image to the fullsize
images folder we created in Step 1, as well as generate a thumbnail version of the paradigm and shop information technology in the thumb
binder.
The method accepts a single $image
parameter. This should be the element in the PHP $_FILES
superglobal array that contains all the information about the uploaded epitome file.
Here'southward how the storeUploadedImage()
method works:
- Bank check for an upload error
The starting time thing the method does is check that the'error'
element in the$epitome
assortment equals the constantUPLOAD_ERR_OK
. This indicates that the user uploaded an image, and that the upload was successful. If the upload went OK so the method starts to process the uploaded epitome; otherwise it does nothing. - Does the
Article
object accept an ID?
Bold the file was uploaded OK, the method then makes sure that theArticle
object has an ID; in other words, that it has been saved to the database. This is of import, because we're going to rename the image file using the commodity's ID in a moment, so that we can easily associate the image with the article. If the article doesn't have an ID so the method callstrigger_error()
to display an fault message and exit. - Delete whatever previous prototype(s) for this commodity
Next the method calls theArticle::deleteImages()
method to delete whatever existing full-size and thumbnail epitome files associated with the commodity. (Nosotros'll write this method in a moment.) We do this in society to keep the paradigm folders clean, without any sometime, unused commodity images lying almost. For example, if the article currently has a.png
paradigm uploaded, and the user uploads a new.jpg
image, we desire to make sure that the at present-unused.png
images are deleted from the folders. - Get and store the paradigm filename extension
Equally you saw earlier, the$imageExtension
property needs to store the filename extension of the article epitome. Here the method uses thestrrchr()
part to extract the filename extension — that is, everything after (and including) the concluding dot in the filename — and stores the result in$imageExtension
, converted to lowercase withstrtolower()
for consistency. - Shop the image
Now the method moves the actual uploaded image into theimages/articles/fullsize
folder. To do this, it get-go 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'south temporary folder, such equally/tmp/
.Then the method calls
is_uploaded_file()
to cheque that the file in the temporary folder is indeed a file uploaded by PHP. This is a good security precaution to forestall sensitive arrangement files accidentally being made public.Finally, the method calls the
move_uploaded_file()
function to move the uploaded epitome from the temporary folder to theimages/articles/fullsize
folder. This function takes two arguments: the path to the file in the temporary folder, and the path to motility the file to. Information technology's a good thought to usemove_uploaded_file()
to motility uploaded files, since the function performs boosted security checks on the file earlier moving it.In one case the file's in identify, the method calls the
chmod()
role to gear up the file's permissions to 0666. This ensures that the file can be read and written by anyone, including the spider web server user and any FTP user that may need to change or delete the article images. (As always, if you lot're on a shared web server and then you might want to use more than restrictive permissions than this.) - Get the image size and type
The next job forstoreUploadedImage()
is to create the smaller thumbnail version of the uploaded image. First it callsgetimagesize()
, paassing in the path to the uploaded paradigm, in order to become the image's width, summit and format (GIF, JPEG or PNG), which it then stores in$imageWidth
,$imageHeight
and$imageType
respectively. - Load the image into retentivity
Now that the method knows the blazon of image information technology'southward dealing with, information technology callsimagecreatefromgif()
,imagecreatefromjpeg()
orimagecreatefrompng()
as appropriate to load the prototype into an image resource variable,$imageResource
, for processing. - Copy and resize the paradigm to create the thumbnail
Now it'south time to create the thumbnail image. To do this, the method get-go computes the thumbnail elevation,$thumbHeight
, based on the full-size image height ($imageHeight
), the total-size paradigm width ($imageWidth
), and the desired thumbnail width (ARTICLE_THUMB_WIDTH
).Side by side it calls
imagecreatetruecolor()
to create a blank image resource for storing the thumbnail image information, passing in the width and meridian of the paradigm to create. It stores this resource in a$thumbResource
variable.Finally, it calls
imagecopyresampled()
to create the smaller version of the uploaded prototype and store the issue in the$thumbResource
variable. It passes the following arguments toimagecopyresampled()
:- The prototype resource to store the resized paradigm in (
$thumbResource
) - The uploaded image resource (
$imageResource
) - The (ten,y) coordinates of top-left corner of the rectangle in
$thumbResource
to re-create the paradigm data to (0,0
— that is, the summit 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 summit left corner of the uploaded image) - The width and height of the rectangle in
$thumbResource
to copy the paradigm data to (ARTICLE_THUMB_WIDTH
and$thumbHeight
— that is, the entire width and height of the thumbnail) - The width and height of the rectangle in
$imageResource
to copy the image information from ($imageWidth
and$imageHeight
— that is, the entire width and height of the uploaded image)
- The prototype resource to store the resized paradigm in (
- Save the thumbnail
Now that the method has created the thumbnail image data and stored it in$thumbResource
, it needs to write the new thumbnail epitome to disk. To practice this, information technology callsimagegif()
,imagejpeg()
orimagepng()
, depending on the epitome type. It passes in both$thumbResource
and the path to use for the thumbnail prototype. To become the path, it calls thegetImagePath()
method (which nosotros'll expect at in a moment), passing in ourIMG_TYPE_THUMB
abiding to point that we desire the path to the thumbnail. - Update the article record
Finally, since theCommodity
object'southward$imageExtension
property may well take inverse as a result of uploading the prototype, the method calls$this->update()
to update the commodity record in the database.
A new deleteImages()
method
The deleteImages()
method is responsible for immigration out any image files associated with the current article. It's called by storeUploadedImage()
before uploading a new paradigm (as you saw in the previous section). In improver, it's called if the administrator specifically asks to delete the article'south paradigm and thumbnail via the Edit Article course. Finally, deleteImages()
is also called when the article itself needs to exist deleted.
deleteImages()
calls PHP's glob()
part to retrieve a list of all image files in both the images/manufactures/fullsize
and images/manufactures/thumb
folders that are named after the article's ID. For case, if the article's ID is 3, the call to glob()
will return whatsoever image files called "3.gif"
, "iii.jpg"
or "3.png"
.
For each filename in the assortment returned past glob()
, the method attempts to delete the file by calling PHP'south unlink()
function. If there'southward a problem deleting the file then it raises an error and exits.
Once all the image files have been deleted, deleteImages()
sets the Article
object'south $imageExtension
holding to an empty string (""
) to signal that the article no longer has an uploaded epitome.
A new getImagePath()
method
The last new method we've added to the Article
class is getImagePath()
, which returns the path to one of the two article images.
The method takes a single, optional statement, $type
, that indicates whether it should return the path to the full-size epitome (IMG_TYPE_FULLSIZE
, the default), or the thumbnail (IMG_TYPE_THUMB
). Information technology then uses the article's ID, forth with the value stored in the commodity's $imageExtension
holding, to compute the path to the image file inside the images/articles/fullsize
or images/articles/thumb
folder.
For example, if getImagePath()
is passed IMG_TYPE_THUMB
as an argument, the commodity's ID is three, and its $imageExtension
property contains ".jpg"
, then the method will render 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 stop of the file. As y'all tin meet, we've modified the SQL INSERT
and UPDATE
statements to accommodate the new $imageExtension
property so that the epitome extension is stored in the articles
table. Nosotros've also added extra bindValue()
calls to laissez passer the belongings'south value to the SQL statements.
Step five: Change the admin.php
script
Nosotros now demand to make some changes to admin.php
, the back-end admin script, so that information technology can handle paradigm uploads. Fortunately, nosotros've already done most of the hard work in our Article
form, 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. Supervene upon the code in your existing cms/admin.php
file with this code:
<?php require( "config.php" ); session_start(); $activeness = isset( $_GET['action'] ) ? $_GET['action'] : ""; $username = isset( $_SESSION['username'] ) ? $_SESSION['username'] : ""; if ( $activeness != "login" && $activity != "logout" && !$username ) { login(); leave; } switch ( $action ) { case 'login': login(); break; case 'logout': logout(); intermission; case 'newArticle': newArticle(); intermission; case 'editArticle': editArticle(); pause; instance 'deleteArticle': deleteArticle(); pause; default: listArticles(); } function login() { $results = assortment(); $results['pageTitle'] = "Admin Login | Widget News"; if ( isset( $_POST['login'] ) ) { // User has posted the login form: endeavor 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. Please endeavor once more."; require( TEMPLATE_PATH . "/admin/loginForm.php" ); } } else { // User has not posted the login form yet: display the form require( TEMPLATE_PATH . "/admin/loginForm.php" ); } } function logout() { unset( $_SESSION['username'] ); header( "Location: admin.php" ); } function newArticle() { $results = array(); $results['pageTitle'] = "New Article"; $results['formAction'] = "newArticle"; if ( isset( $_POST['saveChanges'] ) ) { // User has posted the article edit form: salve the new commodity $article = new Commodity; $article->storeFormValues( $_POST ); $article->insert(); if ( isset( $_FILES['image'] ) ) $commodity->storeUploadedImage( $_FILES['image'] ); header( "Location: admin.php?status=changesSaved" ); } elseif ( isset( $_POST['cancel'] ) ) { // User has cancelled their edits: render to the article listing header( "Location: admin.php" ); } else { // User has not posted the commodity edit form yet: display the form $results['article'] = new Article; crave( TEMPLATE_PATH . "/admin/editArticle.php" ); } } office editArticle() { $results = array(); $results['pageTitle'] = "Edit Article"; $results['formAction'] = "editArticle"; if ( isset( $_POST['saveChanges'] ) ) { // User has posted the article edit form: save the commodity changes if ( !$article = Article::getById( (int)$_POST['articleId'] ) ) { header( "Location: admin.php?error=articleNotFound" ); return; } $article->storeFormValues( $_POST ); if ( isset($_POST['deleteImage']) && $_POST['deleteImage'] == "yes" ) $commodity->deleteImages(); $article->update(); if ( isset( $_FILES['image'] ) ) $article->storeUploadedImage( $_FILES['image'] ); header( "Location: admin.php?condition=changesSaved" ); } elseif ( isset( $_POST['cancel'] ) ) { // User has cancelled their edits: render to the commodity list header( "Location: admin.php" ); } else { // User has non posted the article edit form yet: display the form $results['article'] = Article::getById( (int)$_GET['articleId'] ); require( TEMPLATE_PATH . "/admin/editArticle.php" ); } } office deleteArticle() { if ( !$article = Article::getById( (int)$_GET['articleId'] ) ) { header( "Location: admin.php?error=articleNotFound" ); render; } $article->deleteImages(); $article->delete(); header( "Location: admin.php?condition=articleDeleted" ); } office listArticles() { $results = assortment(); $data = Article::getList(); $results['manufactures'] = $information['results']; $results['totalRows'] = $information['totalRows']; $results['pageTitle'] = "All Manufactures"; if ( isset( $_GET['error'] ) ) { if ( $_GET['error'] == "articleNotFound" ) $results['errorMessage'] = "Error: Article non found."; } if ( isset( $_GET['status'] ) ) { if ( $_GET['status'] == "changesSaved" ) $results['statusMessage'] = "Your changes have been saved."; if ( $_GET['status'] == "articleDeleted" ) $results['statusMessage'] = "Commodity deleted."; } crave( TEMPLATE_PATH . "/admin/listArticles.php" ); } ?>
Allow's work through these changes to admin.php
:
- Changes to
newArticle()
We've added a single line of code to thenewArticle()
role to handle image uploads. It checks that the'epitome'
chemical element exists in the$_FILES
array and, if it does be, it calls theArticle
object'sstoreUploadedImage()
method, passing in the$_FILES['image']
element, to store the image and create the thumbnail. - Changes to
editArticle()
As withnewArticle()
, we've added a line of code that checks for an uploaded image and calls$article->storeUploadedImage()
to store it and create the thumbnail. We've also added a line of code that checks if the user selected the "delete image" checkbox in the Edit Commodity form. If they did then we call$article->deleteImages()
to delete whatsoever existing images associated with the article. - Changes to
deleteArticle()
Finally, we've added a single line of lawmaking to thedeleteArticle()
function that calls$commodity->deleteImages()
. This ensures that any images associated with the article besides go deleted.
Stride half dozen: Change the front-end templates
Our database and PHP lawmaking can now handle article images, only we demand to make some changes both to the forepart-end templates that visitors run across, and the back-end admin templates.
Let's outset past altering the front-end templates to brandish the article images.
i. homepage.php
The homepage.php
template displays the site's home page, including a list of recent articles. We'll modify this template to display each article'south thumbnail next to the article in the listing.
Hither's the modified file with the new lines highlighted — supersede your sometime cms/templates/homepage.php
file with this code:
<?php include "templates/include/header.php" ?> <ul id="headlines"> <?php foreach ( $results['articles'] as $commodity ) { ?> <li> <h2> <span class="pubDate"><?php echo date('j F', $article->publicationDate)?></span><a href=".?activity=viewArticle&articleId=<?php echo $article->id?>"><?php repeat htmlspecialchars( $commodity->championship )?></a> </h2> <p class="summary"> <?php if ( $imagePath = $article->getImagePath( IMG_TYPE_THUMB ) ) { ?> <a href=".?action=viewArticle&articleId=<?php repeat $article->id?>"><img class="articleImageThumb" src="<?php echo $imagePath?>" alt="Article Thumbnail" /></a> <?php } ?> <?php echo htmlspecialchars( $article->summary )?> </p> </li> <?php } ?> </ul> <p><a href="./?activeness=archive">Commodity Archive</a></p> <?php include "templates/include/footer.php" ?>
Here we've added some lawmaking inside the "summary" paragraph for each article. The lawmaking calls the commodity's getImagePath()
method, passing in IMG_TYPE_THUMB
to signal that nosotros desire the path to the commodity's thumbnail. It then stores the path in the $imagePath
variable. If this path is a non-imitation
value and then the article has a thumbnail image, so the lawmaking then constructs a link to view the article, wrapped around an img
element that contains the thumbnail's path. Nosotros've added an articleImageThumb
form to the thumbnail paradigm and so that we tin fashion it in the stylesheet.
If $imagePath
's value is false
then the article doesn't have a thumbnail, then no markup is constructed.
2. archive.php
archive.php
displays the commodity archive page — that is, a list of all the articles in the database. We need to alter it in the same way as homepage.php
, and then that it displays thumbnails next to the article summaries.
Here's the modified annal.php
file — replace your old cms/templates/archive.php
file with this one:
<?php include "templates/include/header.php" ?> <h1>Article Archive</h1> <ul id="headlines" class="archive"> <?php foreach ( $results['articles'] as $article ) { ?> <li> <h2> <span class="pubDate"><?php echo date('j F Y', $commodity->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 echo $article->id?>"><img class="articleImageThumb" src="<?php echo $imagePath?>" alt="Article Thumbnail" /></a> <?php } ?> <?php repeat htmlspecialchars( $commodity->summary )?> </p> </li> <?php } ?> </ul> <p><?php echo $results['totalRows']?> article<?php echo ( $results['totalRows'] != 1 ) ? 's' : '' ?> in full.</p> <p><a href="./">Return to Homepage</a></p> <?php include "templates/include/footer.php" ?>
As you tin can run across, this is the aforementioned change that we made to homepage.php
. If an article has an image, its thumbnail will now appear next to the article summary in the archive page.
iii. viewArticle.php
The viewArticle.php
template displays an private article page, containing the article'south headline, summary and content. But equally we modified homepage.php
and archive.php
to display thumbnails adjacent to the commodity summaries, we also demand to enhance viewArticle.php
so that it displays the total-size article images in the article pages.
Hither's the changed template. As e'er, I've highlighted the new code. Relieve this code over your onetime cms/templates/viewArticle.php
file:
<?php include "templates/include/header.php" ?> <h1 style="width: 75%;"><?php repeat htmlspecialchars( $results['article']->title )?></h1> <div style="width: 75%; font-way: italic;"><?php repeat htmlspecialchars( $results['article']->summary )?></div> <div manner="width: 75%; min-elevation: 300px;"> <?php if ( $imagePath = $results['article']->getImagePath() ) { ?> <img id="articleImageFullsize" src="<?php echo $imagePath?>" alt="Article Epitome" /> <?php } ?> <?php echo $results['commodity']->content?> </div> <p form="pubDate">Published on <?php repeat date('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 substantially the same way as the code added to the homepage and archive templates. Information technology calls the commodity's getImagePath()
method to get the path to the total-size article prototype. If the path isn't simulated
then the article has an image, and the lawmaking inserts the appropriate img
chemical element into the markup. The img
chemical element is given an id
of articleImageFullsize
and so that we can style information technology using CSS.
Step 7: Modify the back-end templates
There's actually only one back-end admin template that we need to alter, and that's the editArticle.php
article edit class. Hither'southward the new template with changes highlighted — salvage it over your existing cms/templates/admin/editArticle.php
file:
<?php include "templates/include/header.php" ?> <script> // Prevents file upload hangs in Mac Safari // Inspired past http://airbladesoftware.com/notes/note-to-self-prevent-uploads-hanging-in-safari role closeKeepAlive() { if ( /AppleWebKit|MSIE/.exam( navigator.userAgent) ) { var xhr = new XMLHttpRequest(); xhr.open( "Become", "/ping/shut", false ); xhr.send(); } } </script> <div id="adminHeader"> <h2>Widget News Admin</h2> <p>Y'all are logged in equally <b><?php echo htmlspecialchars( $_SESSION['username']) ?></b>. <a href="admin.php?action=logout"?>Log out</a></p> </div> <h1><?php echo $results['pageTitle']?></h1> <course action="admin.php?action=<?php echo $results['formAction']?>" method="postal service" enctype="multipart/form-data" onsubmit="closeKeepAlive()"> <input blazon="hidden" name="articleId" value="<?php echo $results['article']->id ?>"/> <?php if ( isset( $results['errorMessage'] ) ) { ?> <div class="errorMessage"><?php echo $results['errorMessage'] ?></div> <?php } ?> <ul> <li> <label for="title">Commodity Championship</label> <input type="text" proper noun="title" id="title" placeholder="Name of the article" required autofocus maxlength="255" value="<?php repeat htmlspecialchars( $results['article']->championship )?>" /> </li> <li> <label for="summary">Article Summary</label> <textarea name="summary" id="summary" placeholder="Brief clarification of the article" required maxlength="m" style="pinnacle: 5em;"><?php repeat htmlspecialchars( $results['article']->summary )?></textarea> </li> <li> <label for="content">Article Content</label> <textarea name="content" id="content" placeholder="The HTML content of the article" required maxlength="100000" style="height: 30em;"><?php echo htmlspecialchars( $results['article']->content )?></textarea> </li> <li> <label for="publicationDate">Publication Date</label> <input type="date" proper noun="publicationDate" id="publicationDate" placeholder="YYYY-MM-DD" required maxlength="10" value="<?php echo $results['article']->publicationDate ? appointment( "Y-m-d", $results['article']->publicationDate ) : "" ?>" /> </li> <?php if ( $results['article'] && $imagePath = $results['article']->getImagePath() ) { ?> <li> <label>Current Image</label> <img id="articleImage" src="<?php repeat $imagePath ?>" alt="Article Image" /> </li> <li> <characterization></label> <input blazon="checkbox" name="deleteImage" id="deleteImage" value="aye"/ > <label for="deleteImage">Delete</label> </li> <?php } ?> <li> <characterization for="image">New Image</label> <input type="file" name="epitome" id="image" placeholder="Choose an prototype to upload" maxlength="255" /> </li> </ul> <div class="buttons"> <input blazon="submit" name="saveChanges" value="Save Changes" /> <input type="submit" formnovalidate proper name="cancel" value="Cancel" /> </div> </course> <?php if ( $results['commodity']->id ) { ?> <p><a href="admin.php?action=deleteArticle&articleId=<?php echo $results['article']->id ?>" onclick="return confirm('Delete This Article?')">Delete This Article</a></p> <?php } ?> <?php include "templates/include/footer.php" ?>
Let's take a wait at each of these changes in plow:
- The
closeKeepAlive()
JavaScript role
For some reason, Safari on the Mac has suffered from a long-standing consequence whereby file uploads hang occasionally. (Find out more about this outcome here and here.) Since this is quite annoying if y'all use Safari (equally I practise), I've added this little function that makes an Ajax asking to a non-existent URL on the server, forcing Safari to shut its connectedness to the server. This seems to fix the trouble. My function is similar to this script, except that my function doesn't crave the Prototype.js library. - Changes to the
<course>
tag
We've added the attributeenctype="multipart/course-information"
to theform
chemical element. This attribute is required whenever you create a form containing a file upload field. Information technology 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).Nosotros've also attached the
closeKeepAlive()
function as asubmit
effect handler to the class, and then that the function runs when the form is submitted. - The article image and "delete" checkbox
The side by side add-on to the form displays the total-size paradigm currently associated with the article (if any). As with the front end-finish templates, the PHP code calls the article'sgetImagePath()
method to call up the image'southward path. If an image path was returned, we add anli
element to the folio containing a field characterization ("Current Epitome"), along with animg
chemical element linking to the image. We too include anotherli
element containing adeleteImage
checkbox. This lets the administrator delete any image(s) currently associated with the article. - The image upload field
Last, but by no means to the lowest degree, we add together the<input blazon="file">
upload field that allows the ambassador to upload an image for this article. Nosotros give information technology aname
attribute of"image"
, which means that we're able to admission the uploaded file from our PHP code using$_FILES['epitome']
(encounter Step iv earlier in the tutorial).
Step 8: Modify the stylesheet
Adjacent we'll brand some additions and changes to our CMS's stylesheet, manner.css
, in club to style the article images and thumbnails on the front end, equally well as the new elements in the Edit Article form.
Here's the new style.css
file with the changes highlighted. Replace your existing style.css
file in your cms
folder with this file:
/* Fashion the body and outer container */ body { margin: 0; colour: #333; background-colour: #00a0b0; font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; line-pinnacle: 1.5em; } #container { width: 960px; background: #fff; margin: 20px motorcar; padding: 20px; -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; } /* The logo and footer */ #logo { display: block; width: 300px; padding: 0 660px 20px 0; border: none; edge-bottom: 1px solid #00a0b0; margin-bottom: 40px; } #footer { border-top: 1px solid #00a0b0; margin-top: 40px; padding: 20px 0 0 0; font-size: .8em; } /* Headings */ h1 { color: #eb6841; margin-lesser: 30px; line-superlative: one.2em; } h2, h2 a { color: #edc951; } h2 a { text-ornamentation: none; } /* Article 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: uppercase; } #headlines .pubDate { display: inline-block; width: 100px; font-size: .5em; vertical-marshal: middle; } #headlines.archive .pubDate { width: 130px; } #headlines .articleImageThumb { width: 120px; bladder: left; margin: 4px 20px 0 0; border: 1px solid #00a0b0; } .summary { padding-left: 100px; } #headlines.archive .summary { padding-left: 130px; } /* Commodity pages */ #articleImageFullsize { width: 250px; float: left; margin: 4px 20px 10px 0; border: 1px solid #00a0b0; } /* "You lot are logged in..." header on admin pages */ #adminHeader { width: 940px; padding: 0 10px; edge-bottom: 1px solid #00a0b0; margin: -30px 0 40px 0; font-size: 0.8em; } /* Style the form with a coloured background, forth with curved corners and a drop shadow */ form { margin: 20px auto; padding: 40px 20px; overflow: automobile; background: #fff4cf; edge: 1px solid #666; -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, .viii); box-shadow: 0 0 .5em rgba(0, 0, 0, .8); } /* Give form elements consistent margin, padding and line height */ form ul { list-style: none; margin: 0; padding: 0; overflow: subconscious; } form ul li { margin: .9em 0 0 0; padding: 0; } course * { line-tiptop: 1em; } /* The field labels */ label { display: block; float: left; clear: left; text-align: correct; width: fifteen%; padding: .4em 0 0 0; margin: .15em .5em 0 0; } /* The fields */ input, select, textarea { brandish: block; margin: 0; padding: .4em; width: 80%; } input, textarea, .date { border: 2px solid #666; -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; groundwork: #fff; } input { font-size: .9em; } input[type="checkbox"] { display: inline-block; padding: 0; margin: 0 0 .8em 0; width: auto; } select { padding: 0; margin-bottom: 2.5em; position: relative; acme: .7em; } textarea { font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; font-size: .9em; meridian: 5em; line-height: 1.5em; } textarea#content { font-family: "Courier New", courier, fixed; } #articleImage { edge: 2px solid #666; } #deleteImage { clear: both; } label[for="deleteImage"] { float: none; display: inline; } input[type="file"] { float: left; } /* Place a border effectually focused fields */ course *:focus { edge: 2px solid #7c412b; outline: none; } /* Display correctly filled-in fields with a green background */ input:valid, textarea:valid { groundwork: #efe; } /* Submit buttons */ .buttons { text-align: center; 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, .eight); box-shadow: 0 0 .5em rgba(0, 0, 0, .8); color: #fff; groundwork: #ef7d50; font-weight: bold; -webkit-appearance: none; } input[type="submit"]:hover, input[blazon="submit"]:active { cursor: pointer; groundwork: #fff; color: #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, .viii) inset; box-shadow: 0 0 .5em rgba(0, 0, 0, .8) inset; } /* Tables */ table { width: 100%; border-plummet: collapse; } tr, th, td { padding: 10px; margin: 0; text-align: left; } table, th { edge: 1px solid #00a0b0; } th { edge-left: none; border-right: none; background: #ef7d50; color: #fff; cursor: default; } tr:nth-kid(odd) { background: #fff4cf; } tr:nth-child(even) { background: #fff; } tr:hover { background: #ddd; cursor: arrow; } /* Condition and error boxes */ .statusMessage, .errorMessage { font-size: .8em; padding: .5em; margin: 2em 0; -moz-border-radius: 5px; -webkit-border-radius: 5px; edge-radius: 5px; -moz-box-shadow: 0 0 .5em rgba(0, 0, 0, .eight); -webkit-box-shadow: 0 0 .5em rgba(0, 0, 0, .viii); -box-shadow: 0 0 .5em rgba(0, 0, 0, .eight); } .statusMessage { background-color: #2b2; border: 1px solid #080; color: #fff; } .errorMessage { groundwork-color: #f22; edge: 1px solid #800; colour: #fff; }
Every bit you can encounter, we've added various rulesets to manner the thumbnails in the homepage and archive pages; the full-size images in article pages; and the "delete epitome" checkbox, article image and file upload field inside the "Edit Article" course (which you added in Pace 7).
On line 89, we've set the thumbnail width to 120 pixels to match the ARTICLE_THUMB_WIDTH
setting in config.php
. Similarly, on line 107 we've set the width of the full-size images in the article pages to 250 pixels. If y'all want to use dissimilar widths for the thumbnails and images then you need to change these two values, as well equally the ARTICLE_THUMB_WIDTH
setting.
Try it out!
Not bad stuff! You now have a CMS that can handle image uploads. To examination your new CMS, follow these steps:
- Log in
Open up your browser and visit the base URL of your CMS — for example,http://localhost/cms/
. Click the Site Admin link in the footer, and log in. - Upload some images
Click an article in the All Articles list, or add together a new article by clicking the Add a New Article link at the lesser of the folio. In the New Article / Edit Article form, click the button next to the New Prototype characterization at the bottom of the class. Cull a file to upload, then click Salvage Changes to save your article edits and upload the epitome. - View your images
Click the Widget News logo at the top of the folio to view the site. Yous should see thumbnail images next to the articles in the listing. If you click the Article Archive link at the lesser of the page then yous should also meet the thumbnails there. Click a thumbnail (or article headline) to view the total article page, along with the total-size article image. - Changing and deleting images
Just to make certain everything works, try editing an article with an image and uploading a new prototype. This should then replace the previous commodity image and thumbnail. (Depending on your server and browser setup, you may need to articulate your browser'southward cache and reload the folio to meet the new image.) You can also try clicking the Delete checkbox to remove an existing image from an article.
Yous can as well try out the demo on our server too! The demo is read-only, so yous tin can't upload or delete images, only you tin see how the images expect on the forepart-end, as well as in the back-cease article edit form.
Summary
In this tutorial we've added an epitome upload feature to the content management organization from my original tutorial. Hither's what nosotros did:
- Created some folders within your CMS to store the commodity images and thumbnails
- Added some constants to the config file to specify the path to the images folder, 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, as well as methods to handle epitome uploads, prototype deletion, and retrieving image paths - Extended the
admin.php
admin script to let uploading and deleting of article images - Altered 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 form,
editArticle.php
, to include the epitome upload field, also as the currently-uploaded epitome and a "delete" image" checkbox, and - Tweaked the stylesheet,
way.css
, to manner the article images and thumbnails, as well equally the commodity edit form elements.
You tin now use your CMS to publish articles illustrated with images. Now all y'all need to practise is create some cute images! Enjoy. 🙂
[Flickr photograph credits for sample images: Businessman, Cogs, Willy Wonka, Newspapers, Dollars.]
Source: https://www.elated.com/add-image-uploading-to-your-cms/
0 Response to "Metatag Warning: Be Sure to Upload Your Image to Your Cms or Host."
Enviar um comentário