How to create Pinterest-like script – step 4

Tutorials

Welcome our friends and sorry for the delay, I had to do a lot of things in real life. But in any case, I am glad to give you our fourth lesson where we create our own Pinterest like script. Today I added the ajaxy comment system and profile pages here. We can now write our own comments (this is available for logged users) directly on our pins, or on larger versions (popups)of images as well.
Today I will publish all updated files of our script, in case if you want to investigate it at your local computer – you always can download full package with sources.

Now you are welcome to try our demo and download the source package here:

Live Demo

[sociallocker]

download in package

[/sociallocker]


Step 1. HTML

As I told, I added profile pages. As usual, I’m going to push whole html layout into single template file (so, you will be able to modify it easily):

templates/profile.html

01 <!DOCTYPE html>
02 <html lang="en" >
03     <head>
04         <meta charset="utf-8" />
05         <meta name="author" content="Script Tutorials" />
06         <title>How to create Pinterest-like script - step 4 | Script Tutorials</title>
07         <!-- add styles -->
08         <link href="css/main.css" rel="stylesheet" type="text/css" />
09         <link href="css/colorbox.css" rel="stylesheet" type="text/css" />
10         <!-- add scripts -->
11         <script src="js/jquery.min.js"></script>
12         <script src="js/jquery.colorbox-min.js"></script>
13         <script src="js/jquery.masonry.min.js"></script>
14         <script src="js/script.js"></script>
15     </head>
16     <body>
17         <!-- header panel -->
18         <div class="header_panel">
19             <!-- logo -->
20             <a href="index.php" class="logo"></a>
21             <!-- search form -->
22             <form action="" method="get" class="search">
23                 <input autocomplete="off" name="q" size="27" placeholder="Search" type="text" />
24                 <input name="search" type="submit" />
25             </form>
26             <!-- navigation menu -->
27             <ul class="nav">
28                 <li>
29                     <a href="#">About<span></span></a>
30                     <ul>
31                         <li><a href="#">Help</a></li>
32                         <li><a href="#">Pin It Button</a></li>
33                         <li><a href="#" target="_blank">For Businesses</a></li>
34                         <li class="div"><a href="#">Careers</a></li>
35                         <li><a href="#">Team</a></li>
36                         <li><a href="#">Blog</a></li>
37                         <li class="div"><a href="#">Terms of Service</a></li>
38                         <li><a href="#">Privacy Policy</a></li>
39                         <li><a href="#">Copyright</a></li>
40                         <li><a href="#">Trademark</a></li>
41                     </ul>
42                 </li>
43                 {menu_elements}
44                 <li>
45                     <a href="https://www.script-tutorials.com/pinterest-like-script-step-4/">Back to tutorial</a>
46                 </li>
47             </ul>
48         </div>
49         {extra_data}
50         <h2 class="pname">Profile {profile_name}</h2>
51         <!-- main container -->
52         <div class="main_container">
53             {images_set}
54         </div>
55     </body>
56 </html>

Step 2. PHP

Now, please check PHP code of our profile page:

profile.php

01 // set warning level
02 if (version_compare(phpversion(), '5.3.0''>=')  == 1)
03   error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
04 else
05   error_reporting(E_ALL & ~E_NOTICE);
06 require_once('classes/CMySQL.php');
07 require_once('classes/CMembers.php');
08 require_once('classes/CPhotos.php');
09 // get login data
10 list ($sLoginMenu$sExtra) = $GLOBALS['CMembers']->getLoginData();
11 // profile id
12 $i = (int)$_GET['id'];
13 if ($i) {
14     $aMemberInfo $GLOBALS['CMembers']->getProfileInfo($_SESSION['member_id']);
15     if ($aMemberInfo) {
16         // get all photos by profile
17         $sPhotos $GLOBALS['CPhotos']->getAllPhotos($i);
18         // draw profile page
19         $aKeys array(
20             '{menu_elements}' => $sLoginMenu,
21             '{extra_data}' => $sExtra,
22             '{images_set}' => $sPhotos,
23             '{profile_name}' => $aMemberInfo['first_name'],
24         );
25         echo strtr(file_get_contents('templates/profile.html'), $aKeys);
26         exit;
27     }
28 }
29 header('Location: error.php');

This is very easy file as you see, it is similar as our index page, except for one – this page displays a profile name and his photos only. Now, in order to display his photos only, I had to modify CPhotos::getAllPhotos function. There is updated version:

