1. Code
  2. Coding Fundamentals
  3. Databases & SQL

Build a Newsletter System With PHP and MySQL

Scroll to top

Today, we are going to be building a newsletter system using PHP with a MySQL database. The tutorial will cover building a system that allows for multiple newsletter lists and the sending of messages to specific lists.

We are going to build a pretty complex newsletter system, so let's get started! You are going to need two resources for the project:

Create the Application Skeleton

Firstly, let's have a look at the overall project directory structure.

Go ahead and create a main project directory in the first place. Next, create a folder named admin within your project folder. Moving further, within the admin folder, include two sub-directories named media and swift. Additionally, create a new folder named images inside the media directory. It is recommended to position the Swift lib folder within the previously created swift folder. Moreover, make sure to copy the set of six silk icons that we will be using.

  • bullet_green.png
  • bullet_red.png
  • delete.png
  • email_go.png
  • find.png
  • page_edit.png

For the rest of the tutorial, we'll be mostly developing CRUD functionalities (create, read, update, and delete).

Application Configuration

To easily manage application configuration throughout the PHP code, let's create a global configuration file, which defines constants that can be used throughout the application. We'll name it admin/config.php.

1
<?php  
2
session_start(); 
3
require_once 'database.php';
4
5
$mini = false;
6
$nonav = false;
7
8
error_reporting(0);
9
10
define('DB_SERVER', 'localhost');
11
define('DB_USER', 'root');
12
define('DB_PASSWORD', ''); 
13
define('DB_NAME', 'tutsplus_ns_demo');
14
define('FROM_EMAIL', 'no_reply@ohyeahemail.com');
15
define('FROM_NAME', 'oh yeah email!');
16
17
$db = new Database(DB_SERVER, DB_USER, DB_PASSWORD, DB_NAME);

As you can see, we've defined constants related to the database settings and email configuration. Make sure you change the above settings as per your environment. So that's it for creating the global configuration file. We've also included the database class file, which we'll implement in a moment.

The Database Class

In this section, we'll implement the Database class, which helps us to perform different types of database operations that we are going to use in our newsletter application.

Let's have a look at the admin/database.php file.

1
<?php
2
class Database {
3
    private $connection;
4
5
    public function __construct($db_server, $db_user, $db_password, $db_name) 
6
    {
7
        $this->connection = new mysqli($db_server, $db_user, $db_password, $db_name);
8
9
        if ($this->connection->connect_error) {
10
            die('There was a problem connecting to the database.');
11
        }
12
    }
13
14
    public function validateUser($username, $pw) 
15
    {
16
        if ($this->checkUsernameAndPassword($username, $pw)) {
17
            return true;
18
        } else {
19
            $_SESSION['error'] = "Login error.";
20
            return false;
21
        }
22
    }
23
24
    public function loggedIn() 
25
    {
26
        if ($_SESSION['authorized'] == true) {
27
            return true;
28
        } else {
29
            return false;
30
        }
31
    }
32
33
    public function loginRequired() 
34
    {
35
        if ($this->loggedIn()) {
36
            return true;
37
        } else {
38
            return false;
39
        }
40
    }
41
42
    public function query($sql) 
43
    {
44
        $stmt = $this->connection->prepare($sql);
45
        $stmt->execute();
46
        $meta = $stmt->result_metadata();
47
48
        $parameters = [];
49
        while ($field = $meta->fetch_field()) {
50
            $parameters[] = &$row[$field->name];
51
        }
52
53
        $results = [];
54
        call_user_func_array([$stmt, 'bind_result'], $parameters);
55
56
        while ($stmt->fetch()) {
57
            $x = [];
58
            foreach ($row as $key => $val) {
59
                $x[$key] = $val;
60
            }
61
            $results[] = $x;
62
        }
63
64
        $stmt->close();
65
        return $results;
66
    }
67
68
    public function countQuery($query) 
69
    {
70
        if ($stmt = $this->connection->prepare($query)) {
71
            $stmt->execute();
72
            $stmt->bind_result($result);
73
            $stmt->fetch();
74
            $stmt->close();
75
        }
76
    }
77
78
    public function insertQuery($data, $table)
79
    {
80
        $columns = implode(', ', array_keys($data));
81
        $placeholders = implode(', ', array_fill(0, count($data), '?'));
82
83
        $query = "INSERT INTO $table ($columns) VALUES ($placeholders)";
84
        
85
        if ($stmt = $this->connection->prepare($query)) {
86
            $stmt->bind_param(str_repeat('s', count($data)), ...array_values($data));
87
            $stmt->execute();
88
            $stmt->close();
89
        }
90
    }
91
92
    public function updateQuery($data, $id, $table)
93
    {
94
        $setStatements = implode('=?, ', array_keys($data)) . '=?';
95
        $query = "UPDATE $table SET $setStatements WHERE id=?";
96
97
        $types = str_repeat('s', count($data)) . 'i';
98
        $values = array_merge(array_values($data), [$id]);
99
100
        if ($stmt = $this->connection->prepare($query)) {
101
            $stmt->bind_param($types, ...$values);
102
            $stmt->execute();
103
            $stmt->close();
104
        }
105
    }
106
107
    public function deleteQuery($table, $id)
108
    {
109
        $query = "DELETE FROM ? WHERE id=? LIMIT 1";
110
    
111
        if ($stmt = $this->connection->prepare($query)) {
112
            $stmt->bind_param('si', $table, $id);
113
            $stmt->execute();
114
            $stmt->close();
115
        }
116
    }
117
    
118
    public function checkUsernameAndPassword($u, $pw) 
119
    {
120
        $query = "SELECT * FROM users WHERE username = ? AND password = ? LIMIT 1";
121
        if ($stmt = $this->connection->prepare($query)) {
122
            $p = md5($pw);
123
124
            $stmt->bind_param('ss', $u, $p);
125
            $stmt->execute();
126
            $stmt->bind_result($id, $username, $pw);
127
            if ($stmt->fetch()) {
128
                $_SESSION['authorized'] = true;
129
                $_SESSION['username'] = $username;
130
                return true;
131
            } else {
132
                return false;
133
            }
134
135
            $stmt->close();
136
        }
137
    }
138
139
    public function __destruct() 
140
    {
141
        $this->connection->close();
142
    }
143
144
    public function errorMessages()
145
    {
146
	    $message = '';
147
148
	    if($_SESSION['success'] != '') {
149
	        $message = '<span class="success" id="message">'.$_SESSION['success'].'</span>';
150
	        $_SESSION['success'] = ''; 
151
	    }
152
153
	    if($_SESSION['error'] != '') {
154
	        $message = '<span class="error" id="message">'.$_SESSION['error'].'</span>';
155
	        $_SESSION['error'] = '';
156
	    }
157
158
	    return $message; 
159
	}
160
}

Let's quickly go through the different methods of this class.

In the __construct method, we are creating a mysqli connection object. It takes four parameters—$db_server, $db_user, $db_password, and $db_name—and establishes a database connection using the provided credentials. If the connection fails, an error message is displayed.

The validateUser method handles user authentication. It takes a username and password as parameters and calls the checkUsernameAndPassword method to verify the credentials. If the credentials are valid, it returns true; otherwise, it returns false.

The loggedIn method checks if a user is logged in by checking the $_SESSION['authorized'] variable. It returns true if the user is logged in and false otherwise.

Next, the query method executes the SQL query provided in the first argument. It prepares the query and executes it. Then, it retrieves the result metadata, binds the result to variables, fetches the results, and returns an array containing the fetched data. The insertQuery, updateQuery, and deleteQuery methods allow insert, update, and delete operations in the database. The countQuery method executes an SQL query, which returns a single result, such as a count.

The checkUsernameAndPassword method is used to check the username and password against the database. It prepares a query to fetch the user with the provided username and password. It also sets the appropriate session variables if the credentials are valid.

Application Layout

In this section, we'll discuss how to set up our main layout file, which will be used as the container for the rest of the pages that we'll implement.

Go ahead and create the admin/layout.php file as shown in the following snippet.

1
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "https://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> 
2
3
<html xmlns="http://www.w3.org/1999/xhtml" > 
4
    <head> 
5
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
6
        <title><?php echo $title; ?> » My Newsletter App</title> 
7
8
        <!-- Stylesheets --> 
