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:
[sociallocker]
[/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 | `when` int(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', |
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', |
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'); |
05 | list ($sLoginMenu, $sExtra) = $GLOBALS['CMembers']->getLoginData(); |
07 | $sSearchParam = strip_tags($_GET['q']); |
09 | $sPhotos = $GLOBALS['CPhotos']->getAllPhotos(0, $sSearchParam); |
11 | $sExtra .= '<h2 class="pname">Search results for <strong>'.$sSearchParam.'</strong></h2>'; |
15 | '{menu_elements}' => $sLoginMenu, |
16 | '{extra_data}' => $sExtra, |
17 | '{images_set}' => $sPhotos |
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(); |
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); |
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']) { |
019 | echo $GLOBALS['Comments']->acceptComment(); exit; |
022 | echo $GLOBALS['CPhotos']->acceptLike(); exit; |
025 | echo $GLOBALS['CPhotos']->acceptRepin(); exit; |
029 | echo '<h3>Please login first</h3>'; |
033 | header('Location: error.php'); |
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); |
046 | $iHeight = $aSize[1]; |
048 | $iLoggId = (int)$_SESSION['member_id']; |
049 | $sActions = ($iLoggId && $aPhotoInfo['owner'] != $iLoggId) ? '<a href="#" class="button repinbutton" onclick="return repinPhoto(this);">Repin</a>' : ''; |
051 | <div class="pin bigpin" bpin_id="<?= $i ?>"> |
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" /> |
057 | <p class="owner_name"><a href="profile.php?id=<?= $aOwnerInfo['id'] ?>"><?= $sOwnerName ?></a></p> |
058 | <p class="owner_when"><?= $sPhotoDate ?></p> |
061 | <div class="actions"> |
064 | <a class="image" href="#" title="<?= $sPhotoTitle ?>"> |
065 | <img alt="<?= $sPhotoTitle ?>" src="<?= $sFullImgPath ?>" style="width:<?= $iWidth ?>px;height:<?= $iHeight ?>px;" /> |
068 | <p class="desc"><?= $sPhotoTitle ?></p> |
069 | <div class="comments"></div> |
071 | function submitCommentAjx() { |
075 | data: 'add=comment&id=' + <?= $i ?> + '&comment=' + $('#pcomment').val(), |
077 | success: function(html){ |
079 | $('.comments').html(html); |
080 | $(this).colorbox.resize(); |
085 | function repinPhoto(obj) { |
086 | var iPinId = $(obj).parent().parent().parent().attr('bpin_id'); |
090 | data: 'add=repin&id=' + iPinId, |
092 | success: function(res){ |
093 | window.location.href = 'profile.php?id=' + res; |
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> |
Next updated file is the main Photos class:
classes/CPhotos.php
009 | function getAllPhotos($iPid = 0, $sKeyPar = '') { |
013 | $aWhere[] = "`owner` = '{$iPid}'"; |
015 | if ($sKeyPar != '') { |
016 | $sKeyword = $GLOBALS['MySQL']->escape($sKeyPar); |
017 | $aWhere[] = "`title` LIKE '%{$sKeyword}%'"; |
019 | $sFilter = (count($aWhere)) ? 'WHERE ' . implode(' AND ', $aWhere) : ''; |
026 | $aPhotos = $GLOBALS['MySQL']->getAll($sSQL); |
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']); |
042 | <!-- pin element {$iPhotoId} --> |
043 | <div class="pin" pin_id="{$iPhotoId}"> |
045 | <div class="actions"> |
047 | <a href="#" class="button comment_tr">Comment</a> |
049 | <a class="image ajax" href="service.php?id={$iPhotoId}" title="{$sTitle}"> |
050 | <img alt="{$sTitle}" src="{$sFolder}{$sFile}"> |
053 | <p class="desc">{$sTitle}</p> |
055 | <span class="LikesCount"><strong>{$iLikes}</strong> likes</span> |
056 | <span>{$iRepins} repins</span> |
057 | <span>{$iCmts} comments</span> |
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" /> |
069 | function getPhotoInfo($i) { |
070 | $sSQL = "SELECT * FROM `pd_photos` WHERE `id` = '{$i}'"; |
071 | $aInfos = $GLOBALS['MySQL']->getAll($sSQL); |
075 | function formatTime($iSec) { |
077 | return gmdate($sFormat, $iSec); |
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(); |
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); |
093 | function acceptLike() { |
094 | $iItemId = (int)$_POST['id']; |
095 | $iLoggId = (int)$_SESSION['member_id']; |
096 | if ($iItemId && $iLoggId) { |
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"); |
101 | $GLOBALS['MySQL']->res("INSERT INTO `pd_items_likes` SET `l_item_id` = '{$iItemId}', `l_pid` = '{$iLoggId}', `l_when` = UNIX_TIMESTAMP()"); |
103 | $GLOBALS['MySQL']->res("UPDATE `pd_photos` SET `like_count` = `like_count` + 1 WHERE `id` = '{$iItemId}'"); |
106 | return (int)$GLOBALS['MySQL']->getOne("SELECT `like_count` FROM `pd_photos` WHERE `id` = '{$iItemId}'"); |
109 | function acceptRepin() { |
110 | $iItemId = (int)$_POST['id']; |
111 | $iLoggId = (int)$_SESSION['member_id']; |
112 | if ($iItemId && $iLoggId) { |
113 | $aPhotoInfo = $this->getPhotoInfo($iItemId); |
115 | $iOldId = $GLOBALS['MySQL']->getOne("SELECT `id` FROM `pd_photos` WHERE `owner` = '{$iLoggId}' AND `repin_id` = '{$iItemId}'"); |
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}' |
125 | $GLOBALS['MySQL']->res($sSQL); |
127 | $GLOBALS['MySQL']->res("UPDATE `pd_photos` SET `repin_count` = `repin_count` + 1 WHERE `id` = '{$iItemId}'"); |
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
02 | $('.pin .actions .likebutton').click(function () { |
03 | $(this).attr('disabled', 'disabled'); |
04 | var iPinId = $(this).parent().parent().parent().attr('pin_id'); |
08 | data: 'add=like&id=' + iPinId, |
10 | success: function(res){ |
11 | $('.pin[pin_id='+iPinId+'] .info .LikesCount strong').text(res); |
17 | $('.pin .actions .repinbutton').click(function () { |
18 | var iPinId = $(this).parent().parent().parent().attr('pin_id'); |
22 | data: 'add=repin&id=' + iPinId, |
24 | success: function(res){ |
25 | window.location.href = 'profile.php?id=' + res; |
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).
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!