01     function getAllPhotos($iPid = 0) {
02         $sFilter = ($iPid) ? "WHERE `owner` = '{$iPid}'" '';
03         $sSQL = "
04             SELECT *
05             FROM `pd_photos`
06             {$sFilter}
07             ORDER BY `when` DESC
08         ";
09         $aPhotos $GLOBALS['MySQL']->getAll($sSQL);
10         $sPhotos '';
11         $sFolder 'photos/';
12         foreach ($aPhotos as $i => $aPhoto) {
13             $iPhotoId = (int)$aPhoto['id'];
14             $sFile $aPhoto['filename'];
15             $sTitle $aPhoto['title'];
16             $iCmts = (int)$aPhoto['comments_count'];
17             $aPathInfo pathinfo($sFolder $sFile);
18             $sExt strtolower($aPathInfo['extension']);
19             $sImages .= <<<EOL
20 <!-- pin element {$iPhotoId} -->
21 <div class="pin" pin_id="{$iPhotoId}">
22     <div class="holder">
23         <div class="actions">
24             <a href="#" class="button">Repin</a>
25             <a href="#" class="button">Like</a>
26             <a href="#" class="button comment_tr">Comment</a>
27         </div>
28         <a class="image ajax" href="service.php?id={$iPhotoId}" title="{$sTitle}">
29             <img alt="{$sTitle}" src="{$sFolder}{$sFile}">
30         </a>
31     </div>
32     <p class="desc">{$sTitle}</p>
33     <p class="info">
34         <span>XX likes</span>
35         <span>XX repins</span>
36         <span>{$iCmts} comments</span>
37     </p>
38     <form class="comment" method="post" action="" style="display: none" onsubmit="return submitComment(this, {$iPhotoId})">
39         <textarea placeholder="Add a comment..." maxlength="255" name="comment"></textarea>
40         <input type="submit" class="button" value="Comment" />
41     </form>
42 </div>
43 EOL;
44         }
45         return $sImages;
46     }

Basically – I only added profile filter here, nothing more. Then, I prepared a new class for Comments:

classes/CComments.php

01 /*
02 * Comments class
03 */
04 class CComments {
05     // constructor
06     function CComments() {
07     }
08     // return comments block
09     function getComments($i) {
10         // draw last 5 comments
11         $sComments '';
12         $aComments $GLOBALS['MySQL']->getAll("SELECT * FROM `pd_items_cmts` WHERE `c_item_id` = '{$i}' ORDER BY `c_when` DESC LIMIT 5");
13         foreach ($aComments as $iCmtId => $aCmtsInfo) {
14             $sWhen date('F j, Y H:i'$aCmtsInfo['c_when']);
15             $sComments .= <<<EOF
16 <div class="comment" id="{$aCmtsInfo['c_id']}">
17     <p>Comment from {$aCmtsInfo['c_name']} <span>({$sWhen})</span>:</p>
18     <p>{$aCmtsInfo['c_text']}</p>
19 </div>
20 EOF;
21         }
22         return $sComments;
23     }
24     function acceptComment() {
25         $iItemId = (int)$_POST['id']; // prepare necessary information
26         $sIp $this->getVisitorIP();
27         $aMemberInfo $GLOBALS['CMembers']->getProfileInfo($_SESSION['member_id']);
28         $sName $GLOBALS['MySQL']->escape(strip_tags($aMemberInfo['first_name']));
29         $sText $GLOBALS['MySQL']->escape(strip_tags($_POST['comment']));
30         if ($iItemId && $sName && strlen($sText) > 2) {
31             // check - if there is any recent comment from the same person (IP) or not (for last 5 mins)
32             $iOldId $GLOBALS['MySQL']->getOne("SELECT `c_item_id` FROM `pd_items_cmts` WHERE `c_item_id` = '{$iItemId}' AND `c_ip` = '{$sIp}' AND `c_when` >= UNIX_TIMESTAMP() - 300 LIMIT 1");
33             if (! $iOldId) {
34                 // if everything is fine - allow to add comment
35                 $GLOBALS['MySQL']->res("INSERT INTO `pd_items_cmts` SET `c_item_id` = '{$iItemId}', `c_ip` = '{$sIp}', `c_when` = UNIX_TIMESTAMP(), `c_name` = '{$sName}', `c_text` = '{$sText}'");
36                 $GLOBALS['MySQL']->res("UPDATE `pd_photos` SET `comments_count` = `comments_count` + 1 WHERE `id` = '{$iItemId}'");
37                 // and print out last 5 comments
38                 $sOut '';
39                 $aComments $GLOBALS['MySQL']->getAll("SELECT * FROM `pd_items_cmts` WHERE `c_item_id` = '{$iItemId}' ORDER BY `c_when` DESC LIMIT 5");
40                 foreach ($aComments as $i => $aCmtsInfo) {
41                     $sWhen date('F j, Y H:i'$aCmtsInfo['c_when']);
42                     $sOut .= <<<EOF
43 <div class="comment" id="{$aCmtsInfo['c_id']}">
44     <p>Comment from {$aCmtsInfo['c_name']} <span>({$sWhen})</span>:</p>
45     <p>{$aCmtsInfo['c_text']}</p>
46 </div>
47 EOF;
48                 }
49                 return $sOut;
50             }
51         }
52     }
53     // get visitor IP
54     function getVisitorIP() {
55         $ip "0.0.0.0";
56         if( ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) && ( !empty$_SERVER['HTTP_X_FORWARDED_FOR'] ) ) ) {
57             $ip $_SERVER['HTTP_X_FORWARDED_FOR'];
58         elseif( ( isset( $_SERVER['HTTP_CLIENT_IP'])) && (!empty($_SERVER['HTTP_CLIENT_IP'] ) ) ) {
59             $ip explode(".",$_SERVER['HTTP_CLIENT_IP']);
60             $ip $ip[3].".".$ip[2].".".$ip[1].".".$ip[0];
61         elseif((!isset( $_SERVER['HTTP_X_FORWARDED_FOR'])) || (empty($_SERVER['HTTP_X_FORWARDED_FOR']))) {
62             if ((!isset( $_SERVER['HTTP_CLIENT_IP'])) && (empty($_SERVER['HTTP_CLIENT_IP']))) {
63                 $ip $_SERVER['REMOTE_ADDR'];
64             }
65         }
66         return $ip;
67     }
68 }
69 $GLOBALS['Comments'] = new CComments();

