How to create Pinterest-like script – step 5

Tutorials

Today – the first article in 2013. We are about to finish our Pinterest like script. In our fifth lesson I prepared next things: like and repin functionality and search. As you know, ‘like’ is a kind of a rating system. In our script – any logged member can rate any certain photo (to like it) once a hour (it is a protection system against cheating).

If you like a photo and want to add it to your profile – you can click ‘repin’ button. This will add a copy of this photo for you (actually – only a new record to database). As for the search – everything is easy: we prepared this search bar long ago, but it has not worked before. I added this functionality today. We are going to publish updated sources of our script in our lesson. If you are ready – let’s start.

It is the very time to try our updated demonstration and download the source package here:

Live Demo

[sociallocker]

download in package

[/sociallocker]


Step 1. SQL

In order to implement like counter and repin functionality we have to expand our `pd_photos` table.

01 CREATE TABLE IF NOT EXISTS `pd_photos` (
02   `id` int(10) unsigned NOT NULL auto_increment,
03   `title` varchar(255) default '',
04   `filename` varchar(255) default '',
05   `owner` int(11) NOT NULL,
06   `whenint(11) NOT NULL default '0',
07   `comments_count` int(11) NOT NULL default '0',
08   `repin_id` int(11) NOT NULL default '0',
09   `repin_count` int(11) NOT NULL default '0',
10   `like_count` int(11) NOT NULL default '0',
11   PRIMARY KEY  (`id`)
12 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;

If you want only update your existed table, please execute only this small SQL:

1 ALTER TABLE `pd_photos`
2  ADD `repin_id` int(11) NOT NULL default '0',
3  ADD `repin_count` int(11) NOT NULL default '0',
4  ADD `like_count` int(11) NOT NULL default '0';

Finally, I prepared one new SQL table to keep likes:

1 CREATE TABLE IF NOT EXISTS `pd_items_likes` (
2   `l_id` int(11) NOT NULL AUTO_INCREMENT ,
3   `l_item_id` int(12) NOT NULL default '0',
4   `l_pid` int(12) NOT NULL default '0',
5   `l_when` int(11) NOT NULL default '0',
6   PRIMARY KEY (`l_id`),
7   KEY `l_item_id` (`l_item_id`)
8 ) ENGINE=MYISAM DEFAULT CHARSET=utf8;

Step 2. PHP

I decided to display search result at our ‘index.php’ page, we need to make few minor changes here. Here is updated version:

index.php

01 require_once('classes/CMySQL.php');
02 require_once('classes/CMembers.php');
03 require_once('classes/CPhotos.php');
04 // get login data
05 list ($sLoginMenu$sExtra) = $GLOBALS['CMembers']->getLoginData();
06 // get search keyword (if provided)
07 $sSearchParam strip_tags($_GET['q']);
08 // get all photos
09 $sPhotos $GLOBALS['CPhotos']->getAllPhotos(0, $sSearchParam);
10 if ($sSearchParam) {
11     $sExtra .= '<h2 class="pname">Search results for <strong>'.$sSearchParam.'</strong></h2>';
12 }
13 // draw common page
14 $aKeys array(
15     '{menu_elements}' => $sLoginMenu,
16     '{extra_data}' => $sExtra,
17     '{images_set}' => $sPhotos
18 );
19 echo strtr(file_get_contents('templates/index.html'), $aKeys);

Now, as you remember, we use ‘service.php’ file to perform various service methods. Please review our updated version (where I added possibilities to work with ‘like’ and ‘repin’ buttons:

service.php

001 require_once('classes/CMySQL.php');
002 require_once('classes/CMembers.php');
003 require_once('classes/CPhotos.php');
004 require_once('classes/CComments.php');
005 if (! isset($_SESSION['member_id']) && $_POST['Join'] == 'Join') {
006     $GLOBALS['CMembers']->registerProfile();
007 }
008 $i = (int)$_GET['id'];
009 if ($_GET && $_GET['get'] == 'comments') {
010     header('Content-Type: text/html; charset=utf-8');
011     echo $GLOBALS['Comments']->getComments($i);
012     exit;
013 }
014 if ($_POST) {
015     header('Content-Type: text/html; charset=utf-8');
016     if ($_SESSION['member_id'] && $_SESSION['member_status'] == 'active' && $_SESSION['member_role']) {
017         switch($_POST['add']) {
018             case 'comment':
019                 echo $GLOBALS['Comments']->acceptComment(); exit;
020                 break;
021             case 'like':
022                 echo $GLOBALS['CPhotos']->acceptLike(); exit;
023                 break;
024             case 'repin':
025                 echo $GLOBALS['CPhotos']->acceptRepin(); exit;
026                 break;
027         }
028     }
029     echo '<h3>Please login first</h3>';
030     exit;
031 }
032 if (! $i) { // if something is wrong - relocate to error page
033     header('Location: error.php');
034     exit;
035 }
036 $aPhotoInfo $GLOBALS['CPhotos']->getPhotoInfo($i);
037 $aOwnerInfo $GLOBALS['CMembers']->getProfileInfo($aPhotoInfo['owner']);
038 $sOwnerName = ($aOwnerInfo['first_name']) ? $aOwnerInfo['first_name'] : $aOwnerInfo['email'];
039 $sPhotoTitle $aPhotoInfo['title'];
040 $sPhotoDate = ($aPhotoInfo['repin_id'] == 0) ? 'Uploaded on ' 'Repinned on ';
041 $sPhotoDate .= $GLOBALS['CPhotos']->formatTime($aPhotoInfo['when']);
042 $sFolder 'photos/';
043 $sFullImgPath $sFolder 'f_' $aPhotoInfo['filename'];
044 $aSize getimagesize($sFullImgPath); // get image info
045 $iWidth $aSize[0];
046 $iHeight $aSize[1];
047 // repin possibility to logged members
048 $iLoggId = (int)$_SESSION['member_id'];
049 $sActions = ($iLoggId && $aPhotoInfo['owner'] != $iLoggId) ? '<a href="#" class="button repinbutton" onclick="return repinPhoto(this);">Repin</a>' '';
050 ?>
051 <div class="pin bigpin" bpin_id="<?= $i ?>">
052     <div class="owner">
053         <a href="#" class="button follow_button">Follow</a>
054         <a class="owner_img" href="profile.php?id=<?= $aOwnerInfo['id'] ?>">
055             <img alt="<?= $sOwnerName ?>" src="images/avatar.jpg" />
056         </a>
057         <p class="owner_name"><a href="profile.php?id=<?= $aOwnerInfo['id'] ?>"><?= $sOwnerName ?></a></p>
058         <p class="owner_when"><?= $sPhotoDate ?></p>
059     </div>
060     <div class="holder">
061         <div class="actions">
062             <?= $sActions ?>
063         </div>
064         <a class="image" href="#" title="<?= $sPhotoTitle ?>">
065             <img alt="<?= $sPhotoTitle ?>" src="<?= $sFullImgPath ?>" style="width:<?= $iWidth ?>px;height:<?= $iHeight ?>px;" />
066         </a>
067     </div>
068     <p class="desc"><?= $sPhotoTitle ?></p>
069     <div class="comments"></div>
070     <script>
071     function submitCommentAjx() {
072         $.ajax({
073           type: 'POST',
074           url: 'service.php',
075           data: 'add=comment&id=' + <?= $i ?> + '&comment=' + $('#pcomment').val(),
076           cache: false,
077           success: function(html){
078             if (html) {
079               $('.comments').html(html);
080               $(this).colorbox.resize();
081             }
082           }
083         });
084     }
085     function repinPhoto(obj) {
086         var iPinId = $(obj).parent().parent().parent().attr('bpin_id');
087         $.ajax({
088           url: 'service.php',
089           type: 'POST',
090           data: 'add=repin&id=' + iPinId,
091           cache: false,
092           success: function(res){
093             window.location.href = 'profile.php?id=' + res;
094           }
095         });
096         return false;
097     }
098     </script>
099     <form class="comment" method="post" action="#">
100         <textarea placeholder="Add a comment..." maxlength="255" id="pcomment"></textarea>
101         <button type="button" class="button" onclick="return submitCommentAjx()">Comment</button>
102     </form>
103 </div>

Next updated file is the main Photos class:

classes/CPhotos.php

001 /*
002 * Photos class
003 */
004 class CPhotos {
005     // constructor
006     function CPhotos() {
007     }
008     // get all photos
009     function getAllPhotos($iPid = 0, $sKeyPar '') {
010         // prepare WHERE filter
011         $aWhere array();
012         if ($iPid) {
013             $aWhere[] = "`owner` = '{$iPid}'";
014         }
015         if ($sKeyPar != '') {
016             $sKeyword $GLOBALS['MySQL']->escape($sKeyPar);
017             $aWhere[] = "`title` LIKE '%{$sKeyword}%'";
018         }
019         $sFilter = (count($aWhere)) ? 'WHERE ' . implode(' AND '$aWhere) : '';
020         $sSQL = "
021             SELECT *
022             FROM `pd_photos`
023             {$sFilter}
024             ORDER BY `when` DESC
025         ";
026         $aPhotos $GLOBALS['MySQL']->getAll($sSQL);
027         $sPhotos '';
028         $sFolder 'photos/';
029         foreach ($aPhotos as $i => $aPhoto) {
030             $iPhotoId = (int)$aPhoto['id'];
031             $sFile $aPhoto['filename'];
032             $sTitle $aPhoto['title'];
033             $iCmts = (int)$aPhoto['comments_count'];
034             $iLoggId = (int)$_SESSION['member_id'];
035             $iOwner = (int)$aPhoto['owner'];
036             $iRepins = (int)$aPhoto['repin_count'];
037             $iLikes = (int)$aPhoto['like_count'];
038             $sActions = ($iLoggId && $iOwner != $iLoggId) ? '<a href="#" class="button repinbutton">Repin</a><a href="#" class="button likebutton">Like</a>' '';
039             $aPathInfo pathinfo($sFolder $sFile);
040             $sExt strtolower($aPathInfo['extension']);
041             $sImages .= <<<EOL
042 <!-- pin element {$iPhotoId} -->
043 <div class="pin" pin_id="{$iPhotoId}">
044     <div class="holder">
045         <div class="actions">
046             {$sActions}
047             <a href="#" class="button comment_tr">Comment</a>
048         </div>
049         <a class="image ajax" href="service.php?id={$iPhotoId}" title="{$sTitle}">
050             <img alt="{$sTitle}" src="{$sFolder}{$sFile}">
051         </a>
052     </div>
053     <p class="desc">{$sTitle}</p>
054     <p class="info">
055         <span class="LikesCount"><strong>{$iLikes}</strong> likes</span>
056         <span>{$iRepins} repins</span>
057         <span>{$iCmts} comments</span>
058     </p>
059     <form class="comment" method="post" action="" style="display: none" onsubmit="return submitComment(this, {$iPhotoId})">
060         <textarea placeholder="Add a comment..." maxlength="255" name="comment"></textarea>
061         <input type="submit" class="button" value="Comment" />
062     </form>
063 </div>
064 EOL;
065         }
066         return $sImages;
067     }
068     // get certain photo info
069     function getPhotoInfo($i) {
070         $sSQL "SELECT * FROM `pd_photos` WHERE `id` = '{$i}'";
071         $aInfos $GLOBALS['MySQL']->getAll($sSQL);
072         return $aInfos[0];
073     }
074     // format time by timestamp
075     function formatTime($iSec) {
076         $sFormat 'j F Y';
077         return gmdate($sFormat$iSec);
078     }
079     // insert a new blank photo into DB
080     function insertBlankPhoto($sTitle$iOwner) {
081         $sTitle $GLOBALS['MySQL']->escape($sTitle);
082         $iOwner = (int)$iOwner;
083         $sSQL "INSERT INTO `pd_photos` SET `title` = '{$sTitle}', `owner` = '{$iOwner}', `when` = UNIX_TIMESTAMP()";
084         $GLOBALS['MySQL']->res($sSQL);
085         return $GLOBALS['MySQL']->lastId();
086     }
087     // update filename
088     function updateFilename($i$sFilename) {
089         $sFilename $GLOBALS['MySQL']->escape($sFilename);
090         $sSQL "UPDATE `pd_photos` SET `filename` = '{$sFilename}' WHERE `id`='{$i}'";
091         return $GLOBALS['MySQL']->res($sSQL);
092     }
093     function acceptLike() {
094         $iItemId = (int)$_POST['id']; // prepare necessary information
095         $iLoggId = (int)$_SESSION['member_id'];
096         if ($iItemId && $iLoggId) {
097             // check - if there is any recent record from the same person for last 1 hour
098             $iOldId $GLOBALS['MySQL']->getOne("SELECT `l_item_id` FROM `pd_items_likes` WHERE `l_item_id` = '{$iItemId}' AND `l_pid` = '{$iLoggId}' AND `l_when` >= UNIX_TIMESTAMP() - 3600 LIMIT 1");
099             if (! $iOldId) {
100                 // if everything is fine - we can add a new like
101                 $GLOBALS['MySQL']->res("INSERT INTO `pd_items_likes` SET `l_item_id` = '{$iItemId}', `l_pid` = '{$iLoggId}', `l_when` = UNIX_TIMESTAMP()");
102                 // and update total amount of likes
103                 $GLOBALS['MySQL']->res("UPDATE `pd_photos` SET `like_count` = `like_count` + 1 WHERE `id` = '{$iItemId}'");
104             }
105             // and return total amount of likes
106             return (int)$GLOBALS['MySQL']->getOne("SELECT `like_count` FROM `pd_photos` WHERE `id` = '{$iItemId}'");
107         }
108     }
109     function acceptRepin() {
110         $iItemId = (int)$_POST['id']; // prepare necessary information
111         $iLoggId = (int)$_SESSION['member_id'];
112         if ($iItemId && $iLoggId) {
113             $aPhotoInfo $this->getPhotoInfo($iItemId);
114             // check - for already repinned element
115             $iOldId $GLOBALS['MySQL']->getOne("SELECT `id` FROM `pd_photos` WHERE `owner` = '{$iLoggId}' AND `repin_id` = '{$iItemId}'");
116             if (! $iOldId) {
117                 // if everything is fine - add a copy of photo as own photo (repin)
118                 $sSQL = "INSERT INTO `pd_photos` SET
119                             `title` = '{$aPhotoInfo['title']}',
120                             `filename` = '{$aPhotoInfo['filename']}',
121                             `owner` = '{$iLoggId}',
122                             `when` = UNIX_TIMESTAMP(),
123                             `repin_id` = '{$iItemId}'
124                 ";
125                 $GLOBALS['MySQL']->res($sSQL);
126                 // update repin count for original photo
127                 $GLOBALS['MySQL']->res("UPDATE `pd_photos` SET `repin_count` = `repin_count` + 1 WHERE `id` = '{$iItemId}'");
128             }
129             // and return current member id
130             return $iLoggId;
131         }
132     }
133 }
134 $GLOBALS['CPhotos'] = new CPhotos();

You can see, that I modified ‘getAllPhotos’ function. now it can handle with search params, plus, it displays amounts of repins and counts. Since today – repin and like buttons are available only for logged members. You can also find here two new functions ‘acceptLike’ and ‘acceptRepin’. First one is to accept likes, second one is to do ‘repin’. As you see – it just makes a single record to database (a copy of repinned object), but with a link to original photo (repin_id field).

Step 3. Javascript

I updated our main javascript file. There are only two new event handlers:

js/script.js

01 // onclick event handler (for like button)
02 $('.pin .actions .likebutton').click(function () {
03     $(this).attr('disabled''disabled');
04     var iPinId = $(this).parent().parent().parent().attr('pin_id');
05     $.ajax({
06       url: 'service.php',
07       type: 'POST',
08       data: 'add=like&id=' + iPinId,
09       cache: false,
10       success: function(res){
11         $('.pin[pin_id='+iPinId+'] .info .LikesCount strong').text(res);
12       }
13     });
14     return false;
15 });
16 // onclick event handler (for repin button)
17 $('.pin .actions .repinbutton').click(function () {
18     var iPinId = $(this).parent().parent().parent().attr('pin_id');
19     $.ajax({
20       url: 'service.php',
21       type: 'POST',
22       data: 'add=repin&id=' + iPinId,
23       cache: false,
24       success: function(res){
25         window.location.href = 'profile.php?id=' + res;
26       }
27     });
28     return false;
29 });

The main idea it to use jQuery ajax to send necessary information about liked or repined photo to our ‘service.php’ server file. Once we click ‘like’ button, we send Photo ID, and then – server returns us total amount of likes for this photo, then we can update ‘like’ counter. The situation is similar for ‘repin’ button. We send photo id to server, once it ‘repins’ selected photo – it relocates us to our profile page (where we can see a result).


Live Demo

Conclusion

We have just finished our fifth lesson where we are 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