Facebook like photo gallery with comments

Tutorials

Have you thought about own facebook-style photo gallry system with comments? I think – yes. Today I made up my mind to prepare it for you. Main idea – when we click at images – they popup (ajax) with bigger image at the left and comments section at the right. All images are in the database (mySQL). And, of course, we will use PHP to achieve our result. Also, our comment system will prevent accepting more than 1 comment per 10 mins (to avoid spam).

Live Demo

[sociallocker]

download in package

[/sociallocker]


Now – download the source files and lets start coding !


Step 1. SQL

For our gallery I prepared two SQL tables: first table keeps records of our images. It contains several fields: title, filename, description, time of adding and comments count. Another table keeps comments. So, execute next SQL instructions:

01 CREATE TABLE IF NOT EXISTS `s281_photos` (
02   `id` int(10) unsigned NOT NULL auto_increment,
03   `title` varchar(255) default '',
04   `filename` varchar(255) default '',
05   `description` text NOT NULL,
06   `when` int(11) NOT NULL default '0',
07   `comments_count` int(11) NOT NULL default '0',
08   PRIMARY KEY  (`id`)
09 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
10 INSERT INTO `s281_photos` (`title`, `filename`, `description`, `when`) VALUES
11 ('Item #1''photo1.jpg''Description of Item #1', UNIX_TIMESTAMP()),
12 ('Item #2''photo2.jpg''Description of Item #2', UNIX_TIMESTAMP()+1),
13 ('Item #3''photo3.jpg''Description of Item #3', UNIX_TIMESTAMP()+2),
14 ('Item #4''photo4.jpg''Description of Item #4', UNIX_TIMESTAMP()+3),
15 ('Item #5''photo5.jpg''Description of Item #5', UNIX_TIMESTAMP()+4),
16 ('Item #6''photo6.jpg''Description of Item #6', UNIX_TIMESTAMP()+5),
17 ('Item #7''photo7.jpg''Description of Item #7', UNIX_TIMESTAMP()+6),
18 ('Item #8''photo8.jpg''Description of Item #8', UNIX_TIMESTAMP()+7),
19 ('Item #9''photo9.jpg''Description of Item #9', UNIX_TIMESTAMP()+8),
20 ('Item #10''photo10.jpg''Description of Item #10', UNIX_TIMESTAMP()+9);
21 CREATE TABLE IF NOT EXISTS `s281_items_cmts` (
22   `c_id` int(11) NOT NULL AUTO_INCREMENT ,
23   `c_item_id` int(12) NOT NULL default '0',
24   `c_ip` varchar(20) default NULL,
25   `c_name` varchar(64) default '',
26   `c_text` text NOT NULL ,
27   `c_when` int(11) NOT NULL default '0',
28   PRIMARY KEY (`c_id`),
29   KEY `c_item_id` (`c_item_id`)
30 ) ENGINE=MYISAM DEFAULT CHARSET=utf8;

Step 2. PHP

Now, please create empty index.php file and put next code:

index.php

01 <?php
02 // disable warnings
03 if (version_compare(phpversion(), "5.3.0"">=")  == 1)
04   error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
05 else
06   error_reporting(E_ALL & ~E_NOTICE);
07 require_once('classes/CMySQL.php'); // include service classes to work with database and comments
08 require_once('classes/CMyComments.php');
09 if ($_POST['action'] == 'accept_comment') {
10     echo $GLOBALS['MyComments']->acceptComment();
11     exit;
12 }
13 // prepare a list with photos
14 $sPhotos '';
15 $aItems $GLOBALS['MySQL']->getAll("SELECT * FROM `s281_photos` ORDER by `when` ASC"); // get photos info
16 foreach ($aItems as $i => $aItemInfo) {
17     $sPhotos .= '<div class="photo"><img src="images/thumb_'.$aItemInfo['filename'].'" id="'.$aItemInfo['id'].'" /><p>'.$aItemInfo['title'].' item</p><i>'.$aItemInfo['description'].'</i></div>';
18 }
19 ?>
20 <!DOCTYPE html>
21 <html lang="en"><head>
22     <meta charset="utf-8" />
23     <title>Facebook like photo gallery with comments | Script Tutorials</title>
24     <!-- Link styles -->
25     <link href="css/main.css" rel="stylesheet" type="text/css" />
26     <!-- Link scripts -->
27     <script src="https://www.google.com/jsapi"></script>
28     <script>
29         google.load("jquery""1.7.1");
30     </script>
31     <script src="js/script.js"></script>
32 </head>
33 <body>
34     <header>
35         <h2>Facebook like photo gallery with comments</h2>
36         <a href="https://www.script-tutorials.com/facebook-like-photo-gallery-with-comments/" class="stuts">Back to original tutorial on <span>Script Tutorials</span></a>
37     </header>
38     <!-- Container with last photos -->
39     <div class="container">
40         <h1>Last photos:</h1>
41         <?= $sPhotos ?>
42     </div>
43     <!-- Hidden preview block -->
44     <div id="photo_preview" style="display:none">
45         <div class="photo_wrp">
46             <img class="close" src="images/close.gif" />
47             <div style="clear:both"></div>
48             <div class="pleft">test1</div>
49             <div class="pright">test2</div>
50             <div style="clear:both"></div>
51         </div>
52     </div>
53 </body></html>

We have just created main index file of our gallery. By default – script generates a list of images (with title and description), and it also generates an empty hidden object which we are going to use in order to accept custom content by ajax requests. Also, when we post comments, we forward this request (to accept new comment) into comments class. Now, lets review next important php file:

photos_ajx.php

01 <?php
02 // disable warnings
03 if (version_compare(phpversion(), "5.3.0"">=")  == 1)
04   error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
05 else
06   error_reporting(E_ALL & ~E_NOTICE);
07 if ($_POST['action'] == 'get_info' && (int)$_POST['id'] > 0) {
08     require_once('classes/CMySQL.php'); // include service classes to work with database and comments
09     require_once('classes/CMyComments.php');
10     // get photo info
11     $iPid = (int)$_POST['id'];
12     $aImageInfo $GLOBALS['MySQL']->getRow("SELECT * FROM `s281_photos` WHERE `id` = '{$iPid}'");
13     // prepare last 10 comments
14     $sCommentsBlock $GLOBALS['MyComments']->getComments($iPid);
15     $aItems $GLOBALS['MySQL']->getAll("SELECT * FROM `s281_photos` ORDER by `when` ASC"); // get photos info
16     // Prev & Next navigation
17     $sNext $sPrev '';
18     $iPrev = (int)$GLOBALS['MySQL']->getOne("SELECT `id` FROM `s281_photos` WHERE `id` < '{$iPid}' ORDER BY `id` DESC LIMIT 1");
19     $iNext = (int)$GLOBALS['MySQL']->getOne("SELECT `id` FROM `s281_photos` WHERE `id` > '{$iPid}' ORDER BY `id` ASC LIMIT 1");
20     $sPrevBtn = ($iPrev) ? '<div class="preview_prev" onclick="getPhotoPreviewAjx(\''.$iPrev.'\')"><img src="images/prev.png" alt="prev" /></div>' '';
21     $sNextBtn = ($iNext) ? '<div class="preview_next" onclick="getPhotoPreviewAjx(\''.$iNext.'\')"><img src="images/next.png" alt="next" /></div>' '';
22     require_once('classes/Services_JSON.php');
23     $oJson new Services_JSON();
24     header('Content-Type:text/javascript');
25     echo $oJson->encode(array(
26         'data1' => '<img class="fileUnitSpacer" src="images/'$aImageInfo['filename'] .'">' $sPrevBtn $sNextBtn,
27         'data2' => $sCommentsBlock,
28     ));
29     exit;
30 }

This file sends back information about requested photo. This is an enlarged image, block with comments and navigation buttons (to open previous / next images ajaxy). As you can see – we use comments class, now, it’s time to look at it too:

classes/CMyComments.php

01 <?php
02 class CMyComments {
03     // constructor
04     function CMyComments() {
05     }
06     // return comments block
07     function getComments($i) {
08         // draw last 10 comments
09         $sComments '';
10         $aComments $GLOBALS['MySQL']->getAll("SELECT * FROM `s281_items_cmts` WHERE `c_item_id` = '{$i}' ORDER BY `c_when` DESC LIMIT 10");
11         foreach ($aComments as $i => $aCmtsInfo) {
12             $sWhen date('F j, Y H:i'$aCmtsInfo['c_when']);
13             $sComments .= <<<EOF
14 <div class="comment" id="{$aCmtsInfo['c_id']}">
15     <p>Comment from {$aCmtsInfo['c_name']} <span>({$sWhen})</span>:</p>
16     <p>{$aCmtsInfo['c_text']}</p>
17 </div>
18 EOF;
19         }
20         return <<<EOF
21 <div class="comments" id="comments">
22     <h2>Comments</h2>
23     <div id="comments_warning1" style="display:none">Don`t forget to fill both fields (Name and Comment)</div>
24     <div id="comments_warning2" style="display:none">You can't post more than one comment per 10 minutes (spam protection)</div>
25     <form onsubmit="return false;">
26         <table>
27             <tr><td class="label"><label>Your name: </label></td><td class="field"><input type="text" value="" title="Please enter your name" id="name" /></td></tr>
28             <tr><td class="label"><label>Comment: </label></td><td class="field"><textarea name="text" id="text"></textarea></td></tr>
29             <tr><td class="label">&nbsp;</td><td class="field"><button onclick="submitComment({$i}); return false;">Post comment</button></td></tr>
30         </table>
31     </form>
32     <div id="comments_list">{$sComments}</div>
33 </div>
34 EOF;
35     }
36     function acceptComment() {
37         $iItemId = (int)$_POST['id']; // prepare necessary information
38         $sIp $this->getVisitorIP();
39         $sName $GLOBALS['MySQL']->escape(strip_tags($_POST['name']));
40         $sText $GLOBALS['MySQL']->escape(strip_tags($_POST['text']));
41         if ($sName && $sText) {
42             // check - if there is any recent post from you or not
43             $iOldId $GLOBALS['MySQL']->getOne("SELECT `c_item_id` FROM `s281_items_cmts` WHERE `c_item_id` = '{$iItemId}' AND `c_ip` = '{$sIp}' AND `c_when` >= UNIX_TIMESTAMP() - 600 LIMIT 1");
44             if (! $iOldId) {
45                 // if everything is fine - allow to add comment
46                 $GLOBALS['MySQL']->res("INSERT INTO `s281_items_cmts` SET `c_item_id` = '{$iItemId}', `c_ip` = '{$sIp}', `c_when` = UNIX_TIMESTAMP(), `c_name` = '{$sName}', `c_text` = '{$sText}'");
47                 $GLOBALS['MySQL']->res("UPDATE `s281_photos` SET `comments_count` = `comments_count` + 1 WHERE `id` = '{$iItemId}'");
48                 // and print out last 10 comments
49                 $sOut '';
50                 $aComments $GLOBALS['MySQL']->getAll("SELECT * FROM `s281_items_cmts` WHERE `c_item_id` = '{$iItemId}' ORDER BY `c_when` DESC LIMIT 10");
51                 foreach ($aComments as $i => $aCmtsInfo) {
52                     $sWhen date('F j, Y H:i'$aCmtsInfo['c_when']);
53                     $sOut .= <<<EOF
54 <div class="comment" id="{$aCmtsInfo['c_id']}">
55     <p>Comment from {$aCmtsInfo['c_name']} <span>({$sWhen})</span>:</p>
56     <p>{$aCmtsInfo['c_text']}</p>
57 </div>
58 EOF;
59                 }
60                 return $sOut;
61             }
62         }
63         return 1;
64     }
65     // get visitor IP
66     function getVisitorIP() {
67         $ip "0.0.0.0";
68         if( ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) && ( !empty$_SERVER['HTTP_X_FORWARDED_FOR'] ) ) ) {
69             $ip $_SERVER['HTTP_X_FORWARDED_FOR'];
70         elseif( ( isset( $_SERVER['HTTP_CLIENT_IP'])) && (!empty($_SERVER['HTTP_CLIENT_IP'] ) ) ) {
71             $ip explode(".",$_SERVER['HTTP_CLIENT_IP']);
72             $ip $ip[3].".".$ip[2].".".$ip[1].".".$ip[0];
73         elseif((!isset( $_SERVER['HTTP_X_FORWARDED_FOR'])) || (empty($_SERVER['HTTP_X_FORWARDED_FOR']))) {
74             if ((!isset( $_SERVER['HTTP_CLIENT_IP'])) && (empty($_SERVER['HTTP_CLIENT_IP']))) {
75                 $ip $_SERVER['REMOTE_ADDR'];
76             }
77         }
78         return $ip;
79     }
80 }
81 $GLOBALS['MyComments'] = new CMyComments();
82 ?>

This class performs two main functions – it can accept new comments and also it can give us a box with comments. There are two more service classes: CMySQL.php and Services_JSON.php. They are two known classes to work with database and json. You can adjust database settings in database class. Both classes available in our package.

Step 3. Javascript

Now we should prepare user interface behavior with using javascript, please prepare next file for the project:

js/script.js

01 // close photo preview block
02 function closePhotoPreview() {
03     $('#photo_preview').hide();
04     $('#photo_preview .pleft').html('empty');
05     $('#photo_preview .pright').html('empty');
06 };
07 // display photo preview block
08 function getPhotoPreviewAjx(id) {
09     $.post('photos_ajx.php', {action: 'get_info', id: id},
10         function(data){
11             $('#photo_preview .pleft').html(data.data1);
12             $('#photo_preview .pright').html(data.data2);
13             $('#photo_preview').show();
14         }, "json"
15     );
16 };
17 // submit comment
18 function submitComment(id) {
19     var sName = $('#name').val();
20     var sText = $('#text').val();
21     if (sName && sText) {
22         $.post('index.php', { action: 'accept_comment', name: sName, text: sText, id: id },
23             function(data){
24                 if (data != '1') {
25                     $('#comments_list').fadeOut(1000, function () {
26                         $(this).html(data);
27                         $(this).fadeIn(1000);
28                     });
29                 else {
30                     $('#comments_warning2').fadeIn(1000, function () {
31                         $(this).fadeOut(1000);
32                     });
33                 }
34             }
35         );
36     else {
37         $('#comments_warning1').fadeIn(1000, function () {
38             $(this).fadeOut(1000);
39         });
40     }
41 };
42 // init
43 $(function(){
44     // onclick event handlers
45     $('#photo_preview .photo_wrp').click(function (event) {
46         event.preventDefault();
47         return false;
48     });
49     $('#photo_preview').click(function (event) {
50         closePhotoPreview();
51     });
52     $('#photo_preview img.close').click(function (event) {
53         closePhotoPreview();
54     });
55     // display photo preview ajaxy
56     $('.container .photo img').click(function (event) {
57         if (event.preventDefault) event.preventDefault();
58         getPhotoPreviewAjx($(this).attr('id'));
59     });
60 })

Please note, we use jQuery instructions in our script (I hope that you haven’t forgot that we linked jQuery library in the header section through google service).

Step 4. CSS

In the long run, we should stylize our page elements (our container with photos, photo preview area with comments):

css/main.css

001 /* project styles */
002 .container {
003     border1px solid #111111;
004     color#000000;
005     margin20px auto;
006     overflowhidden;
007     padding15px;
008     positionrelative;
009     text-aligncenter;
010     width1090px;
011     -moz-border-radius: 5px;
012     -ms-border-radius: 5px;
013     -o-border-radius: 5px;
014     -webkit-border-radius: 5px;
015     border-radius: 5px;
016 }
017 .photo {
018     border1px solid transparent;
019     floatleft;
020     margin4px;
021     overflowhidden;
022     padding4px;
023     white-spacenowrap;
024     /* CSS3 Box sizing property */
025     -moz-box-sizing: border-box;
026     -webkit-box-sizing: border-box;
027     -o-box-sizing: border-box;
028     box-sizing: border-box;
029     /* CSS3 transition */
030     -moz-transition: border 0.2s ease 0s;
031     -ms-transition: border 0.2s ease 0s;
032     -o-transition: border 0.2s ease 0s;
033     -webkit-transition: border 0.2s ease 0s;
034     transition: border 0.2s ease 0s;
035 }
036 .photo:hover {
037     border-color#444;
038 }
039 .photo img {
040     cursorpointer;
041     width200px;
042 }
043 .photo p, .photo i {
044     displayblock;
045 }
046 .photo p {
047     font-weightbold;
048 }
049 /* preview styles */
050 #photo_preview {
051     background-color: rgba(0000.7);
052     bottom0;
053     color#000000;
054     displaynone;
055     left0;
056     overflowhidden;
057     positionfixed;
058     right0;
059     top0;
060     z-index10;
061 }
062 .photo_wrp {
063     background-color#FAFAFA;
064     heightauto;
065     margin100px auto 0;
066     overflowhidden;
067     padding15px;
068     text-aligncenter;
069     vertical-alignmiddle;
070     width1000px;
071     -moz-border-radius: 5px;
072     -ms-border-radius: 5px;
073     -o-border-radius: 5px;
074     -webkit-border-radius: 5px;
075     border-radius: 5px;
076 }
077 .close {
078     cursorpointer;
079     floatright;
080 }
081 .pleft {
082     floatleft;
083     overflowhidden;
084     positionrelative;
085     width600px;
086 }
087 .pright {
088     floatright;
089     positionrelative;
090     width360px;
091 }
092 .preview_prev, .preview_next {
093     cursorpointer;
094     margin-top-64px;
095     opacity: 0.5;
096     positionabsolute;
097     top50%;
098     -moz-transition: opacity 0.2s ease 0s;
099     -ms-transition: opacity 0.2s ease 0s;
100     -o-transition: opacity 0.2s ease 0s;
101     -webkit-transition: opacity 0.2s ease 0s;
102     transition: opacity 0.2s ease 0s;
103 }
104 .preview_prev:hover, .preview_next:hover {
105     opacity: 1;
106 }
107 .preview_prev {
108     left20px;
109 }
110 .preview_next {
111     right40px;
112 }
113 /* comments styles */
114 #comments form {
115     margin10px 0;
116     text-alignleft;
117 }
118 #comments table td.label {
119     color#000;
120     font-size13px;
121     padding-right3px;
122     text-alignright;
123     width105px;
124 }
125 #comments table label {
126     color#000;
127     font-size16px;
128     font-weightnormal;
129     vertical-alignmiddle;
130 }
131 #comments table td.field input, #comments table td.field textarea {
132     border1px solid #96A6C5;
133     font-familyVerdana,Arial,sans-serif;
134     font-size16px;
135     margin-top2px;
136     padding6px;
137     width250px;
138 }
139 #comments_list {
140     margin10px 0;
141     text-alignleft;
142 }
143 #comments_list .comment {
144     border-top1px solid #000;
145     padding10px 0;
146 }
147 #comments_list .comment:first-child {
148     border-top-width:0px;
149 }
150 #comments_list .comment span {
151     font-size11px;
152 }

Live Demo

Conclusion

And again, we have just prepared our next practically useful tutorial. Sure that this material will useful for your own projects. Good luck in your work!

Rate article