There are only three functions now: getVisitorIP (to determinate IP of visitors), getComments (to return comments, by default – we display 5 last comments) and acceptComment (to accept comments and add them into database). Finally, I had to modify our service file:

service.php

01 // set warning level
02 if (version_compare(phpversion(), '5.3.0''>=')  == 1)
03   error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
04 else
05   error_reporting(E_ALL & ~E_NOTICE);
06 require_once('classes/CMySQL.php');
07 require_once('classes/CMembers.php');
08 require_once('classes/CPhotos.php');
09 require_once('classes/CComments.php');
10 if (! isset($_SESSION['member_id']) && $_POST['Join'] == 'Join') {
11     $GLOBALS['CMembers']->registerProfile();
12 }
13 $i = (int)$_GET['id'];
14 if ($_GET && $_GET['get'] == 'comments') {
15     header('Content-Type: text/html; charset=utf-8');
16     echo $GLOBALS['Comments']->getComments($i);
17     exit;
18 }
19 if ($_POST && $_POST['add'] == 'comment') {
20     if ($_SESSION['member_id'] && $_SESSION['member_status'] == 'active' && $_SESSION['member_role']) {
21         header('Content-Type: text/html; charset=utf-8');
22         echo $GLOBALS['Comments']->acceptComment();
23         exit;
24     }
25     header('Content-Type: text/html; charset=utf-8');
26     echo '<h3>Please login first</h3>';
27     exit;
28 }
29 if (! $i) { // if something is wrong - relocate to error page
30     header('Location: error.php');
31     exit;
32 }
33 $aPhotoInfo $GLOBALS['CPhotos']->getPhotoInfo($i);
34 $aOwnerInfo $GLOBALS['CMembers']->getProfileInfo($aPhotoInfo['owner']);
35 $sOwnerName = ($aOwnerInfo['first_name']) ? $aOwnerInfo['first_name'] : $aOwnerInfo['email'];
36 $sPhotoTitle $aPhotoInfo['title'];
37 $sPhotoDate $GLOBALS['CPhotos']->formatTime($aPhotoInfo['when']);
38 $sFolder 'photos/';
39 $sFullImgPath $sFolder 'f_' $aPhotoInfo['filename'];
40 $aSize getimagesize($sFullImgPath); // get image info
41 $iWidth $aSize[0];
42 $iHeight $aSize[1];
43 ?>
44 <div class="pin bigpin">
45     <div class="owner">
46         <a href="#" class="button follow_button">Follow</a>
47         <a class="owner_img" href="profile.php?id=<?= $aOwnerInfo['id'] ?>">
48             <img alt="<?= $sOwnerName ?>" src="images/avatar.jpg" />
49         </a>
50         <p class="owner_name"><a href="profile.php?id=<?= $aOwnerInfo['id'] ?>"><?= $sOwnerName ?></a></p>
51         <p class="owner_when">Uploaded on <?= $sPhotoDate ?></p>
52     </div>
53     <div class="holder">
54         <div class="actions">
55             <a href="#" class="button">Repin</a>
56             <a href="#" class="button">Like</a>
57         </div>
58         <a class="image" href="#" title="<?= $sPhotoTitle ?>">
59             <img alt="<?= $sPhotoTitle ?>" src="<?= $sFullImgPath ?>" style="width:<?= $iWidth ?>px;height:<?= $iHeight ?>px;" />
60         </a>
61     </div>
62     <p class="desc"><?= $sPhotoTitle ?></p>
63     <div class="comments"></div>
64     <script>
65     function submitCommentAjx() {
66         $.ajax({
67           type: 'POST',
68           url: 'service.php',
69           data: 'add=comment&id=' + <?= $i ?> + '&comment=' + $('#pcomment').val(),
70           cache: false,
71           success: function(html){
72             if (html) {
73               $('.comments').html(html);
74               $(this).colorbox.resize();
75             }
76           }
77         });
78     }
79     </script>
80     <form class="comment" method="post" action="#">
81         <textarea placeholder="Add a comment..." maxlength="255" id="pcomment"></textarea>
82         <button type="button" class="button" onclick="return submitCommentAjx()">Comment</button>
83     </form>
84 </div>