9
        <!-- <link rel="stylesheet" href="media/style.css" type="text/css" media="all" /> --> 
10
    </head> 
11
12
    <body<?php if ($mini == true) { ?> class="mini"<?php } ?>> 
13
        <div id="header"> 
14
            <h1><a href="index.php">My Newsletter App</a></h1> 
15
        </div> 
16
17
        <?php if ($nonav == false) { ?> 
18
            <div id="nav"> 
19
                <a href="messages.php"<?php if($tab == 'mess') {?>class="current"<?php } ?>>messages</a> 
20
                <a href="subscribers.php"<?php if($tab == 'sub') {?>class="current"<?php } ?>>subscribers</a> 
21
                <a href="newsletters.php"<?php if($tab == 'nl') {?>class="current"<?php } ?>>newsletters</a> 
22
                <a href="templates.php"<?php if($tab == 'temp') {?>class="current"<?php } ?>>templates</a> 
23
24
                <span class="right"> 
25
                    <a href="logout.php">log out</a> 
26
                </span> 
27
            </div> 
28
        <?php } ?> 
29
30
        <div id="container"> 
31
            <h3><?php echo $title;?></h3> 
32
            <?php echo $content; ?> 
33
        </div> 
34
    </body> 
35
</html>

It's a fairly straightforward XHTML layout file, which contains sections like head, body, etc. We've also implemented a navigation, which allows users to navigate between different sections of our application.

Next, go ahead and create the admin/index.php file with the following contents.

1
<?php 
2
require_once 'config.php';
3
4
$title = "Home!";
5
$content =  '<h3>current stats</h3>Our home page!';
6
include 'layout.php'; 
7
?>

In the above file, we've included the config file, initialized the title variable, and then initialized the content variable. Finally, we've included the layout file. It should look like this.

newsletter app home pagenewsletter app home pagenewsletter app home page

Now, let's add some style. Add the following to the admin/media/style.css file.

