How to create Pinterest-like script – step 3

I hope you’re looking forward to a new lesson. For today I prepared few important changes. For the first – this is a new login and registration system for our script, the second – since today we are going to use database to keep information about members, photos, and for future comments. Now, you need to log in to be able to upload photos. Today I’ll publish all updated files of our script, in case if you want to investigate everything at your local computer – you always can download full package with sources.

Now you can check our demo and download the sources here:

Live Demo

[sociallocker]

download in package

[/sociallocker]


Step 1. SQL

To better understand how it works – let’s look at all the new database tables:

01 CREATE TABLE `pd_profiles` (
02   `id` int(10) unsigned NOT NULL auto_increment,
03   `first_name` varchar(255) NOT NULL default '',
04   `last_name` varchar(255) NOT NULL default '',
05   `email` varchar(255) NOT NULL default '',
06   `password` varchar(40) NOT NULL default '',
07   `salt` varchar(10) NOT NULL default '',
08   `status` enum('active','passive') NOT NULL default 'active',
09   `role` tinyint(4) unsigned NOT NULL default '1',
10   `date_reg` datetime NOT NULL default '0000-00-00 00:00:00',
11   PRIMARY KEY  (`id`)
12 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
13 INSERT INTO `pd_profiles` (`first_name`, `last_name`, `email`, `password`, `salt`, `status`, `role`, `date_reg`) VALUES
14 ('test user first name', 'test user last name', '[email protected]', 'b88c654d6c68fc37f4dda1d29935235eea9a845b', 'testing', 'active', 1, NOW()),
15 ('moderator first name', 'moderator last name', '[email protected]', 'b88c654d6c68fc37f4dda1d29935235eea9a845b', 'testing', 'active', 2, NOW()),
16 ('admin first name', 'admin last name', '[email protected]', 'b88c654d6c68fc37f4dda1d29935235eea9a845b', 'testing', 'active', 3, NOW()),
17 ('test user 2 first name', 'test user 2 last name', '[email protected]', 'b88c654d6c68fc37f4dda1d29935235eea9a845b', 'testing', 'active', 1, NOW());
18 CREATE TABLE IF NOT EXISTS `pd_photos` (
19   `id` int(10) unsigned NOT NULL auto_increment,
20   `title` varchar(255) default '',
21   `filename` varchar(255) default '',
22   `owner` int(11) NOT NULL,
23   `when` int(11) NOT NULL default '0',
24   `comments_count` int(11) NOT NULL default '0',
25   PRIMARY KEY  (`id`)
26 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
27 INSERT INTO `pd_photos` (`title`, `filename`, `owner`, `when`) VALUES
28 ('Item #1', 'pic1.jpg', 1, UNIX_TIMESTAMP()),
29 ('Item #2', 'pic2.jpg', 2, UNIX_TIMESTAMP()+1),
30 ('Item #3', 'pic3.jpg', 3, UNIX_TIMESTAMP()+2),
31 ('Item #4', 'pic4.jpg', 4, UNIX_TIMESTAMP()+3);
32 CREATE TABLE IF NOT EXISTS `pd_items_cmts` (
33   `c_id` int(11) NOT NULL AUTO_INCREMENT ,
34   `c_item_id` int(12) NOT NULL default '0',
35   `c_ip` varchar(20) default NULL,
36   `c_name` varchar(64) default '',
37   `c_text` text NOT NULL ,
38   `c_when` int(11) NOT NULL default '0',
39   PRIMARY KEY (`c_id`),
40   KEY `c_item_id` (`c_item_id`)
41 ) ENGINE=MYISAM DEFAULT CHARSET=utf8;

There are three tables, the first one (pd_profiles) keeps information about members, the second one (pd_photos) keep info about all photos, and the third one is for comments. Please pay attention, that all members have the same password: ‘password’. You have to use email and password to login into system.

Step 2. HTML

I made small corrections in our ‘index.html’ template file. As you know, we don’t have to display upload form for usual visitors, it is only for logged in members. But, we have to display login and join forms for visitors. Please look at updated html markup of our index page:

templates/index.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 3 | 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="#" 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-3/">Back to tutorial</a>
46                 </li>
47             </ul>
48         </div>
49         {extra_data}
50         <!-- main container -->
51         <div class="main_container">
52             {images_set}
53         </div>
54     </body>
55 </html>

As you see – I added two new template keys: {menu_elements} – this keys will contain extra join and login forms for visitors, and Upload, Profile and Logout menu elements for logged-in members. Our new key {extra_data} contains join and login forms for visitors, and upload form for members. All these forms – pure CSS-driven forms.

Step 3. PHP

As you might imagine – all our php files were modified. Let’s start with main index file:

index.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 // get all photos
12 $sPhotos $GLOBALS['CPhotos']->getAllPhotos();
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);

As you see – it is much smaller now. But in the same time you can see that I added three new classes: CMySQL (to work with database), CMembers (to work with members) and CPhotos (to work with photos). Well, this file displays Login and Join forms for visitors and Upload form for active members. Also, we display all photos at this page. The second updated file is:

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 if (! isset($_SESSION['member_id']) && $_POST['Join'] == 'Join') {
10     $GLOBALS['CMembers']->registerProfile();
11 }
12 $i = (int)$_GET['id'];
13 if (! $i) { // if something is wrong - relocate to error page
14     header('Location: error.php');
15     exit;
16 }
17 $aPhotoInfo $GLOBALS['CPhotos']->getPhotoInfo($i);
18 $aOwnerInfo $GLOBALS['CMembers']->getProfileInfo($aPhotoInfo['owner']);
19 $sOwnerName = ($aOwnerInfo['first_name']) ? $aOwnerInfo['first_name'] : $aOwnerInfo['email'];
20 $sPhotoTitle $aPhotoInfo['title'];
21 $sPhotoDate $GLOBALS['CPhotos']->formatTime($aPhotoInfo['when']);
22 $sFolder 'photos/';
23 $sFullImgPath $sFolder 'f_' $aPhotoInfo['filename'];
24 ?>
25 <div class="pin bigpin">
26     <div class="owner">
27         <a href="#" class="button follow_button">Follow</a>
28         <a target="_blank" class="owner_img" href="#">
29             <img alt="<?= $sOwnerName ?>" src="images/avatar.jpg" />
30         </a>
31         <p class="owner_name"><a target="_blank" href="#"><?= $sOwnerName ?></a></p>
32         <p class="owner_when">Uploaded on <?= $sPhotoDate ?></p>
33     </div>
34     <div class="holder">
35         <div class="actions">
36             <a href="#" class="button">Repin</a>
37             <a href="#" class="button">Like</a>
38         </div>
39         <a class="image" href="#" title="<?= $sPhotoTitle ?>">
40             <img alt="<?= $sPhotoTitle ?>" src="<?= $sFullImgPath ?>">
41         </a>
42     </div>
43     <p class="desc"><?= $sPhotoTitle ?></p>
44     <div class="comments"></div>
45     <form class="comment" method="post" action="#">
46         <input type="hidden" name="id" value="0" />
47         <textarea placeholder="Add a comment..." maxlength="1000"></textarea>
48         <button type="button" class="button">Comment</button>
49     </form>
50 </div>

I just added an user registration. In addition, we do not need to pass the full name of the file to see larger version. Instead, we pass the image ID. The next updated file is uploader:

upload.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 function uploadImageFile() { // Note: GD library is required for this upload function
10     $iDefWidth = 192; // default photos width (in case of resize)
11     $iFDefWidth = 556; // full default photos width (in case of resize)
12     if ($_SERVER['REQUEST_METHOD'] == 'POST') {
13         $iWidth $iHeight $iFDefWidth// desired image dimensions
14         $iJpgQuality = 75;
15         if ($_FILES) {
16             // if there are no errors and filesize less than 400kb
17             if (! $_FILES['image_file']['error'] && $_FILES['image_file']['size'] < 400 * 1024) {
18                 if (is_uploaded_file($_FILES['image_file']['tmp_name'])) {
19                     // new unique filename
20                     $sTempFileName 'photos/' . md5(time().rand());
21                     // move uploaded file into cache folder
22                     move_uploaded_file($_FILES['image_file']['tmp_name'], $sTempFileName);
23                     // change file permission to 644
24                     @chmod($sTempFileName, 0644);
25                     // if temp file exists
26                     if (file_exists($sTempFileName) && filesize($sTempFileName) > 0) {
27                         $aSize getimagesize($sTempFileName); // obtain image info
28                         if (!$aSize) {
29                             @unlink($sTempFileName);
30                             return;
31                         }
32                         // check for image type and create a new image from file
33                         switch($aSize[2]) {
34                             case IMAGETYPE_JPEG:
35                                 $sExt '.jpg';
36                                 $vImg = @imagecreatefromjpeg($sTempFileName);
37                                 break;
38                             case IMAGETYPE_PNG:
39                                 $sExt '.png';
40                                 $vImg = @imagecreatefrompng($sTempFileName);
41                                 break;
42                             default:
43                                 @unlink($sTempFileName);
44                                 return;
45                         }
46                         // get source image width and height
47                         $iSrcWidth = imagesx($vImg);
48                         $iSrcHeight = imagesy($vImg);
49                         // recalculate height (depends on width)
50                         $iHeight $iSrcHeight $iWidth $iSrcWidth;
51                         // create a new true color image
52                         $vDstImg = @imagecreatetruecolor($iWidth$iHeight);
53                         // copy and resize
54                         imagecopyresampled($vDstImg$vImg, 0, 0, 0, 0, $iWidth$iHeight$iSrcWidth$iSrcHeight);
55                         // add a blank image object into DB
56                         $iLastId $GLOBALS['CPhotos']->insertBlankPhoto($_FILES['image_file']['name'], $_SESSION['member_id']);
57                         // define a result image filename
58                         $sResultFileName 'photos/f_pic' $iLastId $sExt;
59                         // update filename for our object
60                         $GLOBALS['CPhotos']->updateFilename($iLastId'pic' $iLastId $sExt);
61                         // output image to file and set permission 644
62                         imagejpeg($vDstImg$sResultFileName$iJpgQuality);
63                         @chmod($sResultFileName, 0644);
64                         // and, prepare a thumbnail as well
65                         $iWidth $iDefWidth;
66                         $iHeight $iSrcHeight $iWidth $iSrcWidth;
67                         $vDstThImg = @imagecreatetruecolor($iWidth$iHeight);
68                         imagecopyresampled($vDstThImg$vImg, 0, 0, 0, 0, $iWidth$iHeight$iSrcWidth$iSrcHeight);
69                         $sResultThumnName 'photos/pic' $iLastId $sExt;
70                         imagejpeg($vDstThImg$sResultThumnName$iJpgQuality);
71                         @chmod($sResultThumnName, 0644);
72                         // unlink temp file
73                         @unlink($sTempFileName);
74                         return $sResultFileName;
75                     }
76                 }
77             }
78         }
79     }
80 }
81 // upload available only for logged in members
82 if ($_SESSION['member_id'] && $_SESSION['member_status'] == 'active' && $_SESSION['member_role']) {
83     $sImage = uploadImageFile();
84     echo '1';
85 }

Now, only logged members can upload photos (as I told in the beginning). Well, I think that now it is important to review all our new classes as well:

classes/CMembers.php

001 /*
002 * Members class
003 */
004 class CMembers {
005     // constructor
006     function CMembers() {
007         session_start();
008     }
009     // get login box function
010     function getLoginData() {
011         if (isset($_GET['logout'])) { // logout process
012             if (isset($_SESSION['member_email']) && isset($_SESSION['member_pass']))
013                 $this->performLogout();
014         }
015         if ($_POST && $_POST['email'] && $_POST['password']) { // login process
016             if ($this->checkLogin($_POST['email'], $_POST['password'], false)) { // successful login
017                 $this->performLogin($_POST['email'], $_POST['password']);
018                 header('Location: index.php');
019                 exit;
020             }
021         else // in case if we are already logged in
022             if (isset($_SESSION['member_email']) && $_SESSION['member_email'] && $_SESSION['member_pass']) {
023                 $aReplaces array(
024                     '{name}' => $_SESSION['member_email'],
025                     '{status}' => $_SESSION['member_status'],
026                     '{role}' => $_SESSION['member_role'],
027                 );
028                 // display Profiles menu and Logout
029                 $sLoginMenu = <<<EOF
030 <li><a href="#add_form" id="add_pop">Add +</a></li>
031 <li>
032     <a href="#">Profile<span></span></a>
033     <ul>
034         <li><a href="#">Invite Friends</a></li>
035         <li><a href="#">Find Friends</a></li>
036         <li class="div"><a href="#">Boards</a></li>
037         <li><a href="#">Pins</a></li>
038         <li><a href="#">Likes</a></li>
039         <li class="div"><a href="#">Settings</a></li>
040         <li><a href="#">Logout</a></li>
041     </ul>
042 </li>
043 <li><a href="index.php?logout">Logout</a></li>
044 EOF;
045         $sExtra = <<<EOF
046 <!-- upload form -->
047 <a href="#x" class="overlay" id="add_form"></a>
048 <div class="popup">
049     <div class="header">
050         <a class="close" href="#close">x</a>
051         <h2>Upload a Pin</h2>
052     </div>
053     <form id="upload_form">
054         <input type="file" name="image_file" id="image_file" onchange="" />
055     </form>
056     <div id="upload_result"></div>
057 </div>
058 EOF;
059                 return array($sLoginMenu$sExtra);
060             }
061         }
062         // display Join and Login menu buttons
063         $sLoginMenu = <<<EOF
064 <li><a href="#join_form" id="join_pop">Join</a></li>
065 <li><a href="#login_form" id="login_pop">Login</a></li>
066 EOF;
067         $sExtra = <<<EOF
068 <!-- join form -->
069 <a href="#x" class="overlay2" id="join_form"></a>
070 <div class="popup">
071     <div class="header">
072         <a class="close" href="#close">x</a>
073         <h2>Create your account</h2>
074     </div>
075     <form method="POST" action="service.php">
076         <ul class="ctrl_grp">
077             <li>
078                 <input type="text" name="email" />
079                 <label>Email Address</label>
080                 <span class="fff"></span>
081             </li>
082             <li>
083                 <input type="password" name="password" />
084                 <label>Password</label>
085                 <span class="fff"></span>
086             </li>
087             <li>
088                 <input type="text" name="first_name" />
089                 <label>First Name</label>
090                 <span class="fff"></span>
091             </li>
092             <li>
093                 <input type="text" name="last_name" />
094                 <label>Last Name</label>
095                 <span class="fff"></span>
096             </li>
097         </ul>
098         <div>
099             <input type="hidden" name="Join" value="Join" />
100             <button class="submit_button" type="submit">Create Account</button>
101         </div>
102     </form>
103 </div>
104 <!-- login form -->
105 <a href="#x" class="overlay3" id="login_form"></a>
106 <div class="popup">
107     <div class="header">
108         <a class="close" href="#close">x</a>
109         <h2>Login</h2>
110     </div>
111     <form method="POST" action="index.php">
112         <ul class="ctrl_grp">
113             <li>
114                 <input type="text" name="email" id="id_email">
115                 <label>Email</label>
116                 <span class="fff"></span>
117             </li>
118             <li>
119                 <input type="password" name="password" id="id_password">
120                 <label>Password</label>
121                 <span class="fff"></span>
122             </li>
123         </ul>
124         <div>
125             <button class="submit_button" type="submit">Login</button>
126         </div>
127     </form>
128 </div>
129 EOF;
130         return array($sLoginMenu$sExtra);
131     }
132     // perform login
133     function performLogin($sEmail$sPass) {
134         $this->performLogout();
135         // make variables safe
136         $sEmail $GLOBALS['MySQL']->escape($sEmail);
137         $aProfile $GLOBALS['MySQL']->getRow("SELECT * FROM `pd_profiles` WHERE `email`='{$sEmail}'");
138         // $sPassEn = $aProfile['password'];
139         $iPid $aProfile['id'];
140         $sSalt $aProfile['salt'];
141         $sStatus $aProfile['status'];
142         $sRole $aProfile['role'];
143         $sPass = sha1(md5($sPass) . $sSalt);
144         $_SESSION['member_id'] = $iPid;
145         $_SESSION['member_email'] = $sEmail;
146         $_SESSION['member_pass'] = $sPass;
147         $_SESSION['member_status'] = $sStatus;
148         $_SESSION['member_role'] = $sRole;
149     }
150     // perform logout
151     function performLogout() {
152         unset($_SESSION['member_id']);
153         unset($_SESSION['member_email']);
154         unset($_SESSION['member_pass']);
155         unset($_SESSION['member_status']);
156         unset($_SESSION['member_role']);
157     }
158     // check login
159     function checkLogin($sEmail$sPass$isHash = true) {
160         // escape variables to make them self
161         $sEmail $GLOBALS['MySQL']->escape($sEmail);
162         $sPass $GLOBALS['MySQL']->escape($sPass);
163         $aProfile $GLOBALS['MySQL']->getRow("SELECT * FROM `pd_profiles` WHERE `email`='{$sEmail}'");
164         $sPassEn $aProfile['password'];
165         if ($sEmail && $sPass && $sPassEn) {
166             if (! $isHash) {
167                 $sSalt $aProfile['salt'];
168                 $sPass = sha1(md5($sPass) . $sSalt);
169             }
170             return ($sPass == $sPassEn);
171         }
172         return false;
173     }
174     // profile registration
175     function registerProfile() {
176         $sFirstname $GLOBALS['MySQL']->escape($_POST['first_name']);
177         $sLastname $GLOBALS['MySQL']->escape($_POST['last_name']);
178         $sEmail $GLOBALS['MySQL']->escape($_POST['email']);
179         $sPassword $GLOBALS['MySQL']->escape($_POST['password']);
180         if ($sEmail && $sPassword) {
181             // check if email is already exists
182             $aProfile $GLOBALS['MySQL']->getRow("SELECT * FROM `pd_profiles` WHERE `email`='{$sEmail}'");
183             if ($aProfile['id'] > 0) {
184                 // relocate to 'error' page
185                 header('Location: error.php');
186             else {
187                 // generate Salt and Cached password
188                 $sSalt $this->getRandSaltCode();
189                 $sPass = sha1(md5($sPassword) . $sSalt);
190                 // add new member into database
191                 $sSQL = "
192                     INSERT INTO `pd_profiles` SET
193                     `first_name` = '{$sFirstname}',
194                     `last_name` = '{$sLastname}',
195                     `email` = '{$sEmail}',
196                     `password` = '{$sPass}',
197                     `salt` = '{$sSalt}',
198                     `status` = 'active',
199                     `role` = '1',
200                     `date_reg` = NOW();
201                 ";
202                 $GLOBALS['MySQL']->res($sSQL);
203                 // autologin
204                 $this->performLogin($sEmail$sPassword);
205                 // relocate back to index page
206                 header('Location: index.php');
207             }
208         else {
209             // otherwise - relocate to error page
210             header('Location: error.php');
211         }
212     }
213     // get random salt code
214     function getRandSaltCode($iLen = 8) {
215         $sRes '';
216         $sChars '23456789abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ';
217         for ($i = 0; $i $iLen$i++) {
218             $z = rand(0, strlen($sChars) -1);
219             $sRes .= $sChars[$z];
220         }
221         return $sRes;
222     }
223     // get certain member info
224     function getProfileInfo($i) {
225         $sSQL = "
226             SELECT *
227             FROM `pd_profiles`
228             WHERE `id` = '{$i}'
229         ";
230         $aInfos $GLOBALS['MySQL']->getAll($sSQL);
231         return $aInfos[0];
232     }
233 }
234 $GLOBALS['CMembers'] = new CMembers();

The main members class contains next functions: getLoginData (this functions does several functions: login and logout processing, also it returns Join & Login menu buttons (with forms) and Upload & Logout buttons (with forms) for members), performLogin (to login), performLogout (to logout), checkLogin (to check login information), registerProfile (to register a new member), getRandSaltCode (to get random salt code), getProfileInfo (to get info about certain member). I think that it is enough functions for members for now. As you noticed, I use a new php file: error.php to generate errors, this is the very easy file:

error.php

01 require_once('classes/CMySQL.php');
02 require_once('classes/CMembers.php');
03 // login system init and generation code
04 list ($sLoginMenu$sExtra) = $GLOBALS['CMembers']->getLoginData();
05 // draw common page
06 $aKeys array(
07     '{menu_elements}' => $sLoginMenu,
08     '{extra_data}' => $sExtra,
09     '{images_set}' => '<center><h1>Error Occurred, please try again</h1></center>'
10 );
11 echo strtr(file_get_contents('templates/index.html'), $aKeys);

…Finally, our last new class for today is:

classes/CPhotos.php

01 /*
02 * Photos class
03 */
04 class CPhotos {
05     // constructor
06     function CPhotos() {
07     }
08     // get all photos
09     function getAllPhotos() {
10         $sSQL = "
11             SELECT *
12             FROM `pd_photos`
13             ORDER BY `when` DESC
14         ";
15         $aPhotos $GLOBALS['MySQL']->getAll($sSQL);
16         $sPhotos '';
17         $sFolder 'photos/';
18         foreach ($aPhotos as $i => $aPhoto) {
19             $iPhotoId = (int)$aPhoto['id'];
20             $sFile $aPhoto['filename'];
21             $sTitle $aPhoto['title'];
22             $aPathInfo pathinfo($sFolder $sFile);
23             $sExt strtolower($aPathInfo['extension']);
24             $sImages .= <<<EOL
25 <!-- pin element {$iPhotoId} -->
26 <div class="pin">
27     <div class="holder">
28         <div class="actions" pin_id="{$iPhotoId}">
29             <a href="#" class="button">Repin</a>
30             <a href="#" class="button">Like</a>
31             <a href="#" class="button comment_tr">Comment</a>
32         </div>
33         <a class="image ajax" href="service.php?id={$iPhotoId}" title="{$sTitle}">
34             <img alt="{$sTitle}" src="{$sFolder}{$sFile}">
35         </a>
36     </div>
37     <p class="desc">{$sTitle}</p>
38     <p class="info">
39         <span>XX likes</span>
40         <span>XX repins</span>
41     </p>
42     <form class="comment" method="post" action="" style="display: none">
43         <input type="hidden" name="id" value="0" />
44         <textarea placeholder="Add a comment..." maxlength="1000"></textarea>
45         <button type="button" class="button">Comment</button>
46     </form>
47 </div>
48 EOL;
49         }
50         return $sImages;
51     }
52     // get certain photo info
53     function getPhotoInfo($i) {
54         $sSQL "SELECT * FROM `pd_photos` WHERE `id` = '{$i}'";
55         $aInfos $GLOBALS['MySQL']->getAll($sSQL);
56         return $aInfos[0];
57     }
58     // format time by timestamp
59     function formatTime($iSec) {
60         $sFormat 'j F Y';
61         return gmdate($sFormat$iSec);
62     }
63     // insert a new blank photo into DB
64     function insertBlankPhoto($sTitle$iOwner) {
65         $sTitle $GLOBALS['MySQL']->escape($sTitle);
66         $iOwner = (int)$iOwner;
67         $sSQL "INSERT INTO `pd_photos` SET `title` = '{$sTitle}', `owner` = '{$iOwner}', `when` = UNIX_TIMESTAMP()";
68         $GLOBALS['MySQL']->res($sSQL);
69         return $GLOBALS['MySQL']->lastId();
70     }
71     // update filename
72     function updateFilename($i$sFilename) {
73         $sFilename $GLOBALS['MySQL']->escape($sFilename);
74         $sSQL "UPDATE `pd_photos` SET `filename` = '{$sFilename}' WHERE `id`='{$i}'";
75         return $GLOBALS['MySQL']->res($sSQL);
76     }
77 }
78 $GLOBALS['CPhotos'] = new CPhotos();

There are several more functions like: getAllPhotos (to get all set of photos for our index page), getPhotoInfo (to get info about certain image), formatTime (to format time for bigger popup version), insertBlankPhoto and updateFilename (to add a new image into database and update filename for that image)

Step 4. CSS

In order to handle with two new forms (Join and Login) I had to update a bit popup styles:

css/main.css

01 /* upload form styles */
02 .overlay, .overlay2, .overlay3 {
03     background-color#FFFFFF;
04     bottom0;
05     displaynone;
06     left0;
07     opacity: 0.8;
08     positionfixed;
09     right0;
10     top0;
11     z-index9;
12 }
13 .overlay:target, .overlay2:target, .overlay3:target {
14     displayblock;
15 }
16 .popup {
17     backgroundnone repeat scroll 0 0 #FCF9F9;
18     border1px solid #F7F5F5;
19     box-shadow: 0 2px 5px rgba(3425250.5);
20     display: inline-block;
21     left50%;
22     padding30px 30px 20px;
23     positionfixed;
24     top40%;
25     visibilityhidden;
26     width550px;
27     z-index10;
28     -webkit-transform: translate(-50%-50%);
29     -moz-transform: translate(-50%-50%);
30     -ms-transform: translate(-50%-50%);
31     -o-transform: translate(-50%-50%);
32     transform: translate(-50%-50%);
33     -webkit-transition: all 0.3s ease-in-out 0s;
34     -moz-transition: all 0.3s ease-in-out 0s;
35     -ms-transition: all 0.3s ease-in-out 0s;
36     -o-transition: all 0.3s ease-in-out 0s;
37     transition: all 0.3s ease-in-out 0s;
38 }
39 .overlay:target+.popup, .overlay2:target+.popup, .overlay3:target+.popup {
40     top50%;
41     opacity: 1 ;
42     visibilityvisible;
43 }

And, I added new styles for our new both forms:

01 /* login & join form styles */
02 .ctrl_grp li {
03     displayblock;
04     font-size21px;
05     list-stylenone outside none;
06     margin-bottom18px;
07     positionrelative;
08 }
09 .ctrl_grp input[type="text"], .ctrl_grp input[type="password"] {
10     background-colortransparent;
11     border1px solid #AD9C9C;
12     border-radius: 6px 6px 6px 6px;
13     box-shadow: 0 1px rgba(3425250.15inset0 1px #FFFFFF;
14     color#221919;
15     displayblock;
16     font-size18px;
17     line-height1.4em;
18     padding6px 12px;
19     positionrelative;
20     transition: all 0.08s ease-in-out 0s;
21     width95%;
22     z-index3;
23 }
24 .ctrl_grp input[type="text"]:focus, .ctrl_grp input[type="password"]:focus {
25     border-color: ##993300;
26     box-shadow: 0 1px rgba(3425250.15inset0 1px rgba(2552552550.8), 0 0 14px rgba(23582820.35);
27 }
28 .ctrl_grp label {
29     -moz-user-select: none;
30     color#EFEFEF;
31     displayblock;
32     font-size18px;
33     left13px;
34     line-height1.4em;
35     positionabsolute;
36     top5px;
37     transition: all 0.16s ease-in-out 0s;
38     z-index2;
39 }
40 .ctrl_grp .fff {
41     background-color#FFFFFF;
42     border-radius: 8px 8px 8px 8px;
43     bottom0;
44     left0;
45     positionabsolute;
46     right0;
47     top0;
48     z-index1;
49 }
50 .submit_button {
51     background-image: -moz-linear-gradient(center top #FDFAFB#F9F7F7 50%#F6F3F4 50%#F0EDED);
52     border1px solid #BBBBBB;
53     border-radius: 6px 6px 6px 6px;
54     box-shadow: 0 1px rgba(2552552550.8), 0 1px rgba(2552552550.35inset;
55     color#524D4D;
56     cursorpointer;
57     display: inline-block;
58     font-family"helvetica neue",arial,sans-serif;
59     font-size18px;
60     font-weightbold;
61     line-height1em;
62     margin0;
63     padding0.45em 0.825em;
64     text-aligncenter;
65     text-shadow0 1px rgba(2552552550.9);
66     transition: all 0.05s ease-in-out 0s;
67 }
68 .submit_button:hover {
69     box-shadow: 0 1px rgba(2552552550.8), 0 1px rgba(2552552550.35inset0 0 10px rgba(2322302300.75);
70 }

Live Demo

Conclusion

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