Now we can handle with comments! We use $.ajax function to POST comments to server. As soon as we post a new comment – server returns us (in response) 5 last comments, so we can update a list of last comments for an object (pin). Also, please pay attention, that our profiles have own links now (profile.php).

Step 3. CSS

I added only one little CSS property to our profile page (add this in the end of our styles file):

css/main.css

1 /* profile page */
2 .pname {
3     font-size24px;
4     margin-bottom10px;
5     margin-top20px;
6     text-aligncenter;
7 }

Step 4. Javascript

I updated our main script file. There are few changes: a new function to submit comments to the server, and, – to load comments (ajaxy) once we have opened a bigger photo using Colorbox.

js/script.js

01 function fileSelectHandler() {
02     // get selected file
03     var oFile = $('#image_file')[0].files[0];
04     // html5 file upload
05     var formData = new FormData($('#upload_form')[0]);
06     $.ajax({
07         url: 'upload.php'//server script to process data
08         type: 'POST',
09         // ajax events
10         beforeSend: function() {
11         },
12         success: function(e) {
13             $('#upload_result').html('Thank you for your photo').show();
14             setTimeout(function() {
15                 $("#upload_result").hide().empty();
16                 location.reload();
17             }, 4000);
18         },
19         error: function(e) {
20             $('#upload_result').html('Error while processing uploaded image');
21         },
22         // form data
23         data: formData,
24         // options to tell JQuery not to process data or worry about content-type
25         cache: false,
26         contentType: false,
27         processData: false
28     });
29 }
30 function submitComment(form, id) {
31     $.ajax({
32       type: 'POST',
33       url: 'service.php',
34       data: 'add=comment&id=' + id + '&comment=' + $(form).find('textarea').val(),
35       cache: false,
36       success: function(html){
37         if (html) {
38           location.reload();
39         }
40       }
41     });
42     return false;
43 }
44 $(document).ready(function(){
45     // file field change handler
46     $('#image_file').change(function(){
47         var file = this.files[0];
48         name = file.name;
49         size = file.size;
50         type = file.type;
51         // extra validation
52         if (name && size)  {
53             if (! file.type.match('image.*')) {
54                 alert("Select image please");
55             else {
56                 fileSelectHandler();
57             }
58         }
59     });
60     // masonry initialization
61     $('.main_container').masonry({
62         // options
63         itemSelector : '.pin',
64         isAnimated: true,
65         isFitWidth: true
66     });
67     // onclick event handler (for comments)
68     $('.comment_tr').click(function () {
69         $(this).toggleClass('disabled');
70         $(this).parent().parent().parent().find('form.comment').slideToggle(400, function () {
71             $('.main_container').masonry();
72         });
73     });
74     $('.ajax').colorbox({
75         onOpen:function(){
76         },
77         onLoad:function(){
78         },
79         onComplete:function(){
80             $(this).colorbox.resize();
81             var iPinId = $(this).parent().parent().attr('pin_id');
82             $.ajax({
83               url: 'service.php',
84               data: 'get=comments&id=' + iPinId,
85               cache: false,
86               success: function(html){
87                 $('.comments').append(html);
88                 $(this).colorbox.resize();
89               }
90             });
91         },
92         onCleanup:function(){
93         },
94         onClosed:function(){
95         }
96     });
97 });

Live Demo

Conclusion

We have just finished our fourth lesson where we writing our own Pinterest-like script. I hope you enjoy this series. It would be kind of you to share our materials with your friends. Good luck and welcome back!

Rate article