How to create Pinterest-like script – step 5
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.
CREATE TABLE IF NOT EXISTS `pd_photos` ( `id` int(10) unsigned NOT NULL auto_increment, `title` varchar(255) default '', `filename` varchar(255) default '', `owner` int(11) NOT NULL, `when` int(11) NOT NULL default '0', `comments_count` int(11) NOT NULL default '0', `repin_id` int(11) NOT NULL default '0', `repin_count` int(11) NOT NULL default '0', `like_count` int(11) NOT NULL default '0', PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
If you want only update your existed table, please execute only this small SQL:
ALTER TABLE `pd_photos` ADD `repin_id` int(11) NOT NULL default '0', ADD `repin_count` int(11) NOT NULL default '0', ADD `like_count` int(11) NOT NULL default '0';
Finally, I prepared one new SQL table to keep likes:
CREATE TABLE IF NOT EXISTS `pd_items_likes` ( `l_id` int(11) NOT NULL AUTO_INCREMENT , `l_item_id` int(12) NOT NULL default '0', `l_pid` int(12) NOT NULL default '0', `l_when` int(11) NOT NULL default '0', PRIMARY KEY (`l_id`), KEY `l_item_id` (`l_item_id`) ) 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
require_once('classes/CMySQL.php'); require_once('classes/CMembers.php'); require_once('classes/CPhotos.php'); // get login data list ($sLoginMenu, $sExtra) = $GLOBALS['CMembers']->getLoginData(); // get search keyword (if provided) $sSearchParam = strip_tags($_GET['q']); // get all photos $sPhotos = $GLOBALS['CPhotos']->getAllPhotos(0, $sSearchParam); if ($sSearchParam) { $sExtra .= '<h2 class="pname">Search results for <strong>'.$sSearchParam.'</strong></h2>'; } // draw common page $aKeys = array( '{menu_elements}' => $sLoginMenu, '{extra_data}' => $sExtra, '{images_set}' => $sPhotos ); 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
require_once('classes/CMySQL.php'); require_once('classes/CMembers.php'); require_once('classes/CPhotos.php'); require_once('classes/CComments.php'); if (! isset($_SESSION['member_id']) && $_POST['Join'] == 'Join') { $GLOBALS['CMembers']->registerProfile(); } $i = (int)$_GET['id']; if ($_GET && $_GET['get'] == 'comments') { header('Content-Type: text/html; charset=utf-8'); echo $GLOBALS['Comments']->getComments($i); exit; } if ($_POST) { header('Content-Type: text/html; charset=utf-8'); if ($_SESSION['member_id'] && $_SESSION['member_status'] == 'active' && $_SESSION['member_role']) { switch($_POST['add']) { case 'comment': echo $GLOBALS['Comments']->acceptComment(); exit; break; case 'like': echo $GLOBALS['CPhotos']->acceptLike(); exit; break; case 'repin': echo $GLOBALS['CPhotos']->acceptRepin(); exit; break; } } echo '<h3>Please login first</h3>'; exit; } if (! $i) { // if something is wrong - relocate to error page header('Location: error.php'); exit; } $aPhotoInfo = $GLOBALS['CPhotos']->getPhotoInfo($i); $aOwnerInfo = $GLOBALS['CMembers']->getProfileInfo($aPhotoInfo['owner']); $sOwnerName = ($aOwnerInfo['first_name']) ? $aOwnerInfo['first_name'] : $aOwnerInfo['email']; $sPhotoTitle = $aPhotoInfo['title']; $sPhotoDate = ($aPhotoInfo['repin_id'] == 0) ? 'Uploaded on ' : 'Repinned on '; $sPhotoDate .= $GLOBALS['CPhotos']->formatTime($aPhotoInfo['when']); $sFolder = 'photos/'; $sFullImgPath = $sFolder . 'f_' . $aPhotoInfo['filename']; $aSize = getimagesize($sFullImgPath); // get image info $iWidth = $aSize[0]; $iHeight = $aSize[1]; // repin possibility to logged members $iLoggId = (int)$_SESSION['member_id']; $sActions = ($iLoggId && $aPhotoInfo['owner'] != $iLoggId) ? '<a href="#" class="button repinbutton" onclick="return repinPhoto(this);">Repin</a>' : ''; ?> <div class="pin bigpin" bpin_id="<?= $i ?>"> <div class="owner"> <a href="#" class="button follow_button">Follow</a> <a class="owner_img" href="profile.php?id=<?= $aOwnerInfo['id'] ?>"> <img alt="<?= $sOwnerName ?>" src="images/avatar.jpg" /> </a> <p class="owner_name"><a href="profile.php?id=<?= $aOwnerInfo['id'] ?>"><?= $sOwnerName ?></a></p> <p class="owner_when"><?= $sPhotoDate ?></p> </div> <div class="holder"> <div class="actions"> <?= $sActions ?> </div> <a class="image" href="#" title="<?= $sPhotoTitle ?>"> <img alt="<?= $sPhotoTitle ?>" src="<?= $sFullImgPath ?>" style="width:<?= $iWidth ?>px;height:<?= $iHeight ?>px;" /> </a> </div> <p class="desc"><?= $sPhotoTitle ?></p> <div class="comments"></div> <script> function submitCommentAjx() { $.ajax({ type: 'POST', url: 'service.php', data: 'add=comment&id=' + <?= $i ?> + '&comment=' + $('#pcomment').val(), cache: false, success: function(html){ if (html) { $('.comments').html(html); $(this).colorbox.resize(); } } }); } function repinPhoto(obj) { var iPinId = $(obj).parent().parent().parent().attr('bpin_id'); $.ajax({ url: 'service.php', type: 'POST', data: 'add=repin&id=' + iPinId, cache: false, success: function(res){ window.location.href = 'profile.php?id=' + res; } }); return false; } </script> <form class="comment" method="post" action="#"> <textarea placeholder="Add a comment..." maxlength="255" id="pcomment"></textarea> <button type="button" class="button" onclick="return submitCommentAjx()">Comment</button> </form> </div>
Next updated file is the main Photos class:
classes/CPhotos.php
/* * Photos class */ class CPhotos { // constructor function CPhotos() { } // get all photos function getAllPhotos($iPid = 0, $sKeyPar = '') { // prepare WHERE filter $aWhere = array(); if ($iPid) { $aWhere[] = "`owner` = '{$iPid}'"; } if ($sKeyPar != '') { $sKeyword = $GLOBALS['MySQL']->escape($sKeyPar); $aWhere[] = "`title` LIKE '%{$sKeyword}%'"; } $sFilter = (count($aWhere)) ? 'WHERE ' . implode(' AND ', $aWhere) : ''; $sSQL = " SELECT * FROM `pd_photos` {$sFilter} ORDER BY `when` DESC "; $aPhotos = $GLOBALS['MySQL']->getAll($sSQL); $sPhotos = ''; $sFolder = 'photos/'; foreach ($aPhotos as $i => $aPhoto) { $iPhotoId = (int)$aPhoto['id']; $sFile = $aPhoto['filename']; $sTitle = $aPhoto['title']; $iCmts = (int)$aPhoto['comments_count']; $iLoggId = (int)$_SESSION['member_id']; $iOwner = (int)$aPhoto['owner']; $iRepins = (int)$aPhoto['repin_count']; $iLikes = (int)$aPhoto['like_count']; $sActions = ($iLoggId && $iOwner != $iLoggId) ? '<a href="#" class="button repinbutton">Repin</a><a href="#" class="button likebutton">Like</a>' : ''; $aPathInfo = pathinfo($sFolder . $sFile); $sExt = strtolower($aPathInfo['extension']); $sImages .= <<<EOL <!-- pin element {$iPhotoId} --> <div class="pin" pin_id="{$iPhotoId}"> <div class="holder"> <div class="actions"> {$sActions} <a href="#" class="button comment_tr">Comment</a> </div> <a class="image ajax" href="service.php?id={$iPhotoId}" title="{$sTitle}"> <img alt="{$sTitle}" src="{$sFolder}{$sFile}"> </a> </div> <p class="desc">{$sTitle}</p> <p class="info"> <span class="LikesCount"><strong>{$iLikes}</strong> likes</span> <span>{$iRepins} repins</span> <span>{$iCmts} comments</span> </p> <form class="comment" method="post" action="" style="display: none" onsubmit="return submitComment(this, {$iPhotoId})"> <textarea placeholder="Add a comment..." maxlength="255" name="comment"></textarea> <input type="submit" class="button" value="Comment" /> </form> </div> EOL; } return $sImages; } // get certain photo info function getPhotoInfo($i) { $sSQL = "SELECT * FROM `pd_photos` WHERE `id` = '{$i}'"; $aInfos = $GLOBALS['MySQL']->getAll($sSQL); return $aInfos[0]; } // format time by timestamp function formatTime($iSec) { $sFormat = 'j F Y'; return gmdate($sFormat, $iSec); } // insert a new blank photo into DB function insertBlankPhoto($sTitle, $iOwner) { $sTitle = $GLOBALS['MySQL']->escape($sTitle); $iOwner = (int)$iOwner; $sSQL = "INSERT INTO `pd_photos` SET `title` = '{$sTitle}', `owner` = '{$iOwner}', `when` = UNIX_TIMESTAMP()"; $GLOBALS['MySQL']->res($sSQL); return $GLOBALS['MySQL']->lastId(); } // update filename function updateFilename($i, $sFilename) { $sFilename = $GLOBALS['MySQL']->escape($sFilename); $sSQL = "UPDATE `pd_photos` SET `filename` = '{$sFilename}' WHERE `id`='{$i}'"; return $GLOBALS['MySQL']->res($sSQL); } function acceptLike() { $iItemId = (int)$_POST['id']; // prepare necessary information $iLoggId = (int)$_SESSION['member_id']; if ($iItemId && $iLoggId) { // check - if there is any recent record from the same person for last 1 hour $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"); if (! $iOldId) { // if everything is fine - we can add a new like $GLOBALS['MySQL']->res("INSERT INTO `pd_items_likes` SET `l_item_id` = '{$iItemId}', `l_pid` = '{$iLoggId}', `l_when` = UNIX_TIMESTAMP()"); // and update total amount of likes $GLOBALS['MySQL']->res("UPDATE `pd_photos` SET `like_count` = `like_count` + 1 WHERE `id` = '{$iItemId}'"); } // and return total amount of likes return (int)$GLOBALS['MySQL']->getOne("SELECT `like_count` FROM `pd_photos` WHERE `id` = '{$iItemId}'"); } } function acceptRepin() { $iItemId = (int)$_POST['id']; // prepare necessary information $iLoggId = (int)$_SESSION['member_id']; if ($iItemId && $iLoggId) { $aPhotoInfo = $this->getPhotoInfo($iItemId); // check - for already repinned element $iOldId = $GLOBALS['MySQL']->getOne("SELECT `id` FROM `pd_photos` WHERE `owner` = '{$iLoggId}' AND `repin_id` = '{$iItemId}'"); if (! $iOldId) { // if everything is fine - add a copy of photo as own photo (repin) $sSQL = "INSERT INTO `pd_photos` SET `title` = '{$aPhotoInfo['title']}', `filename` = '{$aPhotoInfo['filename']}', `owner` = '{$iLoggId}', `when` = UNIX_TIMESTAMP(), `repin_id` = '{$iItemId}' "; $GLOBALS['MySQL']->res($sSQL); // update repin count for original photo $GLOBALS['MySQL']->res("UPDATE `pd_photos` SET `repin_count` = `repin_count` + 1 WHERE `id` = '{$iItemId}'"); } // and return current member id return $iLoggId; } } } $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
// onclick event handler (for like button) $('.pin .actions .likebutton').click(function () { $(this).attr('disabled', 'disabled'); var iPinId = $(this).parent().parent().parent().attr('pin_id'); $.ajax({ url: 'service.php', type: 'POST', data: 'add=like&id=' + iPinId, cache: false, success: function(res){ $('.pin[pin_id='+iPinId+'] .info .LikesCount strong').text(res); } }); return false; }); // onclick event handler (for repin button) $('.pin .actions .repinbutton').click(function () { var iPinId = $(this).parent().parent().parent().attr('pin_id'); $.ajax({ url: 'service.php', type: 'POST', data: 'add=repin&id=' + iPinId, cache: false, success: function(res){ window.location.href = 'profile.php?id=' + res; } }); return false; });
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!
Thanks so much sir for this tutorials am very happy u created this tutorial since my project is on image, i think i can get some ideals here to work with ;)
Hello Andrew,I am a student from China.I pay attention to your site for a long time.I have learned a lot here though I am a green-hand in HTML and CSS.
ah…My English is so poor,but I still want to show my thanks to you.
???????????????????????????“???”?
????????????????????????SQL?PHP?????????????????
?????????????????????????????????????
maybe you can understand the sentences above with the help of Google translater.
Finally,happy new year! Although there are still several days before Chinese traditional new year.
Hello Zhang,
Thank you for your letter, well (I’ve translated your comments in Chinese), yes, you can use our script on your own private website.
I love your website and was hoping if there is a way to include facebook login system with it
Hi Manuel,
I think that ‘facebook login’ could be done as separated lesson. Actually, you can try to read this article before: https://script-tutorials.com/facebook-api-get-friends-list/
thanks for this tutorial
is there a way to make the script runs profile page with username
like site.com/profilename
Yes, sure, if you want to use permalinks, you can add it as a rule for your root .htaccess file.
in this case, you will need to pass usernames a a GET param (for profile page)
Thank You So much ,,,for givings us ur huge knowledge…..
i learn many thiung from here infact its amazing web-site…:):):)
i’v downloaded the package and installed probably, but i found an issue opening the thumb image !. it keeps loading , could you tell me what i’v to add\edit? thanks
Hi kosh,
By default, it displays pre-defined avatar image (images/avatar.jpg). If you’d like to display here custom avatars for your members – you’re welcome to implement this functionality.
@admin you didn’t get what i mean.
in my previous post i asked when i launched this tutorial everything works but whenever i click on an image thumb the original image doesn’t show up, it keep loading but never show up. ! could you help me with it thanks. btw your tutorials are awesome
Hello kosh,
Ah, I see, then, I think that this would be better to investigate your server response. It should be denied or time outed. Firebug should help you to understand it.
Hello,
Thanks for this material. But we will can see step 6? (follow and url upload). This is will possible?
Sorry my english not good.
Yes, sure, I have not stopped writing articles about pinterest
hi,,
How to submit video ?
Great work !
Hello Esteban,
Videos? Hum, this was made especially for photos. If you need a similar solution for videos, so, it is possible too. In this case – you have to start developing it
Hello Admin,
Can you help me to add infinite scrolling to the code you have given for pinterest.
I have tried a lot but no code is working.
Hello Apoorv,
The infinite scroll was added on step 6, you can check it
I’m also facing the same prob as Kosh, when I click on thumbnail to view full size of image.. it just keeps on loading.. it doesn’t even show any default image… how can I overcome this problem ?? in your demo it is working perfectly..
Hi Akki,
I am curious why it so, it shouldn’t be so. Try to investigate a server response (in firebug). Maybe your server refuses ajax connections, or it generates an error in response.
To resolve the same probleme i have change in service.php (in html divs) the php string : “<?=" in every occurrence by this "<?php print" i and now all work fine!
Great tutorial I must say, it would be more good if you add ” Create Board and add pin to the board” similar as Board in Pinterest.
I continue to get these errors in any location where there is a redirect in the service.php, CMembers.php.
Warning: Cannot modify header information – headers already sent by (output started at C:\Inetpub\vhosts\easyemailflyers.com\site3\sources328\service.php:1) in C:\Inetpub\vhosts\easyemailflyers.com\site3\sources328\service.php on line 21
Any ideas?
Hello,
Make sure that your first line of the service.php file is not empty line, try to open this file in Notepad++
I want to appreciate you for providing such great tutorials…!