1
/* reset */ 
2
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}:focus{outline:0}ins{text-decoration:none}del{text-decoration:line-through}table{border-collapse:collapse;border-spacing:0} 
3
/* typography */ 
4
body{font:13px/1.5 Helvetica,Arial,'Liberation Sans',FreeSans,sans-serif}a:focus{outline:1px dotted invert}hr{border:0 #ccc solid;border-top-width:1px;clear:both;height:0}h1{font-size:25px}h2{font-size:23px}h3{font-size:21px}h4{font-size:19px}h5{font-size:17px}h6{font-size:15px}ol{list-style:decimal}ul{list-style:square}li{margin-left:30px}p,dl,hr,h1,h2,h3,h4,h5,h6,ol,ul,pre,table,address,fieldset{margin:10px 0;}

Let's add more stuff.

1
#header {width:85%; margin:0 auto;} 
2
#header h1 a {text-decoration:none; color:#000;} 
3
#container {width:85%; background: #111; margin:5px auto; color:#fff; padding:10px;}

Now, it should look like this.

newsletter app with styles appliednewsletter app with styles appliednewsletter app with styles applied

Next, let's style our tabs.

1
/* tabs */ 
2
#nav {margin:0 auto 2px;padding:10px;width:85%; background:#444;} 
3
#nav a { padding:12px 10px; margin:0; color:#fff; text-decoration:none; text-align:center;} 
4
#nav a.current, #nav a:hover {background:#111;} 
5
#nav span.right {float:right;}
style the tabsstyle the tabsstyle the tabs

While we are working on this file, go ahead and add the following styles for our mini-layout, form inputs, tables, large links, and our error and success messages.

1
body.mini #header {width:30%; text-align:center;} 
2
body.mini #container {width:30%;} 
3
4
/* form */ 
5
form input.text {width:95%; font-size:16px;} 
6
form textarea {width:95%; height:100%;} 
7
8
/* table */ 
9
table {width:98%; text-align:right; border:rgb(128,128,128); font-size:12px; margin:5px 10px; color:#000;background:#fff;} 
10
table th {background-color: rgb(229, 229, 229); border:1px solid rgb(187, 187, 187); padding:3px 6px; font-weight:normal; color:#000;} 
11
table tr td {border: 1px solid rgb(221, 221, 221); padding:3px 6px;} 
12
table tr:hover {background-color: rgb(240, 240, 240);color:#000;} 
13
14
/* a */ 
15
a.large {padding: 5px; color:#000; background:#eee; text-decoration:none; margin:5px;} 
16
a.large.right {float:right;} 
17
a.large:hover, a.large.current {background:#444; color:#fff;} 
18
19
/* messages */ 
20
#message {margin: 5px 10px; padding: 4px; display:block;text-align:center;} 
21
#message.error {background:#FFEBE8;border: 1px solid #CC0000;color:#CC0000;} 
22
#message.success {border:solid 1px #349534; background:#C9FFCA;color:#008000;}

Authentication

In this section, we're going to implement the authentication system for our application.

Go ahead and create the admin/login.php file with the following contents.

1
<?php 
2
require_once 'config.php'; 
3
4
if($db->loggedIn()) {
5
    header('Location: index.php');
6
    exit;
7
}
8
9
$title = "login"; 
10
$nonav = true; 
11
$mini = true; 
12
13
if($_POST && (!empty($_POST['username']) ) && (!empty($_POST['password']))) { 
14
    $response = $db->validateUser($_POST['username'], $_POST['password']);
15
    
16
    if (!$response) {
17
        header("Location: login.php");
18
        exit;
19
    }
20
} 
21
22
$error = $_SESSION['error'];
23
$content = '';
24
$content .= $error;
25
$content .= ' 

26
<form action="login.php" method="post"> 

27
    <p> 

28
        <label for="username">username:</label><br /> 

29
        <input type="text" name="username" class="text" /> 

30
    </p> 

31
    <p> 

32
        <label for="password">password:</label><br /> 

33
        <input type="password" name="password" class="text" /> 

34
    </p> 

35
    <p> 

36
        <input type="submit" value="login" /> 

37
    </p> 

38
</form>'; 
39
40
include 'layout.php';
41
?>

To begin with, we've included the configuration file. Next, we verify if the user is already logged in, and if so, we redirect the user to the home page. Afterwards, we define the title and layout options.

Next, we check if it's a POST request and if it includes both a username and password. In that case, we call the validateUser method of the Database class. Subsequently, we've assigned the session errors to the $error variable. Then, we proceed to set up our form and display any errors.

Let's also create the admin/logout.php file as well.

1
<?php 
2
require_once 'config.php'; 
3
4
$_SESSION = array();
5
session_destroy(); 
6
header('Location: login.php'); 
7
?>

In the above file, we'll destroy the current session and redirect the user to the login.php file.

login pagelogin pagelogin page

Let's have a quick look at the users table, which we'll need in our database for the user management system.

1
CREATE TABLE `users` ( 
2
   `id` int(10) AUTO_INCREMENT, 
3
   `username` varchar(50), 
4
   `password` varchar(32), 
5
   PRIMARY KEY (`id`) 
6
) ENGINE=MyISAM DEFAULT CHARSET utf8; 
7
8
9
INSERT INTO `users` (`id`, `username`, `password`) VALUES  ('1', 'admin', '5ebe2294ecd0e0f08eab7690d2a6ee69');

Newsletters

In our application, users should be allowed to add as many newsletters as they want. In that case, we need to create the newsletters table as shown in the following snippet.

1
CREATE TABLE `newsletters` ( 
2
   `id` int(10) AUTO_INCREMENT, 
3
   `name` varchar(50), 
4
   `description` varchar(255), 
5
   `visible` varchar(10), 
6
   PRIMARY KEY (`id`) 
7
) ENGINE=MyISAM DEFAULT CHARSET utf8;

Now that we have our newsletters table in place, we are going to implement different pages for the newsletter management.

Newsletters Listing Page

Go ahead and create the admin/newsletters.php file, as shown in the following snippet.

1
<?php 
2
require_once 'config.php'; 
3
4
$response = $db->loginRequired();
5
if (!$response) {
6
    header('Location: login.php');
7
    exit;
8
}
9
$title = "Newsletters"; 
10
$newsletters = $db->query("SELECT * FROM newsletters ORDER BY id ASC");
11
12
$tab = 'nl'; 
13
$table = ""; 
14
15
foreach($newsletters as $row) {
16
    $dlink = '<a href="newsletters_delete.php?id='.$row['id'].'" onclick="return confirm(\'Are you sure you want to delete this newsletter?\');" title="delete"><img src="media/images/delete.png" alt="delete"/></a>';
17
    $elink = '<a href="newsletters_edit.php?id='.$row['id'].'" title="edit" ><img src="media/images/page_edit.png" alt="edit"/></a>';
18
    if($row['visible'] == "1") {
19
        $visible = '<img src="media/images/bullet_green.png" />';} else {$visible = '<img src="media/images/bullet_red.png" />';
20
    }
21
    $table .= "<tr><td>".$row['id']."</td><td>".$row['name']."</td><td>".$row['description']."</td><td>$visible</td><td>".$dlink." ".$elink."</td></tr>\n";
22
} 
23
24
$message = $db->errorMessages();
25
$content = '';
26
$content .= '<a href="newsletters_new.php" class="large">Add New Newsletter »</a> ';
27
$content .= $message;
28
$content .= '<table> 

29
    <tr> 

30
        <th></th> 

31
        <th>name</th> 

32
        <th>description</th> 

33
        <th>visible</th> 

34
        <th></th> 

35
    </tr>';
36
$content .= $table;
37
$content .= '</table>';
38
39
include 'layout.php'; 
40
?>

Firstly, it checks if the user is logged in by calling the loginRequired method of the Database class. If the user is not logged in, it redirects the user to the login.php page.

Next, it retrieves the list of newsletters from the database using the query method of the Database class. It selects all records from the newsletters table and orders them by the id column in ascending order.

Finally, we iterate over the newsletter records and build an HTML table list, and include the layout.php file to display the newsletter listing.

With a few records already in the newsletters table, it should look like this:

list of newsletterslist of newsletterslist of newsletters

Add a Newsletter

Go ahead and create the admin/newsletters_new.php file with the following contents.

1
<?php 
2
require_once 'config.php';
3
4
$response = $db->loginRequired();
5
if (!$response) {
6
    header('Location: login.php');
7
    exit;
8
}
9
10
$tab = 'nl';
11
12
if(isset($_POST['submitted'])) {
13
    $data = array('name'=>$_POST['name'],'description'=>$_POST['description']);
14
    $db->insertQuery($data, 'newsletters');
15
    $_SESSION['success'] = "Added newsletter.";
16
    header('Location: newsletters.php');
17
    exit;
18
}
19
20
$title = "Add New Newsletter";
21
$content = '<form action="newsletters_new.php" method="POST">

22
    <p> 

23
        <label for="name">Name:</label><br /> 

24
        <input type="text" name="name" class="text" />  

25
    </p> 

26
    <p> 

27
        <label for="description">Description:</label> 

28
        <input type="text" name="description" class="text" />  

29
    </p> 

30
    <p> 

31
        <input type="submit" value="Add Newsletter" /> 

32
        <input type="hidden" value="1" name="submitted" />  

33
    </p> 

34
</form>';
35
36
include 'layout.php'; 
37
?>

When a user visits the above page, it displays the "add newsletter" form, which allows the user to enter newsletter details and submit the form.

When the form is submitted, it calls the insertQuery method of the Database class, passing the values of $_POST['name'] and $_POST['description'] to insert a new record into the newsletters table in the database. Finally, it redirects the user to the newsletters.php page.

The form should look like this.

new newsletter formnew newsletter formnew newsletter form

Edit a Newsletter

Let's create the admin/newsletters_edit.php file, which allows users to edit existing newsletters.

1
<?php 
2
require_once 'config.php'; 
3
4
$response = $db->loginRequired();
5
if (!$response) {
6
    header('Location: login.php');
7
    exit;
8
}
9
10
$tab = 'nl';
11
12
if(isset($_POST['submitted'])) { 
13
    if(isset($_POST['visible'])) {$visible = 1;}else{$visible = 0;}
14
    $data = array('name'=>$_POST['name'],'description'=>$_POST['description'], 'visible'=>$visible);
15
    $db->updateQuery($data, $_POST['id'], 'newsletters');
16
    $_SESSION['success'] = "Updated newsletter.";
17
    header('Location: newsletters.php');
18
    exit;
19
} 
20
21
$title = "Edit newsletter";
22
$id = (int) $_GET['id'];
23
24
$results = $db->query("SELECT * FROM newsletters WHERE id='".$id."' LIMIT 1");
25
$name = $results[0]['name'];
26
$description = $results[0]['description'];
27
$visible = ($results[0]['visible'] == "1") ? 'checked="checked"' : '';
28
29
$content = '<form action="newsletters_edit.php" method="POST">

30
    <p> 

31
        <label for="name">Name:</label><br /> 

32
        <input type="text" name="name" class="text" value="'.$name.'" />  

33
    </p> 

34


35
    <p> 

36
        <label for="description">Description:</label> 

37
        <input type="text" name="description" class="text" value="'.$description.'" />  

38
    </p> 

39


40
    <p> 

41
        <label for="visible">Visible:</label> 

42
        <input type="checkbox" name="visible" value="true" '.$visible.'/> 

43
    </p> 

44


45
    <p> 

46
        <input type="submit" value="Edit Newsletter" /> 

47
        <input type="hidden" value="1" name="submitted" />  

48
        <input type="hidden" value="'.$id.'" name="id" /> 

49
    </p> 

50
</form>'; 
51
52
include 'layout.php'; 
53
?>

Overall, it's responsible for displaying a form to edit an existing newsletter. When it's submitted, it updates the corresponding record in the database and redirects the user to the appropriate page based on the outcome.

The edit form looks like this.

edit formedit formedit form
edited newsletteredited newsletteredited newsletter

Delete a Newsletter

Go ahead and create the admin/newsletters_delete.php file, which allows users to delete an existing newsletter.

1
<?php 
2
require_once 'config.php'; 
3
4
$response = $db->loginRequired();
5
if (!$response) {
6
    header('Location: login.php');
7
    exit;
8
}
9
10
$id = (int) $_GET['id'];  
11
$stmt = $db->deleteQuery($id);
12
$_SESSION['success'] = "Newsletter deleted."; 
13
14
header('Location: newsletters.php');
15
?>

Templates

Our application also supports newsletter template management. So we need to build the CRUD in the same way we just built it for the newsletters entity. Since it's pretty similar, I'll quickly go through it by providing the code.

Let's create the templates table as shown in the following snippet.

1
CREATE TABLE `templates` ( 
2
   `id` int(10) AUTO_INCREMENT, 
3
   `name` varchar(50), 
4
   `columns` tinyint(5), 
5
   `body` text, 
6
   PRIMARY KEY (`id`) 
7
) ENGINE=MyISAM DEFAULT CHARSET utf8;

Templates Listing Page

Go ahead and create the admin/templates.php file as shown in the following snippet.

1
<?php 
2
require_once 'config.php'; 
3
4
$response = $db->loginRequired();
5
if (!$response) {
6
    header('Location: login.php');
7
    exit;
8
}
9
10
$title = "Templates"; 
11
$tab = 'temp'; 
12
$templates = $db->query("SELECT * FROM templates ORDER BY id ASC");
13
14
$table = ""; 
15
foreach($templates as $row) { 
16
    $plink = '<a href="" onClick="window.open(\'templates_preview.php?id='.$row['id'].'\',width=800,height=600)" title="preview"><img src="media/images/find.png" alt="preview"/></a>'; 
17
    $dlink = '<a href="templates_delete.php?id='.$row['id'].'" onclick="return confirm(\'Are you sure you want to delete this template?\');" title="delete"><img src="media/images/delete.png" alt="delete"/></a>'; 
18
    $elink = '<a href="templates_edit.php?id='.$row['id'].'" title="edit"><img src="media/images/page_edit.png" alt="edit"/></a>'; 
19
    $table .= "<tr><td>".$row['id']."</td><td>".$row['name']."</td><td>".$row['columns']."</td><td>".$plink." ".$dlink." ".$elink."</td></tr>\n"; 
20
} 
21
22
$message = $db->errorMessages();
23
24
$content = '';
25
$content .= $message;
26
$content .= '<a href="templates_new.php" class="large">Add New Template »</a> 

27
<table> 

28
    <tr> 

29
        <th></th> 

30
        <th>name</th> 

31
        <th>columns</th> 

32
        <th></th> 

33
    </tr>'; 
34
$content .= $table;
35
$content .= '</table>';
36
include 'layout.php'; ?>

It looks like this.

templates listtemplates listtemplates list

Add a Template

Go ahead and create the admin/templates_new.php file with the following contents.

1
<?php 
2
require_once 'config.php';
3
4
$response = $db->loginRequired();
5
if (!$response) {
6
    header('Location: login.php');
7
    exit;
8
}
9
10
$tab = 'nl';
11
12
if(isset($_POST['submitted'])) {
13
    $data = array('name'=>$_POST['name'],'columns'=>$_POST['columns'],'body'=>$_POST['body']);
14
    $db->insertQuery($data, 'templates');
15
    $_SESSION['success'] = "Added template.";
16
    header('Location: templates.php');
17
    exit;
18
}
19
20
$title = "Add New Template";
21
$content = '<form action="templates_new.php" method="POST">

22
    <p> 

23
        <label for="name">Name:</label><br /> 

24
        <input type="text" name="name" class="text" />  

25
    </p> 

26


27
    <p> 

28
        <label for="columns">Columns</label> 

29
        <select name="columns"> 

30
            <option value="1">Single Column Layout</option> 

31
            <option value="2">Two Column Layout</option> 

32
        </select> 

33
    </p> 

34


35
    <p> 

36
        <label for="description">Body: (raw html)</label><br /> 

37
        Use %content% for a single column layout, %leftcol% and %rightcol% for a two column layout.<br /> 

38
        <textarea name="body" rows="35"></textarea>  

39
    </p> 

40


41
    <p> 

42
        <input type="submit" value="Add Template" /> 

43
        <input type="hidden" value="1" name="submitted" />  

44
    </p> 

45
</form>';
46
47
include 'layout.php'; 
48
?>

Let's have a look at the output.

new template formnew template formnew template form

Edit a Template

Let's create the admin/templates_edit.php file, which allows users to edit existing templates.

1
<?php 
2
require_once 'config.php'; 
3
4
$response = $db->loginRequired();
5
if (!$response) {
6
    header('Location: login.php');
7
    exit;
8
}
9
10
$tab = 'nl';
11
12
if(isset($_POST['submitted'])) { 
13
    $data = array('name'=>$_POST['name'],'columns'=>$_POST['columns'],'body'=>$_POST['body']);
14
    $db->updateQuery($data, $_POST['id'], 'templates');
15
    $_SESSION['success'] = "Updated template.";
16
    header('Location: templates.php');
17
    exit;
18
} 
19
20
$title = "Edit template";
21
$id = (int) $_GET['id'];
22
23
$results = $db->query("SELECT * FROM templates WHERE id='".$id."' LIMIT 1");
24
25
$name = $results[0]['name']; 
26
$templatedata = stripslashes(htmlspecialchars($results[0]['body']));
27
28
$content = '<form action="templates_edit.php" method="POST">  

29
    <p> 

30
        <label for="name">Name:</label><br /> 

31
        <input type="text" name="name" class="text" value="'.$name.'"/>  

32
    </p> 

33


34
    <p> 

35
        <label for="columns">Columns</label> 

36
        <select name="columns"> 

37
            <option value="1">Single Column Layout</option> 

38
            <option value="2">Two Column Layout</option> 

39
        </select> 

40
    </p> 

41


42
    <p> 

43
        <label for="body">Body: (raw html)</label><br /> 

44
        Use %content% for a single column layout, %leftcol% and %rightcol% for a two column layout.<br /> 

45
        <textarea name="body" rows="35">'.$templatedata.'</textarea>  

46
    </p> 

47


48
    <p> 

49
        <input type="submit" value="Edit Template" /> 

50
        <input type="hidden" value="1" name="submitted" />  

51
        <input type="hidden" value="'.$id.'" name="id" /> 

52
    </p> 

53
</form>'; 
54
55
include 'layout.php'; 
56
?>

The form in the edit mode should look like this.

edit template formedit template formedit template form

Delete a Template

Go ahead and create the admin/templates_delete.php file, which allows users to delete existing templates.

1
<?php 
2
require_once 'config.php'; 
3
4
$response = $db->loginRequired();
5
if (!$response) {
6
    header('Location: login.php');
7
    exit;
8
}
9
10
$id = (int) $_GET['id'];
11
$stmt = $db->deleteQuery('templates', $id);
12
$_SESSION['success'] = "Template deleted."; 
13
14
header('Location: templates.php');
15
?>

Preview a Newsletter

Now, we are going to work on an extra page, which is not part of the CRUD spectrum; we are going to create a preview page. Go ahead and create the admin/templates_preview.php file with the following contents.

1
<?php 
2
require_once 'config.php'; 
3
4
$response = $db->loginRequired();
5
if (!$response) {
6
    header('Location: login.php');
7
    exit;
8
}
9
10
$id = (int) $_GET['id']; 
11
12
$data = $db->query("SELECT body FROM templates WHERE id={$id} LIMIT 1");
13
$template = $data[0]['body']; 
14
?> 
15
<?php echo $template; ?> 
16
<center><button type="button" onclick="self.close();">close window</button></center>

And an example preview looks like this:

previewpreviewpreview

So that's it for the template management.

Subscribers and Subscriptions

In this section, we'll work on subscriber management. Again, it's pretty similar to the sections that we've already discussed, so I'll quickly go through it by providing the code.

Let's create the subscribers table as shown in the following snippet.

1
CREATE TABLE `subscribers` ( 
2
    `id` tinyint(10) AUTO_INCREMENT, 
3
    `name` varchar(50), 
4
    `email` varchar(50), 
5
    PRIMARY KEY (`id`) 
6
) ENGINE=MyISAM DEFAULT CHARSET utf8;

Similarly, let's create the subscriptions table.

1
CREATE TABLE `subscriptions` ( 
2
    `id` tinyint(10) AUTO_INCREMENT, 
3
    `subscriber_id` tinyint(10), 
4
    `newsletter_id` tinyint(10), 
5
    PRIMARY KEY (`id`) 
6
) ENGINE=MyISAM DEFAULT CHARSET utf8;

So that creates a table for our many-to-many relationship with our newsletters. A subscriber can subscribe to multiple newsletters, and each newsletter can have many subscribers.

Subscriber Listing Page

Go ahead and create the admin/subscribers.php file as shown in the following snippet.

1
<?php 
2
require_once 'config.php'; 
3
4
$response = $db->loginRequired();
5
if (!$response) {
6
    header('Location: login.php');
7
    exit;
8
}
9
10
$title = "Subscribers"; 
11
$tab = 'sub'; 
12
$messages = $db->query("SELECT * FROM subscribers ORDER BY id ASC");
13
14
$table = ""; 
15
foreach($messages as $row) { 
16
	$dlink = '<a href="subscribers_delete.php?id='.$row['id'].'" onclick="return confirm(\'Are you sure you want to delete this subscriber?\');" title="delete"><img src="media/images/delete.png" alt="delete"/></a>'; 
17
	$elink = '<a href="subscribers_edit.php?id='.$row['id'].'" title="edit"><img src="media/images/page_edit.png" alt="edit"/></a>'; 
18
	$table .= '<tr><td>'.$row['id'].'</td><td>'.$row['name'].'</td><td>'.$row['email'].'</td><td>'.$dlink.' '.$elink.'</td></tr>'; 
19
}
20
21
$message = $db->errorMessages();
22
23
$content = '';
24
$content .= $message;
25
$content .= '<table> 

26
    <tr> 

27
        <th></th> 

28
		<th>name</th> 

29
		<th>email</th> 

30
		<th></th> 

31
    </tr>'; 
32
$content .= $table;
33
$content .= '</table>';
34
include 'layout.php'; ?>

We have basically the same listing pages as before, except this time we will be finding our subscribers. It should look like this:

subscriber listsubscriber listsubscriber list

Edit Subscribers

Let's create the admin/subscribers_edit.php file.

1
<?php 
2
require_once 'config.php'; 
3
4
$response = $db->loginRequired();
5
if (!$response) {
6
    header('Location: login.php');
7
    exit;
8
}
9
10
$tab = 'sub'; 
11
12
if(isset($_POST['submitted'])) {  
13
    $id = (int) $_POST['id']; 
14
    $data = array('name'=>$_POST['name'],'email'=>$_POST['email']);
15
    $db->updateQuery($data, $id, 'subscribers');
16
17
    foreach($_POST['newsletter'] as $n) {
18
19
        if($n['exists'] != '1' && $n['subscribe'] == "true") { // If we want to subscribe but the record doesnt exist

20
            $nlid = $n['nlid']; 
21
            $data = array('subscriber_id'=>$id,'newsletter_id'=>$_POST['newsletter_id'],'body'=>$nlid);
22
            $db->insertQuery($data, 'subscriptions');
23
        } elseif ($n['exists'] == '1' && $n['subscribe'] != "true") {// Else if we had an exits but we want to unsubscribe

24
            $subid = $n['subid'];
25
            $db->deleteQuery('subscriptions', $subid);
26
        } 
27
    } 
28
29
    $_SESSION['success'] = "Edited subscriber.";  
30
31
    header('Location: subscribers.php');
32
} 
33
34
$title = "Edit Newsletter"; 
35
36
$id = (int) $_GET['id']; 
37
38
$subscriber = $db->query("SELECT * FROM subscribers WHERE id='".$id."'");
39
$name = $subscriber[0]['name']; 
40
$email = $subscriber[0]['email']; 
41
42
$newsletters = $db->query("SELECT * FROM newsletters");
43
$subs = $db->query("SELECT * FROM subscriptions WHERE subscriber_id='".$id."'");
44
$subscriptions = '';
45
46
foreach($newsletters as $nl) {
47
    $s = false; 
48
    $subid = ''; 
49
50
    foreach($subs as $sub) {
51
        if($sub['newsletter_id'] == $nl['id']) {$s = true; $subid = $sub['id'];} 
52
    } 
53
54
    $checked = ($s == true) ? 'checked="checked"' : ''; 
55
56
    $subscriptions .= '

57
<input type="checkbox" name="newsletter['.$nl["id"].'][subscribe]" value="true" '.$checked.'/>

58
<label for="newsletter['.$nl["id"].']">'.$nl['name'].'</label>

59
<input type="hidden" name="newsletter['.$nl["id"].'][exists]" value="'.$s.'" />

60
<input type="hidden" name="newsletter['.$nl["id"].'][nlid]" value="'.$nl['id'].'" />

61
<input type="hidden" name="newsletter['.$nl["id"].'][subid]" value="'.$subid.'" /><br />

62
'; 
63
} 
64
65
$content = '<form action="subscribers_edit.php" method="POST">  

66
    <p> 

67
        <label for="name">Name:</label><br /> 

68
        <input type="text" name="name" class="text" value="'.$name.'" />  

69
    </p> 

70


71
    <p> 

72
        <label for="email">Email</label><br /> 

73
        <input type="text" name="email" class="text" value="'.$email.'" />  

74
    </p> 

75


76
    <p> 

77
        <strong>Newsletters:</strong><br /> 

78
        $subscriptions 

79
    </p> 

80


81
    <p> 

82
        <input type="submit" value="Edit Subscriber" /> 

83
        <input type="hidden" value="1" name="submitted" />  

84
        <input type="hidden" value="'.$id.'" name="id" /> 

85
    </p> 

86
</form>';
87
88
include 'layout.php'; ?>

This page is quite different, so I will explain each part.

When a user loads this page for the first time, we open it in edit mode. The form displays the subscriber details and the subscriptions that are attached to the subscriber.

When the form is submitted, we update the subscriber details in the subscribers table. Next, we'll either create or delete the subscription record in the subscriptions table based on the checkbox selection, which either subscribes or unsubscribes the user from the selected newsletter.

The page should look like this.

edit subscriber pageedit subscriber pageedit subscriber page

Delete a Subscriber

Go ahead and create the admin/subscribers_delete.php file.

1
<?php 
2
require_once 'config.php'; 
3
4
$response = $db->loginRequired();
5
if (!$response) {
6
    header('Location: login.php');
7
    exit;
8
}
9
10
$id = (int) $_GET['id'];
11
$stmt = $db->deleteQuery('subscribers', $id);
12
$_SESSION['success'] = "Subscriber deleted."; 
13
14
header('Location: subscribers.php');
15
?>

So that's it for subscriber management.

Messages

This section is going to be a bit lengthy, as it'll contain a lot of pages.

Firstly, let's create the messages table with this SQL.

1
CREATE TABLE `messages` ( 
2
   `id` tinyint(10) AUTO_INCREMENT, 
3
   `subject` varchar(255), 
4
   `leftcol` text, 
5
   `rightcol` text, 
6
   `template_id` tinyint(10), 
7
   PRIMARY KEY (`id`) 
8
) ENGINE=MyISAM DEFAULT CHARSET utf8;

List Messages

Go ahead and create the admin/messages.php file as shown in the following snippet.

1
<?php 
2
require_once 'config.php'; 
3
4
$response = $db->loginRequired();
5
if (!$response) {
6
    header('Location: login.php');
7
    exit;
8
}
9
10
$title = "Messages"; 
11
$tab = 'mes'; 
12
$messages = $db->query("SELECT * FROM messages ORDER BY id ASC");
13
14
$table = ""; 
15
foreach($messages as $row) { 
16
    $slink = '<a href="messages_send.php?id='.$row['id'].'" title="send message"><img src="media/images/email_go.png" alt="send message"/></a>'; 
17
    $plink = '<a href="messages_preview.php?id='.$row['id'].'" target="_new" title="preview"><img src="media/images/find.png" alt="preview"/></a>'; 
18
    $dlink = '<a href="messages_delete.php?id='.$row['id'].'" onclick="return confirm(\'Are you sure you want to delete this message?\');" title="delete"><img src="media/images/delete.png" alt="delete"/></a>'; 
19
    $elink = '<a href="messages_edit.php?id='.$row['id'].'" title="edit"><img src="media/images/page_edit.png" alt="edit"/></a>'; 
20
    $table .= '<tr><td>'.$row['id'].'</td><td>'.$row['subject'].'</td><td><a href="" onClick="window.open(\'templates_preview.php?id='.$row['template_id'].'\',width=800,height=600)" title="preview"><img src="media/images/find.png" alt="preview"/></a></td><td>'.$slink.' '.$plink.' '.$dlink.' '.$elink.'</td></tr>'; 
21
}
22
23
$message = $db->errorMessages();
24
25
$content = '<a href="messages_new.php" class="large">New Message »</a>';
26
$content .= $message;
27
$content .= '<table> 

28
    <tr> 

29
        <th></th> 

30
        <th>subject</th> 

31
        <th>template</th> 

32
        <th></th> 

33
    </tr>'; 
34
$content .= $table;
35
$content .= '</table>';
36
include 'layout.php'; ?>

It's a listing page with a few additional links for different operations. Your page should look like this:

message listmessage listmessage list

Now, we are going to start working on our new pages.

On the first page, we'll collect the subject and template-related information, and then on the next page, we'll collect the actual message.

Create the admin/messages_new.php file with the following contents.

1
<?php 
2
require_once 'config.php'; 
3
4
$response = $db->loginRequired();
5
if (!$response) {
6
    header('Location: login.php');
7
    exit;
8
}
9
10
$tab = 'mess'; 
11
12
if(isset($_POST['subject'])) {
13
    $data = array('subject'=>$_POST['subject'],'template_id'=>$_POST['template_id']);
14
    $db->insertQuery($data, 'messages');
15
16
    $i = $db->query("SELECT id FROM messages WHERE subject='".$_POST['subject']."' AND template_id=".$_POST['template']." ");
17
    $id = $i[0]['id'];
18
19
    $_SESSION['success'] = "Added template.";
20
    header('Location: messages_new_step2.php?id=' + $id);
21
} 
22
23
$title = "New Message";
24
$templates = $db->query("SELECT id,name,columns FROM templates");
25
$tselect = '<select name="template">';
26
foreach($templates as $row) { 
27
    $tselect .= '<option value="'.$row['id'].'">'.$row['name'].'</option>'; 
28
} 
29
$tselect .= "</select>"; 
30
31
$content = '<form action="messages_new.php" method="POST">

32
    <p>

33
        <label for="subject">Subject:</label><br />

34
        <input type="text" name="subject" class="text" />

35
    </p>

36


37
    <p> 

38
        <label for="template">Template:</label>

39
        $tselect

40
    </p> 

41


42
    <p> 

43
        <button onclick="">Continue »</button> 

44
    </p> 

45
</form>'; 
46
47
include 'layout.php'; 
48
?>

This page is very similar to the other new pages, but there is a minor addition. Right after we create a new message entry, we find the most recent insert id.

The page looks like this:

new message formnew message formnew message form

Next, we redirect the user to step two. Let's create the admin/messages_new_step2.php file with the following contents.

1
<?php
2
require_once 'config.php';
3
4
$response = $db->loginRequired();
5
if (!$response) {
6
    header('Location: login.php');
7
    exit;
8
}
9
10
$title = "New Message - Step 2";
11
$tab = 'mess';
12
$id = (int) $_GET['id'];
13
14
$mess = $db->query("SELECT * FROM messages WHERE id= $id"); 
15
$message = $mess[0]; 
16
$subject = $message['subject']; 
17
18
$templates = $db->query("SELECT id,name,columns FROM templates");
19
20
$tselect = '<select name="template">'; 
21
foreach($templates as $row) { 
22
    if($message['template_id'] == $row['id']) {
23
        $selected = ' selected="selected"';
24
25
        if($row['columns'] == "1") {
26
            $textareas = '<p><label for="body">Body: (raw html)</label><br /><textarea name="body" rows="35"></textarea></p>'; 
27
        } else { 
28
            $textareas = '<p><label for="leftcol">Left column: (raw html)</label><br /><textarea name="leftcol" rows="35"></textarea></p>

29
<p><label for="rightcol">Right column: (raw html)</label><br /><textarea name="rightcol" rows="35"></textarea></p>'; 
30
        } 
31
    } else {
32
        $selected = '';
33
    }
34
35
    $tselect .= '<option value="'.$row['id'].'"'.$selected.'>'.$row['name'].'</option>';
36
}
37
$tselect .= '</select>'; 
38
39
// Check for a POST

40
if(isset($_POST['submitted'])) {
41
    $template = $db->query("SELECT columns FROM templates WHERE id=".$message['template_id']);
42
43
    if($template[0]['columns'] == "1") {
44
        $body = mysql_real_escape_string($_POST['body']); 
45
        $data = array('subject'=>$_POST['subject'],'leftcol' => $body);
46
        $db->updateQuery($data, $id, 'messages');
47
    } else { 
48
        $leftcol = mysql_real_escape_string($_POST['leftcol']);
49
        $rightcol = htmlentities($_POST['rightcol']);
50
        $data = array('subject'=>$_POST['subject'],'leftcol' => $leftcol, 'rightcol'=>$rightcol);
51
        $db->updateQuery($data, $id, 'messages');
52
    } 
53
54
    header('Location: messages_new_step3.php?id='.$id); 
55
} 
56
57
58
$content = '<form action="messages_new_step2.php?id='.$id.'" method="POST">

59
    <p>

60
        <label for="subject">Subject:</label><br /> 

61
        <input type="text" name="subject" class="text" value="'.$subject.'"/>  

62
    </p> 

63


64
    <p> 

65
        <label for="template">Template:</label> 

66
        $tselect 

67
    </p> 

68


69
    '.$textareas.'

70


71
    <p> 

72
        <input type="submit" value="Continue »" /> 

73
        <input type="hidden" value="1" name="submitted" /> 

74
    </p> 

75
</form>';
76
77
include 'layout.php'; ?>

Firstly, we find the message we are working with, and then we find all the templates and construct a dropdown menu. Next, we have our POST block, which creates the link and then checks to see if we are working with one or two columns and creates the appropriate SQL query. In the end, your form should look like this:

new message step 2new message step 2new message step 2

Next, let's create the admin/messages_new_step3.php file with the following contents.

1
<?php 
2
require_once 'config.php'; 
3
4
$response = $db->loginRequired();
5
if (!$response) {
6
    header('Location: login.php');
7
    exit;
8
}
9
10
$title = "New Message - Step 3";
11
$id = (int) $_GET['id'];
12
$tab = 'mess'; 
13
14
15
$mess = $db->query("SELECT * FROM messages WHERE id=$id");
16
$message = $mess[0];
17
$subject = $message['subject'];
18
19
$content = '<a href="messages_preview.php?id=$id" class="large" target="_new">preview »</a><br />

20
<p>Do you want to <a href="messages.php" class="large">return to messages</a> or <a href="messages_send.php?id=$id" class="large">send the message</a>?</p>';
21
22
include 'layout.php'; 
23
?>

It's a very simple page, and it's the end of creating a message. It offers us a few links. The first one is the link to preview the message. The next link takes us back to the home page. Finally, the third one takes us to the page to send the message.

The page looks like this:

subscribe formsubscribe formsubscribe form

Next, we are going to continue with the edit page. It's pretty similar to the messages_new_step2.php file.

Go ahead and create the admin/messages_edit.php file as shown below.

1
<?php 
2
require_once 'config.php'; 
3
4
$response = $db->loginRequired();
5
if (!$response) {
6
    header('Location: login.php');
7
    exit;
8
}
9
10
$title = "Edit Message";
11
$id = (int) $_GET['id'];
12
$tab = 'mess';
13
14
$mess = $db->query("SELECT * FROM messages WHERE id=$id");
15
$message = $mess[0];
16
$subject = $message['subject'];
17
18
$templates = $db->query("SELECT id,name,columns FROM templates");
19
20
$tselect = '<select name="template">';
21
foreach ($templates as $row) {
22
    if($message['template_id'] == $row['id']) {
23
        $selected = ' selected="selected"';
24
    } else {
25
        $selected = '';
26
    }
27
28
    $tselect .= '<option value="'.$row['id'].'"'.$selected.'>'.$row['name'].'</option>';
29
} 
30
$tselect .= '</select>'; 
31
32
$mid = $message['template_id'];
33
$template = $db->query("SELECT id,name,columns FROM templates WHERE id=$mid");
34
35
if ($template[0]['columns'] == "1") {
36
    $textareas = '<p><label for="body">Body: (raw html)</label><br /><textarea name="body" rows="35">'.$message['leftcol'].'</textarea></p>';
37
} else {
38
    $textareas = '<p><label for="leftcol">Left column: (raw html)</label><br /><textarea name="leftcol" rows="35">'.$message['leftcol'].'</textarea></p>

39
<p><label for="rightcol">Right column: (raw html)</label><br /><textarea name="rightcol" rows="35">'.$message['rightcol'].'</textarea></p>';
40
}
41
42
// Check for a POST

43
if (isset($_POST['submitted'])) {
44
    if ($template[0]['columns'] == "1") {
45
        $body = mysql_real_escape_string($_POST['body']);
46
        $data = array('subject'=>$_POST['subject'],'leftcol'=>$body);
47
        $db->updateQuery($data, $id, 'messages');
48
    } else {
49
        $leftcol = mysql_real_escape_string($_POST['leftcol']);
50
        $rightcol = htmlentities($_POST['rightcol']);
51
52
        $data = array('subject'=>$_POST['subject'],'leftcol'=>$body,'rightcol'=>$rightcol);
53
        $db->updateQuery($data, $id, 'messages');
54
    }
55
56
    $_SESSION['success'] = "Edited message.";
57
    header('Location: messages.php');
58
} 
59
60
$content = '<form action="messages_edit.php?id='.$id.'" method="POST">

61
    <p> 

62
        <label for="subject">Subject:</label><br /> 

63
        <input type="text" name="subject" class="text" value="'.$subject.'"/>  

64
    </p> 

65


66
    <p> 

67
        <label for="template">Template:</label> 

68
        '.$tselect.'

69
    </p> 

70
    '.$textareas.'

71
    <p> 

72
        <input type="submit" value="Save »" /> 

73
        <input type="hidden" value="1" name="submitted" /> 

74
    </p> 

75
</form>'; 
76
77
include 'layout.php'; 
78
?>

It looks identical to our step two, but the textarea is populated with the content in edit mode.

Next, let's create the admin/messages_delete.php file.

1
<?php 
2
require_once 'config.php'; 
3
4
$response = $db->loginRequired();
5
if (!$response) {
6
    header('Location: login.php');
7
    exit;
8
}
9
10
$id = (int) $_GET['id'];
11
12
$db->deleteQuery("messages", $id);
13
$_SESSION['success'] = "Message deleted."; 
14
15
header('Location: messages.php'); 
16
?>

Finally, let's have a look at the preview page.

1
<?php 
2
3
require_once 'config.php'; 
4
5
$response = $db->loginRequired();
6
if (!$response) {
7
    header('Location: login.php');
8
    exit;
9
}
10
11
$id = (int) $_GET['id'];
12
13
$mess = $db->query("SELECT * FROM messages WHERE id=$id");
14
$message = $mess[0];
15
$subject = $message['subject'];
16
$tid = $message['template_id']; 
17
18
$data = $db->query("SELECT body,columns FROM templates WHERE id=$tid LIMIT 1");
19
$template = $data[0]['body']; 
20
21
if($data[0]['columns'] == "1") { 
22
    $leftcol = $message['leftcol']; 
23
    $body = str_replace('%content%', $leftcol, $template); 
24
} else { 
25
    $leftcol = $message['leftcol']; 
26
    $rightcol = $message['rightcol']; 
27
    $b = str_replace('%leftcol%', $leftcol, $template); 
28
    $body = str_replace('%rightcol%', $rightcol, $b); 
29
} 
30
?> 
31
32
<?php echo $body; ?> 
33
34
<button type="button" onclick="self.close();">close window</button>

Basically, we're just fetching a message and template from the database and preparing the necessary variable for preview.

The Front-End

In this section, we'll build front-end pages. We need to create files for the front-end section in the root folder.

Subscribe to Newsletters

Let's create the index.php page with the following contents.

1
<?php 
2
3
require_once 'admin/config.php'; 
4
5
$newsletters = $db->query("SELECT * FROM newsletters WHERE visible=1");
6
7
$subscriptions = ''; 
8
9
foreach($newsletters as $nl) {
10
    $subscriptions .= '

11
<input type="checkbox" name="newsletter['.$nl["id"].'][subscribe]" value="true" '.$checked.'/>

12
<label for="newsletter['.$nl["id"].']">'.$nl['name'].'</label>

13
<input type="hidden" name="newsletter['.$nl["id"].'][nlid]" value="'.$nl['id'].'" /><br />

14
'.$nl["description"].'<br />

15
'; 
16
} 
17
?> 
18
19
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> 
20
<html xmlns="http://www.w3.org/1999/xhtml" > 
21
    <head> 
22
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
23
        <title>my newsletters</title> 
24
        <!-- Stylesheets --> 
25
        <link rel="stylesheet" href="style.css" type="text/css" media="all" /> 
26
    </head> 
27
28
    <body> 
29
        <div id="header"> 
30
            <h1>my newsletters</h1> 
31
        </div> 
32
33
        <div id="container"> 
34
            <h3>Subscribe to our newsletters!</h3> 
35
            <form action="subscribe.php" method="POST"> 
36
                <p> 
37
                    <label for="name">Name:</label><br /> 
38
                    <input type='text' name='name' class="text" />  
39
                </p> 
40
41
                <p> 
42
                    <label for="email">Email</label><br /> 
43
                    <input type="text" name="email" class="text" />  
44
                </p> 
45
46
                <p> 
47
                    <strong>Newsletters:</strong><br /> 
48
                    <?php echo $subscriptions; ?> 
49
                </p> 
50
51
                <p> 
52
                    <input type='submit' value='Subscribe »' /> 
53
                    <input type='hidden' value='1' name='submitted' />  
54
                </p> 
55
            </form> 
56
        </div> 
57
    </body> 
58
</html>

We've fetched all the public newsletters from the database and prepared a list, so that a user can subscribe to it.

It looks like this:

subscribe pagesubscribe pagesubscribe page

Let's have a look at the style.css file.

1
#header, #container {width:65%;margin:0 auto; padding:0.7%;} 
2
#container {background:#ccc;} 
3
form input.text {width:95%; font-size:16px;} 
4
#message.success {border:solid 1px #349534; background:#C9FFCA;color:#008000;}
styled subscribe pagestyled subscribe pagestyled subscribe page

Let's have a look at the subscribe.php file, which is called when the user submits the form on the newsletter subscribe page.

1
<?php
2
require_once 'admin/config.php';
3
4
if(isset($_POST['submitted'])) {  
5
    $name = $_POST['name'];
6
    $email = $_POST['email'];
7
8
    $data = array('name'=>$name,'email'=>$email);
9
    $db->insertQuery($data, 'subscribers');
10
11
    $sql = "SELECT id FROM subscribers WHERE name='$name' AND email='$email' LIMIT 1";
12
    $subscriber = $db->query($sql);
13
    $id = $subscriber[0]['id'];
14
15
    foreach($_POST['newsletter'] as $n) {
16
        if($n['subscribe'] == "true") {
17
            $nlid = $n['nlid'];
18
            $data = array('subscriber_id'=>$id, 'newsletter_id'=>$nlid);
19
            $db->insertQuery($data, 'subscriptions');
20
        }
21
    }
22
} else {
23
    header('Location: index.php');
24
}
25
?>
26
27
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> 
28
<html xmlns="http://www.w3.org/1999/xhtml" > 
29
    <head> 
30
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
31
32
        <title>my newsletters</title> 
33
        <!-- Stylesheets --> 
34
        <link rel="stylesheet" href="style.css" type="text/css" media="all" /> 
35
    </head> 
36
37
    <body> 
38
        <div id="header"> 
39
            <h1>my newsletters</h1> 
40
        </div> 
41
42
        <div id="container"> 
43
            <h3>Thank you for subscribing!</h3> 
44
        </div> 
45
    </body> 
46
</html>

When the form is submitted, we insert the user in the subscribers table in the first place. Next, for every selected newsletter, we create an entry in the subscriptions table.

thank you pagethank you pagethank you page

Next, let's create the preferences.php page.

1
<?php 
2
require_once 'admin/config.php'; 
3
4
if(isset($_POST['submitted'])) {
5
    $id = (int) $_POST['id']; 
6
    $data = array('name'=>$_POST['name'],'email'=>$_POST['email']);
7
    $db->updateQuery($data, $id, 'subscribers');
8
9
    foreach ($_POST['newsletter'] as $n) {
10
        if($n['exists'] != '1' && $n['subscribe'] == "true") { // If we want to subscribe but the record doesnt exist

11
            $nlid = $n['nlid']; 
12
13
            $data = array('subscriber_id'=>$id,'newsletter_id'=>$nlid);
14
            $db->insertQuery($data, 'templates');
15
        } elseif ($n['exists'] == '1' && $n['subscribe'] != "true") {// Else if we had an exits but we want to unsubscribe

16
            $subid = $n['subid'];
17
            $db->insertQuery('subscriptions', $subid);
18
        }
19
    }
20
21
    $_SESSION['success'] = "Preferences saved.";  
22
} 
23
24
if(isset($_GET['email'])) {
25
    $email = $_GET['email'];
26
    $display = 'form';
27
} else {
28
    $display = 'find';
29
}
30
31
$subscriber = $db->query("SELECT * FROM subscribers WHERE email='$email'");
32
if($subscriber || $display == 'find') {
33
    $id = $subscriber[0]['id'];
34
    $name = $subscriber[0]['name'];
35
    $email = $subscriber[0]['email'];
36
} else {
37
    header('Location: index.php');
38
}
39
40
$newsletters = $db->query("SELECT * FROM newsletters WHERE visible=1");
41
$subs = $db->query("SELECT * FROM subscriptions WHERE subscriber_id='".$id."'");
42
$subscriptions = '';
43
44
foreach($newsletters as $nl) {
45
    $s = false; 
46
    $subid = ''; 
47
    foreach($subs as $sub) { 
48
        if($sub['newsletter_id'] == $nl['id']) {$s = true; $subid = $sub['id'];} 
49
    } 
50
51
    $checked = ($s == true) ? 'checked="checked"' : ''; 
52
53
    $subscriptions .= '

54
<input type="checkbox" name="newsletter['.$nl["id"].'][subscribe]" value="true" '.$checked.'/>

55
<label for="newsletter['.$nl["id"].']">'.$nl['name'].'</label>

56
<input type="hidden" name="newsletter['.$nl["id"].'][exists]" value="'.$s.'" />

57
<input type="hidden" name="newsletter['.$nl["id"].'][nlid]" value="'.$nl['id'].'" />

58
<input type="hidden" name="newsletter['.$nl["id"].'][subid]" value="'.$subid.'" /><br />'; 
59
60
}  
61
62
$message = $db->errorMessages();
63
?>
64
65
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> 
66
67
<html xmlns="http://www.w3.org/1999/xhtml" > 
68
    <head> 
69
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
70
71
        <title>my newsletters - my preferences</title> 
72
        <!-- Stylesheets --> 
73
        <link rel="stylesheet" href="style.css" type="text/css" media="all" /> 
74
    </head> 
75
76
    <body> 
77
        <div id="header"> 
78
            <h1>my newsletters</h1> 
79
        </div> 
80
81
        <div id="container"> 
82
            <h3>my preferences</h3> 
83
            <?php if($display == 'form') {?> 
84
                <form action="preferences.php" method="POST"> 
85
                    <p> 
86
                        <label for="name">Name:</label><br /> 
87
                        <input type='text' name='name' class="text" value="<?php echo $name; ?>"/>  
88
                    </p> 
89
90
                    <p> 
91
                        <label for="email">Email</label><br /> 
92
                        <input type="text" name="email" class="text" value="<?php echo $email; ?>"/>  
93
                    </p> 
94
95
                    <p> 
96
                        <strong>Newsletters:</strong><br /> 
97
                        <?php echo $subscriptions; ?> 
98
                    </p> 
99
100
                    <p> 
101
                        <input type='submit' value='Save my preferences »' /> 
102
                        <input type='hidden' value='1' name='submitted' />  
103
                        <input type='hidden' value='<?php echo $id; ?>' name='id' /> 
104
                    </p> 
105
                </form>
106
            <?php } else { ?> 
107
                <?php echo $message; ?> 
108
                <form action='preferences.php' method="get"> 
109
                    <p> 
110
                        <label for="email">Email</label><br /> 
111
                        <input type="text" name="email" class="text" />  
112
                    </p> 
113
                    <p> 
114
                        <input type='submit' value='Find »' /> 
115
                    </p> 
116
                </form> 
117
            <?php } ?> 
118
        </div> 
119
    </body> 
120
</html>

It's used to allow users to save their newsletter preferences.

Firstly, when the form loads, it looks like this.

preferencespreferencespreferences

When you click on the Find button, it takes the user to the next page.

preferences page 2preferences page 2preferences page 2

Finally, once a user clicks on the Save my preferences button, it displays the page shown in the following image.

preferences savedpreferences savedpreferences saved

Sending Messages

In this section, we'll build the last page, which allows us to send messages.

Let's create the admin/messages_send.php file as shown in the following snippet.

1
<?php 
2
require_once '../vendor/autoload.php';
3
require_once 'config.php';
4
5
use Symfony\Component\Mailer\Transport;
6
use Symfony\Component\Mailer\Mailer;
7
use Symfony\Component\Mime\Email;
8
9
$response = $db->loginRequired();
10
if (!$response) {
11
    header('Location: login.php');
12
    exit;
13
}
14
15
$title = "Send Message";
16
$id = (int) $_GET['id']; 
17
$tab = 'mess';
18
19
20
if(isset($_POST['submitted'])) {  
21
    $query = "SELECT * FROM subscribers WHERE id=0 "; 
22
    $emails = array(); 
23
24
    foreach($_POST['newsletter'] as $n) { 
25
        if($n['send'] == "true") { 
26
            $nlid = $n['nlid']; 
27
            $e = $db->query("SELECT subscriber_id FROM subscriptions WHERE newsletter_id=$nlid");
28
            foreach($e as $s) { 
29
                $sqlids .= " OR id=".$s['subscriber_id']; 
30
            } 
31
            $query .= $sqlids; 
32
        } 
33
    } 
34
35
    $subscribers = $db->query($query);
36
    foreach($subscribers as $sub) {
37
        $emails[$sub['email']] = $sub['name'];
38
    }
39
40
    $from = array(FROM_EMAIL => FROM_NAME);
41
    $mess = $db->query("SELECT * FROM messages WHERE id=$id");
42
    $message = $mess[0];
43
    $subject = $message['subject'];
44
    $tid = $message['template_id']; 
45
46
    $data = $db->query("SELECT body FROM templates WHERE id=$tid LIMIT 1");
47
    $template = $data[0]['body'];
48
49
    if($message['rightcol'] == '') {
50
        $leftcol = $message['leftcol'];
51
        $body = str_replace('%content%', $leftcol, $template);
52
    } else {
53
        $leftcol = $message['leftcol'];
54
        $rightcol = $message['rightcol'];
55
        $b = str_replace('%leftcol%', $leftcol, $template);
56
        $body = str_replace('%rightcol%', $rightcol, $b);
57
    }
58
59
    $transport = Transport::fromDsn('smtp://username:password@hostname:port');
60
    $mailer = new Mailer($transport); 
61
62
    $email = (new Email())
63
        ->from($from)
64
        ->to($emails)
65
        ->subject($subject)
66
        ->html($body);
67
    $mailer->send($email);
68
69
    header('Location: index.php');
70
} 
71
72
$newsletters = $db->query("SELECT * FROM newsletters");
73
74
foreach($newsletters as $nl) { 
75
    $nls .= '

76
<input type="hidden" name="newsletter['.$nl["id"].'][nlid]" value="'.$nl['id'].'" />

77
<input type="checkbox" name="newsletter['.$nl["id"].'][send]" value="true" '.$checked.'/>

78
<label for="newsletter['.$nl["id"].']">'.$nl['name'].'</label> - '.$nl['description'].'<br />

79
'; 
80
} 
81
82
$mess = $db->query("SELECT * FROM messages WHERE id=$id");
83
$message = $mess[0];
84
$subject = $message['subject'];
85
86
$content = '<a href="messages_preview.php?id='.$id.'" class="large" target="_new">preview »</a><br />

87
<form action="messages_send.php?id='.$id.'" method="POST">

88
    <p>

89
        Subject: '.$subject.'<br />

90
    </p>

91


92
    <p>Send to:<br />

93
        '.$nls.'

94
    </p>

95


96
    <p>

97
        <input type="submit" value="Send »" />

98
        <input type="hidden" value="1" name="submitted" />

99
    </p>

100
</form>';
101
102
include 'layout.php';
103
?>

It's used to build the form, which lists the newsletters in the first place. When an admin selects newsletters and submits the form, we retrieve the subscribers that are subscribed to these selected newsletters. And finally, we send an email to them.

It's also important to note that I've used the Symfony Mailer library to send emails. You can install it with Composer as shown in the following snippet. You need to adjust the settings as per your environment. To check more on this topic, you can visit this article, which provides an in-depth guide to the Symfony Mailer library.

1
$composer require symfony/mailer

The form looks like this:

send messagesend messagesend message

The Homepage

Now, let's build the admin home page, which displays overall statistics for our application.

Let's create the admin/index.php file with the following contents.

1
<?php 
2
require_once 'config.php'; 
3
4
$response = $db->loginRequired();
5
if (!$response) {
6
    header('Location: login.php');
7
    exit;
8
}
9
10
$users = $db->countQuery("SELECT COUNT(*) AS num FROM users");
11
$emails = $db->countQuery("SELECT COUNT(*) AS num FROM subscribers");
12
$subs = $db->countQuery("SELECT COUNT(*) AS num FROM subscriptions"); 
13
$nls = $db->countQuery("SELECT COUNT(*) AS num FROM newsletters");
14
$mess = $db->countQuery("SELECT COUNT(*) AS num FROM messages");
15
$temps = $db->countQuery("SELECT COUNT(*) AS num FROM templates");
16
17
$title = "Home"; 
18
19
$content = "<h3>current stats</h3> 

20
<p>$users user registered</p> 

21
<p>$emails subscribers</p> 

22
<p>$subs newsletter subscriptions</p> 

23
<p>$nls newsletters</p> 

24
<p>$mess messages</p> 

25
<p>$temps templates</p>"; 
26
27
include 'layout.php'; 
28
?>

And it looks like this:

home pagehome pagehome page

Conclusion

And with that, we've reached the end of this article as well. We covered a lot of ground in the tutorial, creating a newsletter app completely from scratch!

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.