• Reading time:13 mins read
How to create Pinterest-like script - step 5

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!