Dynamic pricing table – PHP, MySQL, and Vue JS

A pricing table on a website is used to give your users an idea of how much you charge for your services. In this article, we are going to teach you, how you can create a dynamic pricing table using PHP, MySQL, and Vue JS. The pricing table is created in bootstrap so it will be mobile responsive.

From the admin panel, we will create multiple pricing tables and their data will be stored in the MySQL database. On the user side, the data will be fetched from the database and displayed to the user using Vue JS.

Pricing tables are basically packages offered at different prices so users can pick the one they find convenient. We will be using PHP as a server-side language, and MySQL to store the pricing table data in the database. And Vue JS to render the HTML.

Show form to add pricing table

To make our pricing table dynamic, we must first add it in the database. First, download Bootstrap from here and Vue JS from here. Paste the CSS and JS files in your project, we will be using them in a moment. After that, we need to create a form from which we can create a pricing table.

Each pricing table will have a title, a short description, an amount, and some features. The feature means the services you will be provided in that package.

The following code goes in your admin panel from where you want to add pricing tables.

<!-- include bootstrap -->
<link rel="stylesheet" type="text/css" href="bootstrap.min.css" />

<!-- include vue js -->
<script src="vue.min.js"></script>

<!-- container for vue js app -->
<div class="container" style="margin-top: 50px; margin-bottom: 50px;" id="addPricingTableApp">
	<div class="row">
		<!-- center align form -->
		<div class="offset-md-3 col-md-6">
			<h2 style="margin-bottom: 30px;">Create Pricing Table</h2>

			<!-- prevent the form from redirecting, and call JS function 'store' -->
			<form v-on:submit.prevent="store">

				<!-- get title of pricing table -->
				<div class="form-group">
                    <label>Title</label>
                    <input type="text" v-model="title" class="form-control" />
                </div>

                <!-- get description -->
                <div class="form-group">
                    <label>Description</label>
                    <input type="text" v-model="description" class="form-control" />
                </div>

                <!-- get amount -->
                <div class="form-group">
                    <label>Amount</label>
                    <input type="number" v-model="amount" class="form-control" />
                </div>

                <!-- list of features in this pricing table -->
                <h2 style="margin-top: 50px; margin-bottom: 50px;">Features</h2>

                <div class="row" style="margin-bottom: 50px;">
                    <div class="col-md-12">
                    	<!-- loop through an array in vue js app -->
                        <div v-for="(feature, index) in features" style="border: 1px solid black; padding: 10px; margin-bottom: 10px;">
                        	<!-- show input field to get the feature value -->
                            <div class="form-group">
                                <label>Feature</label>
                                <input type="text" class="form-control" v-model="features[index].value" placeholder="Can have HTML" />
                            </div>

                            <!-- button to delete the feature from pricing table -->
                            <button type="button" class="btn btn-danger btn-sm" v-on:click="deleteFeature" v-bind:data-index="index">Delete Feature</button>
                        </div>
                    </div>
                </div>

                <!-- button to add new feature -->
                <div class="row" style="margin-bottom: 20px;">
                    <div class="col-md-12">
                        <button type="button" class="btn btn-success" v-on:click="addFeature">Add Feature</button>
                    </div>
                </div>

                <!-- submit button -->
                <input type="submit" class="btn btn-info" value="Add Pricing" />
			</form>
		</div>
	</div>

	[show all pricing tables]
</div>

This will show a form with input fields. But when you click on the “Add Feature” button, nothing happens. This is because we need to render it using Vue JS.

<script>
	// initialize vue js app
    var addPricingTableApp = new Vue({
        el: "#addPricingTableApp", // id of container div
        data: {
        	// all values used in this app
        	title: "",
        	description: "",
        	amount: 0,
            features: [],
            pricings: []
        },
        // all methods
        methods: {
            
            // [store method]

        	// delete feature box
        	deleteFeature: function () {
        		var index = event.target.getAttribute("data-index");
        		this.features.splice(index, 1);
        	},
        	// add new feature box
        	addFeature: function() {
		        this.features.push({
		        	value: ""
		        });
		    }
        }
    });
</script>

Refresh the page now and you will be able to add and remove features, this is because of Vue JS. Now we need to call an AJAX request to save this pricing table data in the MySQL database so it can be fetched later.

Save pricing table in MySQL using PHP

First, we need to create a “store” method in Vue JS “methods” object. The following code goes in the [store method] section:

// called when form is submitted
store: function () {
    // get this app instance
    var self = this;

    // call an AJAX to create a new pricing table
    var ajax = new XMLHttpRequest();
    ajax.open("POST", "store.php", true);

    ajax.onreadystatechange = function () {
        if (this.readyState == 4) { // response received
            if (this.status == 200) { // response is successfull
                // console.log(this.responseText);

                // parse the response from JSON string to JS arrays and objects
                var response = JSON.parse(this.responseText);
                console.log(response);

                // if added in database, then prepend in local array too
                if (response.status == "success") {
                    self.pricings.unshift(response.pricing);
                } else {
                    // display an error message
                    alert(response.message);
                }
            }

            if (this.status == 500) {
                console.log(this.responseText);
            }
        }
    };

    // create form data object and append all the values in it
    var formData = new FormData();
    formData.append("title", this.title);
    formData.append("description", this.description);
    formData.append("amount", this.amount);
    formData.append("features", JSON.stringify(this.features));

    // actually sending the request
    ajax.send(formData);
},

Then we need to create a file named “store.php” that will handle this request. Following will be the code of the store.php file:

<?php

// connect with database
$conn = new PDO("mysql:host=localhost;dbname=test", "root", "");

// create tables if not exists
$sql = "CREATE TABLE IF NOT EXISTS pricings (
	id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
	title VARCHAR(255) NOT NULL,
	description TEXT NULL,
	amount DOUBLE NOT NULL
)";
$result = $conn->prepare($sql);
$result->execute();

$sql = "CREATE TABLE IF NOT EXISTS pricing_features (
	id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
	pricing_id INTEGER,
	value TEXT NOT NULL,
	FOREIGN KEY (pricing_id) REFERENCES pricings(id) ON UPDATE CASCADE ON DELETE CASCADE
)";
$result = $conn->prepare($sql);
$result->execute();

// insert in pricings table
$sql = "INSERT INTO pricings (title, description, amount) VALUES (:title, :description, :amount)";
$result = $conn->prepare($sql);
$result->execute([
	":title" => $_POST["title"],
	":description" => $_POST["description"],
	":amount" => $_POST["amount"]
]);
$pricing_id = $conn->lastInsertId();

// save all features in a separate table
$features = json_decode($_POST["features"]);
foreach ($features as $feature)
{
	$sql = "INSERT INTO pricing_features(pricing_id, value) VALUES (:pricing_id, :value)";
	$result = $conn->prepare($sql);
	$result->execute([
		":pricing_id" => $pricing_id,
		":value" => $feature->value
	]);
}

// get the pricing table just inserted in database
$sql = "SELECT * FROM pricings WHERE id = :id";
$result = $conn->prepare($sql);
$result->execute([
	":id" => $pricing_id
]);
$pricing = $result->fetch();

// send the response back to client with new pricing table
echo json_encode([
	"status" => "success",
	"message" => "Pricing table has been added.",
	"pricing" => $pricing
]);
exit();

This assumes that you have a database named “test”. You can change it as per yours if you already have one. This will create the pricings and pricing_features table if you do not already have one.

Refresh the page now and fill out the pricing table form and hit submit. Then refresh your phpMyAdmin page and you will see a new row in the “pricings” table. You will also see multiple rows in the “pricing_features” table.

Show pricing table on the user side

Now the pricing table and its features have been saved in the MySQL database, you can easily display them on your user side. Paste the following code anywhere you want to show the pricing table to your users:

<link rel="stylesheet" type="text/css" href="bootstrap.min.css" />
<script src="vue.min.js"></script>

<style>
	.box-shadow {
		box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
	}
</style>

<div class="container" style="margin-top: 50px; margin-bottom: 50px;" id="pricingTableApp">
	<div class="row">
		<div class="col-md-12">
			<h2 class="text-center" style="margin-bottom: 30px;">Pricing Table</h2>
		</div>
	</div>

	<div class="row">
		<div class="col-md-3" v-for="(pricing, index) in pricings">

			<div class="card mb-4 box-shadow">
				<div class="card-header">
					<h4 class="my-0 font-weight-normal text-center" v-text="pricing.title"></h4>
				</div>

				<div class="card-body">
					<h1 class="card-title pricing-card-title text-center" v-text="'$' + pricing.amount"></h1>
					
					<ul class="list-unstyled mt-3 mb-4">
						<li v-for="(feature, featureIndex) in pricing.features" class="text-center">
							<span v-html="feature.value"></span>
						</li>
					</ul>

					<button type="button" class="btn btn-lg btn-block btn-outline-primary">Buy now</button>
				</div>
	        </div>
		</div>
	</div>
</div>

We need to call an AJAX request to get the pricing tables from the server. So following will be your Javascript code on same file:

<script>
    var pricingTableApp = new Vue({
        el: "#pricingTableApp",
        data: {
            pricings: []
        },
        methods: {
        	getData: function () {
        		var self = this;
        		var ajax = new XMLHttpRequest();
        		ajax.open("POST", "get-data.php", true);

        		ajax.onreadystatechange = function () {
        			if (this.readyState == 4) {
        				if (this.status == 200) {
        					// console.log(this.responseText);
        					var response = JSON.parse(this.responseText);
        					console.log(response);

        					if (response.status == "success") {
        						self.pricings = response.pricings;
        					}
        				}

        				if (this.status == 500) {
        					console.log(this.responseText);
        				}
        			}
        		};

        		var formData = new FormData();
        		ajax.send(formData);
        	}
        },
        mounted: function () {
        	this.getData();
        }
    });
</script>

Now create a file named “get-data.php” that will fetch pricing table along with their features and return to the client.

<?php

// connect with database
$conn = new PDO("mysql:host=localhost;dbname=test", "root", "");

// get all pricing tables sorting by amount from lowest to highest
$sql = "SELECT * FROM pricings ORDER BY amount ASC";
$result = $conn->prepare($sql);
$result->execute();
$pricings = $result->fetchAll();

// get all the features of each pricing table too
$data = [];
foreach ($pricings as $pricing)
{
	$sql = "SELECT * FROM pricing_features WHERE pricing_id = :pricing_id";
	$result = $conn->prepare($sql);
	$result->execute([
		":pricing_id" => $pricing["id"]
	]);
	$pricing_features = $result->fetchAll();

	$pricing["features"] = $pricing_features;
	array_push($data, $pricing);
}

// send the response back to client with the data
echo json_encode([
	"status" => "success",
	"message" => "Data has been fetched.",
	"pricings" => $data
]);
exit();

Refresh the page now and you will be able to see a beautifully designed pricing table on your website.

Edit & delete the pricing table

We will have a pure dynamic pricing table if we were able to modify or delete the features from it from admin panel.

At this point, your desired work is work. But you can go ahead and allow an admin to edit or delete the pricing table. Suppose you want to add more features to some package, or you want to delete a specific pricing table altogether.

Display all pricing tables

First, we need to display a list of all pricing tables in an HTML table. The following code goes in the [show all pricing tables] section:

<!-- show all pricing tables -->
<div class="row" style="margin-top: 50px;">
	<div class="col-md-12">
		<table class="table table-bordered">
			<!-- table heading -->
			<tr>
				<th>ID</th>
				<th>Title</th>
				<th>Description</th>
				<th>Actions</th>
			</tr>

			<!-- loop through an array of pricing tables -->
			<tr v-for="(pricing, index) in pricings">
				<td v-text="pricing.id"></td>
				<td v-text="pricing.title"></td>
				<td v-text="pricing.description"></td>
				<td>
					<!-- edit pricing table link -->
					<a class="btn btn-link" v-bind:href="'edit.php?id=' + pricing.id">
						Edit
					</a>

					<!-- form to delete pricing table -->
					<form v-on:submit.prevent="deletePricing">
						<input type="hidden" name="id" v-model="pricing.id" required />
						<input type="submit" class="btn btn-danger btn-sm" value="Delete" />
					</form>
				</td>
			</tr>
		</table>
	</div>
</div>

We already have a Vue JS instance named addPricingTableApp. When this instance is mounted, we will call an AJAX request to get the pricing tables from MySQL database using PHP.

var addPricingTableApp = new Vue({
    el: "#addPricingTableApp",
    data: {
    	// other variables
    },
    methods: {
    	// other methods

    	// method to get all pricing tables from server
    	getData: function () {
    		// get this app instance
    		var self = this;

    		// call an AJAX to fetch all pricing tables
    		var ajax = new XMLHttpRequest();
    		ajax.open("POST", "get-data.php", true);

    		ajax.onreadystatechange = function () {
    			if (this.readyState == 4) { // response received
    				if (this.status == 200) { // response is successfull
    					// console.log(this.responseText);

    					// parse the response from JSON string to JS arrays and objects
    					var response = JSON.parse(this.responseText);
    					console.log(response);

    					// render the data in table
    					if (response.status == "success") {
    						self.pricings = response.pricings;
    					}
    				}

    				if (this.status == 500) {
    					console.log(this.responseText);
    				}
    			}
    		};

    		// send an AJAX request to the server
    		var formData = new FormData();
    		ajax.send(formData);
    	},

    	// method to delete pricing table
    	deletePricing: function () {
    		//
    	},
    	
    	// other methods
    },
    mounted: function () { // called when the DOM is mounted
    	this.getData();
    }
});

Refresh the admin side now and you will be able to view all the pricing tables in an HTML table. We already have a separate file for fetching data from the database “get-data.php“, so it will fetch easily.

Update

The table above created will also display edit and a delete button. When you click on the edit button, it will take you to the “edit.php” file with a parameter having the value of that pricing table. Now we need to create a file named “edit.php” which will show a form with an auto-populated value of that pricing table.

The edit page will also have the functionality to add or delete features from the pricing table. We will also be using Vue JS here to populate the values in input fields.

<link rel="stylesheet" type="text/css" href="bootstrap.min.css" />
<script src="vue.min.js"></script>

<div class="container" style="margin-top: 50px; margin-bottom: 50px;" id="editPricingTableApp">
	<div class="row">
		<div class="offset-md-3 col-md-6">
			<h2 style="margin-bottom: 30px;">Edit Pricing Table</h2>

			<form v-on:submit.prevent="update" v-if="pricing != null">
				<div class="form-group">
                    <label>Title</label>
                    <input type="text" v-model="pricing.title" class="form-control" />
                </div>

                <div class="form-group">
                    <label>Description</label>
                    <input type="text" v-model="pricing.description" class="form-control" />
                </div>

                <div class="form-group">
                    <label>Amount</label>
                    <input type="number" v-model="pricing.amount" class="form-control" />
                </div>

                <h2 style="margin-top: 50px; margin-bottom: 50px;">Features</h2>

                <div class="row" style="margin-bottom: 50px;">
                    <div class="col-md-12">
                        <div v-for="(feature, index) in pricing.features" style="border: 1px solid black; padding: 10px; margin-bottom: 10px;">
                            <div class="form-group">
                                <label>Feature</label>
                                <input type="text" class="form-control" v-model="pricing.features[index].value" placeholder="Can have HTML" />
                            </div>

                            <button type="button" class="btn btn-danger btn-sm" v-on:click="deleteFeature" v-bind:data-index="index">Delete Feature</button>
                        </div>
                    </div>
                </div>

                <div class="row" style="margin-bottom: 20px;">
                    <div class="col-md-12">
                        <button type="button" class="btn btn-success" v-on:click="addFeature">Add Feature</button>
                    </div>
                </div>

                <input type="submit" class="btn btn-warning" value="Edit Pricing" />
			</form>
		</div>
	</div>

    <!-- hidden input field to get the ID from URL in javascript -->
	<input type="hidden" id="pricing-id" value="<?php echo $_GET['id']; ?>" />
</div>

This will be almost the same as adding a new pricing table. First, we need to fetch the values from the MySQL database by calling an AJAX request.

<script>
    var editPricingTableApp = new Vue({
        el: "#editPricingTableApp",
        data: {
        	id: "",
            pricing: null
        },
        methods: {

            // [update method goes here]

        	getPricingTable: function () {
        		var self = this;

        		var ajax = new XMLHttpRequest();
        		ajax.open("POST", "get-pricing-table.php", true);

        		ajax.onreadystatechange = function () {
        			if (this.readyState == 4) {
        				if (this.status == 200) {
        					// console.log(this.responseText);
        					var response = JSON.parse(this.responseText);
        					console.log(response);

        					if (response.status == "success") {
        						self.pricing = response.pricing;
        					}
        				}

        				if (this.status == 500) {
        					console.log(this.responseText);
        				}
        			}
        		};

        		var formData = new FormData();
        		formData.append("id", self.id);
        		ajax.send(formData);
        	},
        	deleteFeature: function () {
        		var index = event.target.getAttribute("data-index");
        		this.pricing.features.splice(index, 1);
        	},
        	addFeature: function() {
		        this.pricing.features.push({
		        	value: ""
		        });
		    }
        },
        mounted: function () {
        	this.id = document.getElementById("pricing-id").value;
        	this.getPricingTable();
        }
    });
</script>

Then we need to create a file named “get-pricing-table.php” to fetch the data of this pricing table only.

<?php

// connect with database
$conn = new PDO("mysql:host=localhost;dbname=test", "root", "");

// get pricing table using ID
$sql = "SELECT * FROM pricings WHERE id = :id";
$result = $conn->prepare($sql);
$result->execute([
    ":id" => $_POST["id"]
]);
$pricing = $result->fetch();

// get all the features too of this pricing table
$sql = "SELECT * FROM pricing_features WHERE pricing_id = :pricing_id";
$result = $conn->prepare($sql);
$result->execute([
    ":pricing_id" => $pricing["id"]
]);
$pricing_features = $result->fetchAll();

$pricing["features"] = $pricing_features;

// send the response back to client with the data
echo json_encode([
    "status" => "success",
    "message" => "Pricing table has been fetced.",
    "pricing" => $pricing
]);
exit();

Refresh the edit page now and you will be able to view the data in input fields along with all the features of that pricing table.

Now when this form submits, we need to call an AJAX request to update the data. The following code goes in your [update method goes here] section:

update: function () {
    var self = this;

    var ajax = new XMLHttpRequest();
    ajax.open("POST", "update.php", true);

    ajax.onreadystatechange = function () {
        if (this.readyState == 4) {
            if (this.status == 200) {
                // console.log(this.responseText);
                var response = JSON.parse(this.responseText);
                console.log(response);

                alert(response.message);
            }

            if (this.status == 500) {
                console.log(this.responseText);
            }
        }
    };

    var formData = new FormData();
    formData.append("id", this.id);
    formData.append("title", this.pricing.title);
    formData.append("description", this.pricing.description);
    formData.append("amount", this.pricing.amount);
    formData.append("features", JSON.stringify(this.pricing.features));
    ajax.send(formData);
},

Finally, create a file named “update.php” that will handle this request and will update the pricing table.

<?php

// connect with database
$conn = new PDO("mysql:host=localhost;dbname=test", "root", "");

// udpdate the pricing table
$sql = "UPDATE pricings SET title = :title, description = :description, amount = :amount WHERE id = :id";
$result = $conn->prepare($sql);
$result->execute([
	":title" => $_POST["title"],
	":description" => $_POST["description"],
	":amount" => $_POST["amount"],
	":id" => $_POST["id"]
]);

// delete all the old features
$sql = "DELETE FROM pricing_features WHERE pricing_id = :pricing_id";
$result = $conn->prepare($sql);
$result->execute([
	":pricing_id" => $_POST["id"]
]);

// insert new features
$features = json_decode($_POST["features"]);
foreach ($features as $feature)
{
	$sql = "INSERT INTO pricing_features(pricing_id, value) VALUES (:pricing_id, :value)";
	$result = $conn->prepare($sql);
	$result->execute([
		":pricing_id" => $_POST["id"],
		":value" => $feature->value
	]);
}

// send the response back to client
echo json_encode([
	"status" => "success",
	"message" => "Pricing table has been updated."
]);
exit();

Now refresh the page and try to update any pricing table, you will see it will be changed in the database as well as on the user side. On the user side, you have to refresh the page to see the changes.

Delete

You already have a method named “deletePricing” in your Vue JS instance named “addPricingTableApp“. You just need to modify it as the following:

// method to delete pricing table
deletePricing: function () {
	// get this app instance
	var self = this;

	// get form
	var form = event.target;

	// call an AJAX to delete the pricing table
	var ajax = new XMLHttpRequest();
	ajax.open("POST", "delete.php", true);

	ajax.onreadystatechange = function () {
		if (this.readyState == 4) { // response received
			if (this.status == 200) { // response is successfull
				// console.log(this.responseText);

				// parse the response from JSON string to JS arrays and objects
				var response = JSON.parse(this.responseText);
				console.log(response);

				// remove from local array if deleted from server
				if (response.status == "success") {
					for (var a = 0; a < self.pricings.length; a++) {
						var pricing = self.pricings[a];
						if (pricing.id == form.id.value) {
							self.pricings.splice(a, 1);
							break;
						}
					}
				} else {
					// display an error message
					alert(response.message);
				}
			}

			if (this.status == 500) {
				console.log(this.responseText);
			}
		}
	};

	// append form in form data object
	var formData = new FormData(form);

	// call AJAX with form data
	ajax.send(formData);
},

Now we need to create a file named “delete.php” and in this file, we will simply run the delete query.

<?php

// connect with database
$conn = new PDO("mysql:host=localhost;dbname=test", "root", "");

// create a query to delete the pricing table from database
$sql = "DELETE FROM pricings WHERE id = :id";

// prepare the query
$result = $conn->prepare($sql);

// execute the query
$result->execute([
	":id" => $_POST["id"]
]);

// send the response back to client
echo json_encode([
	"status" => "success",
	"message" => "Pricing table has been deleted."
]);
exit();

Refresh the page now on your admin side and hit the delete button. You will see the row will be deleted from an HTML table and also from the database. If you refresh the user side, it will be removed from there too.

Also learn how to create a dynamic testimonial section on your website.

So that’s how you can create a fully responsive dynamic pricing table on your website, which can be managed from the admin panel. You do not have to go into the code to make changes. If you face any problems in following this, please do let us know in the comments section below.

[wpdm_package id=’1395′]

Instagram feed without API – PHP, MySQL

In this tutorial, we are going to teach you how can you show instagram feed in your PHP website without using any API.

Video tutorial:

First, we need to create a table in our database where we will save all the latest posts from our Instagram page or profile.

<?php

    // connect with database
    $conn = new PDO("mysql:host=localhost:8889;dbname=test", "root", "root");

    // create table to store latest instagram feeds
    $sql = "CREATE TABLE IF NOT EXISTS instagram (
        id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
        url TEXT DEFAULT NULL,
        image_path TEXT DEFAULT NULL,
        video_path TEXT DEFAULT NULL,
        caption TEXT DEFAULT NULL,
        likes TEXT DEFAULT NULL,
        comments TEXT DEFAULT NULL
    )";
    $result = $conn->prepare($sql);
    $result->execute();

    [handle form submission]

?>

[form goes here]

This will create a table named “Instagram” in your database named “test”. The table will have the following columns:

  1. id (primary key)
  2. URL shortcode of Instagram post
  3. Path of the image stored in your server
  4. Path of video stored in your server
  5. Caption (if any) of post
  6. Number of likes on a post
  7. Number of comments on a post

Saving Instagram Feed in MySQL using PHP

Now you need to show a form that will show a link that you need to open in your browser. That link will output the JSON string, you need to copy-paste that JSON string into a textbox in the form.

The following code goes in the [form goes here] section:

<form method="POST" action="index.php">
    <p>
        <a href="https://www.instagram.com/{your_page_name}/?__a=1">Goto this link and paste the JSON string in textbox below</a>
    </p>

    <p>
        <label>Paste JSON</label>
        <textarea name="json" rows="10" cols="30" required></textarea>
    </p>

    <input type="submit" name="submit" class="Save" />
</form>

[show instagram feed]

Refresh the page and you will see a link, a textbox, and a submit button. Click on the link and you will see a JSON string, paste that JSON in the textbox and hit submit. Now we need to write the code to save that JSON in our database.

The following code goes in the [handle form submission] section:

// check if form is submitted
if (isset($_POST["submit"]))
{
    // get JSON from textarea
    $json = $_POST["json"];

    // decode JSON into arrays and objects
    $content = json_decode($json);

    // get all the latest posts
    $edges = $content->graphql->user->edge_owner_to_timeline_media->edges;

    mkdir("instagram-media");

    // delete previous posts from our database
    $sql = "DELETE FROM instagram";
    $result = $conn->prepare($sql);
    $result->execute();

    // loop through all posts
    foreach ($edges as $edge)
    {
        // get single post
        $node = $edge->node;

        // get URL shortcode of post
        $url = $node->shortcode;

        // get caption, if any
        $caption = $node->edge_media_to_caption->edges[0]->node->text;

        // get number of likes
        $likes = $node->edge_liked_by->count;

        // get total number of comments
        $comments = $node->edge_media_to_comment->count;

        // save image in our server if uploaded
        $image_path = "";
        if (!is_null($node->display_url))
        {
            $image_path = "instagram-media/" . $url . ".png";
            file_put_contents($image_path, file_get_contents($node->display_url));
        }

        // save video in our server if uploaded
        $video_path = "";
        if (!is_null($node->video_url))
        {
            $video_path = "instagram-media/" . $url . ".mp4";
            file_put_contents($video_path, file_get_contents($node->video_url));
        }

        // insert in database
        $sql = "INSERT INTO instagram(url, image_path, video_path, caption, likes, comments) VALUES (:url, :image_path, :video_path, :caption, :likes, :comments)";
        $result = $conn->prepare($sql);
        $result->execute([
            ":url" => $url,
            ":image_path" => $image_path,
            ":video_path" => $video_path,
            ":caption" => $caption,
            ":likes" => $likes,
            ":comments" => $comments
        ]);
    }

    echo "<p>Done</p>";
}

[fetch all instagram feeds from database]

This will fetch the latest posts from your instagram page or profile. If there is any image or video attached to it, it will be saved in a folder named “instagram-media”. And it’s path will be stored in the database. If all goes well, it will display a message “Done”.

Fetching Instagram Feed from MySQL via PHP

Now we can easily fetch all latest instagram feeds from our database. Replace the section [fetch all instagram feeds from database] with the following code:

// get all posts from database
$sql = "SELECT * FROM instagram ORDER BY id ASC";
$result = $conn->query($sql);
$instagram_feed = $result->fetchAll();

Finally, we need to show them in our HTML. Replace the section [show instagram feed] with the following code:

<main>
    <div class="container">
        <div class="gallery">

            <!-- loop through all rows from database -->
            <?php foreach ($instagram_feed as $feed): ?>
                <div class="gallery-item" tabindex="0">
                    <!-- wrap with anchor tag, when clicked will go to instagram detail post page -->
                    <a href="https://www.instagram.com/p/<?php echo $feed['url']; ?>" target="_blank" style="color: white;">

                        <!-- thumbnail of post -->
                        <img src="<?php echo $feed['image_path']; ?>" class="gallery-image" />

                        <div class="gallery-item-info">
                            <ul>
                                <!-- show no. of likes -->
                                <li class="gallery-item-likes">
                                    <i class="fa fa-heart"></i>
                                    <?php echo $feed["likes"]; ?>
                                </li>
                                
                                <!-- show no. of comments -->
                                <li class="gallery-item-comments">
                                    <i class="fa fa-comment"></i>
                                    <?php echo $feed["comments"]; ?>
                                </li>
                            </ul>

                            <!-- show caption -->
                            <p><?php echo $feed["caption"]; ?></p>
                        </div>
                    </a>
                </div>
            <?php endforeach; ?>

        </div>
    </div>
</main>

<!-- style CSS -->
<link rel="stylesheet" type="text/css" href="instagram.css?v=<?php echo time(); ?>" />

<!-- font awesome -->
<script src="https://use.fontawesome.com/b8680c3f3d.js"></script>

Comments has been added with each line for explanation. Each post will be wrapped in an anchor tag, so when you clicked on it, you will be redirected to the post’s page on instagram.

Styles

Last thing you are going to need is CSS styles. So create a file named “instagram.css” and paste the following code in it:

/*

All grid code is placed in a 'supports' rule (feature query) at the bottom of the CSS (Line 310). 
        
The 'supports' rule will only run if your browser supports CSS grid.

Flexbox and floats are used as a fallback so that browsers which don't support grid will still recieve a similar layout.

*/

/* Base Styles */

*,
*::before,
*::after {
    box-sizing: border-box;
}

body {
    font-family: "Open Sans", Arial, sans-serif;
    min-height: 100vh;
    background-color: #fafafa;
    color: #262626;
    padding-bottom: 3rem;
}

img {
    display: block;
}

.container {
    max-width: 93.5rem;
    margin: 0 auto;
    padding: 0 2rem;
}

.btn {
    display: inline-block;
    font: inherit;
    background: none;
    border: none;
    color: inherit;
    padding: 0;
    cursor: pointer;
}

.btn:focus {
    outline: 0.5rem auto #4d90fe;
}

.visually-hidden {
    position: absolute !important;
    height: 1px;
    width: 1px;
    overflow: hidden;
    clip: rect(1px, 1px, 1px, 1px);
}

/* Profile Section */

.profile {
    padding: 5rem 0;
}

.profile::after {
    content: "";
    display: block;
    clear: both;
}

.profile-image {
    float: left;
    width: calc(33.333% - 1rem);
    display: flex;
    justify-content: center;
    align-items: center;
    margin-right: 3rem;
}

.profile-image img {
    border-radius: 50%;
}

.profile-user-settings,
.profile-stats,
.profile-bio {
    float: left;
    width: calc(66.666% - 2rem);
}

.profile-user-settings {
    margin-top: 1.1rem;
}

.profile-user-name {
    display: inline-block;
    font-size: 3.2rem;
    font-weight: 300;
}

.profile-edit-btn {
    font-size: 1.4rem;
    line-height: 1.8;
    border: 0.1rem solid #dbdbdb;
    border-radius: 0.3rem;
    padding: 0 2.4rem;
    margin-left: 2rem;
}

.profile-settings-btn {
    font-size: 2rem;
    margin-left: 1rem;
}

.profile-stats {
    margin-top: 2.3rem;
}

.profile-stats li {
    display: inline-block;
    font-size: 1.6rem;
    line-height: 1.5;
    margin-right: 4rem;
    cursor: pointer;
}

.profile-stats li:last-of-type {
    margin-right: 0;
}

.profile-bio {
    font-size: 1.6rem;
    font-weight: 400;
    line-height: 1.5;
    margin-top: 2.3rem;
}

.profile-real-name,
.profile-stat-count,
.profile-edit-btn {
    font-weight: 600;
}

/* Gallery Section */

.gallery {
    display: flex;
    flex-wrap: wrap;
    margin: -1rem -1rem;
    padding-bottom: 3rem;
}

.gallery-item {
    position: relative;
    flex: 1 0 22rem;
    margin: 1rem;
    color: #fff;
    cursor: pointer;
}

.gallery-item:hover .gallery-item-info,
.gallery-item:focus .gallery-item-info {
    display: inline-block;
    justify-content: center;
    align-items: center;
    position: absolute;
    top: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.3);
}
.gallery-item-info ul {
    text-align: center;
    position: relative;
    top: 50%;
    transform: translateY(-50%);
    padding-left: 0px;
    margin-top: 0px;
    margin-bottom: 0px;
}
.gallery-item-info p {
    position: relative;
    top: 50%;
    text-align: center;
    margin-top: 0px;
    padding-left: 10px;
    padding-right: 10px;
}

.gallery-item-info {
    display: none;
}

.gallery-item-info li {
    display: inline-block;
    font-size: 1.7rem;
    font-weight: 600;
}

.gallery-item-likes {
    margin-right: 2.2rem;
}

.gallery-item-type {
    position: absolute;
    top: 1rem;
    right: 1rem;
    font-size: 2.5rem;
    text-shadow: 0.2rem 0.2rem 0.2rem rgba(0, 0, 0, 0.1);
}

.fa-clone,
.fa-comment {
    transform: rotateY(180deg);
}

.gallery-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Media Query */

@media screen and (max-width: 40rem) {
    .profile {
        display: flex;
        flex-wrap: wrap;
        padding: 4rem 0;
    }

    .profile::after {
        display: none;
    }

    .profile-image,
    .profile-user-settings,
    .profile-bio,
    .profile-stats {
        float: none;
        width: auto;
    }

    .profile-image img {
        width: 7.7rem;
    }

    .profile-user-settings {
        flex-basis: calc(100% - 10.7rem);
        display: flex;
        flex-wrap: wrap;
        margin-top: 1rem;
    }

    .profile-user-name {
        font-size: 2.2rem;
    }

    .profile-edit-btn {
        order: 1;
        padding: 0;
        text-align: center;
        margin-top: 1rem;
    }

    .profile-edit-btn {
        margin-left: 0;
    }

    .profile-bio {
        font-size: 1.4rem;
        margin-top: 1.5rem;
    }

    .profile-edit-btn,
    .profile-bio,
    .profile-stats {
        flex-basis: 100%;
    }

    .profile-stats {
        order: 1;
        margin-top: 1.5rem;
    }

    .profile-stats ul {
        display: flex;
        text-align: center;
        padding: 1.2rem 0;
        border-top: 0.1rem solid #dadada;
        border-bottom: 0.1rem solid #dadada;
    }

    .profile-stats li {
        font-size: 1.4rem;
        flex: 1;
        margin: 0;
    }

    .profile-stat-count {
        display: block;
    }
}

/*

The following code will only run if your browser supports CSS grid.

Remove or comment-out the code block below to see how the browser will fall-back to flexbox & floated styling. 

*/

@supports (display: grid) {
    .profile {
        display: grid;
        grid-template-columns: 1fr 2fr;
        grid-template-rows: repeat(3, auto);
        grid-column-gap: 3rem;
        align-items: center;
    }

    .profile-image {
        grid-row: 1 / -1;
    }

    .gallery {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
        grid-gap: 2rem;
    }

    .profile-image,
    .profile-user-settings,
    .profile-stats,
    .profile-bio,
    .gallery-item,
    .gallery {
        width: auto;
        margin: 0;
    }

    @media (max-width: 40rem) {
        .profile {
            grid-template-columns: auto 1fr;
            grid-row-gap: 1.5rem;
        }

        .profile-image {
            grid-row: 1 / 2;
        }

        .profile-user-settings {
            display: grid;
            grid-template-columns: auto 1fr;
            grid-gap: 1rem;
        }

        .profile-edit-btn,
        .profile-stats,
        .profile-bio {
            grid-column: 1 / -1;
        }

        .profile-user-settings,
        .profile-edit-btn,
        .profile-settings-btn,
        .profile-bio,
        .profile-stats {
            margin: 0;
        }
    }
}

If you refresh the page now, you will be able to view all the posts from your database in a grid format. There will be 3 posts in each row. Now you have all the code with the style, you can customize it as per your needs.

So that’s how you can show your Instagram feed without API using simple PHP and MySQL.

Laravel error email notification – PHP

In this article, we are going to show you, how you can receive an email notification when there is an internal server error in your Laravel live site using PHP. Internal Server Error has an error code of 500, which means there is an error in your code. When your website is live, we usually disable the “debug mode” in our “.env” file like this:

APP_DEBUG=false

This prevents displaying the error messages to the user. Because otherwise, it will display your code to the users. But for example, you get a message from one of your users saying that they are seeing an “Internal Server Error” on some page. How would you know what causes the problem for that user ?

Log Laravel error

That is where the “error reporting” comes in. There are many types of error reporting, the most common of them is “logging” which means that you can write the error message in a separate file. But you have to check the file regularly and on daily basis.

But you must know immediately when the error happens. That is why we will be sending an email as soon as there is an error in your code.

Video tutorial:

Handle Laravel error

First, open the file “app/Exceptions/Handler.php” and include the “Mail” and “Auth” libraries in it.

// include Mail to send an email
use Mail;

// include Auth to check if the error happened for an authenticated user
use Auth;

Then change the content of the “register” method to the following:

public function register()
{
    $this->reportable(function (Throwable $e) {
        // get error message
        $error_message = $e->getMessage();

        // get file
        $error_file = $e->getFile();

        // get line number
        $error_line = $e->getLine();

        // get method, GET or POST
        $method = request()->method();

        // get full URL including query string
        $full_url = request()->fullUrl();

        // get route name
        $route = "";

        // get list of all middlewares attached to that route
        $middlewares = "";

        // data with the request
        $inputs = "";
        if (request()->route() != null)
        {
            $route = "uri: " . request()->route()->getName();
            $middlewares = json_encode(request()->route()->gatherMiddleware());
            $inputs = json_encode(request()->all());
        }

        // get IP address of user
        $ip = request()->ip();

        // get user browser or request source
        $user_agent = request()->userAgent();

        // create email body
        $html = $error_message . "\n\n";
        $html .= "File: " . $error_file . "\n\n";
        $html .= "Line: " . $error_line . "\n\n";
        $html .= "Inputs: " . $inputs . "\n\n";
        $html .= "Method: " . $method . "\n\n";
        $html .= "Full URL: " . $full_url . "\n\n";
        $html .= "Route: " . $route . "\n\n";
        $html .= "Middlewares: " . $middlewares . "\n\n";
        $html .= "IP: " . $ip . "\n\n";
        $html .= "User Agent: " . $user_agent . "\n\n";

        // for testing purpose only
        Auth::loginUsingid(1);

        // check if user is logged in
        if (Auth::check())
        {
            // get email of user that faced this error
            $html .= "User: " . Auth::user()->email;
        }

        // subject of email
        $subject = "Internal Server Error";

        // send an email
        Mail::raw($html, function ($message) use ($subject) {
            // developer email
            $message->to("support@adnan-tech.com")
                ->subject($subject);
        });
    });
}

Make sure you have set the SMTP configurations in your “.env” file:

MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=465
MAIL_USERNAME=your_email@gmail.com
MAIL_PASSWORD=your_password
MAIL_ENCRYPTION=ssl
MAIL_FROM_ADDRESS=my_email@gmail.com
MAIL_FROM_NAME="${APP_NAME}"

And finally, enable the less secure apps for the above “your_email@gmail.com” account from here.

Enable-less-secure-apps-gmail

You are all set. Now, if you get any error in your live site, you will receive an email notification with details. That’s how you get an email notification whenever you have an error in your Laravel application.

Learn how to enable debugbar in your Laravel application from this tutorial.

10 Javascript Libraries for Every Web Project

In this article, we are going to discuss 10 useful Javascript libraries that you need to use in almost every web project. We will discuss the following libraries along with their use:

  1. Sweetalert
  2. Datetime Picker by XD Soft
  3. Socket IO
  4. DataTable
  5. RichText
  6. PDFObject
  7. Vue JS
  8. Notify JS
  9. Chart JS
  10. TimeCircles

1. Sweetalert

Sweetalert is a JS library used to display alert messages. You can also use it for displaying confirm messages i.e. ask for the user’s confirmation before performing any action. To display a simple alert message:

swal("Message goes here");

To display an alert with a title:

swal("My Title", "Text goes here");

Success alert:

swal("Done", "Your message goes here", "success");

Error alert:

swal("Error", "Error message goes here", "error");

For confirmation dialog:

swal({
	title: "Confirm",
	text: "Are you sure, this data will be removed ?",
	icon: "warning",
	buttons: true,
	dangerMode: true,
})
.then((isOkay) => {
	if (isOkay) {
		//
	}
});

Learn more about how to use the confirmation dialog properly from here. You can also use it as a prompt:

swal("Enter your name:", {
    content: "input",
})
.then((value) => {
    if (value == "") {
        return false;
    }
    console.log(value);
});

You can read more about it from their official page.

2. Datetime Picker by XD Soft

It is a JS library that is used to display a calendar with a date and time picker. You can use it to get the user’s date of birth during registration. The basic usage is, you need to give your input field a unique ID:

<input type="text" id="datetimepicker" />

Then you can display the datetimepicker by calling the function:

jQuery("#datetimepicker").datetimepicker();

Make sure you have jQuery included in your project, this library requires jQuery. You can see this library in action from here. The complete documentation of this library is found on their official page.

3. Socket IO

Socket IO is used for real-time communication. If you are working on a feature that requires data from the server after regular intervals, there are 2 options to achieve this:

  1. Polling
  2. Sockets

Polling

This technique requires calling an AJAX request to the server every few seconds. This will overload the server with a lot of requests. There will be a lot of unnecessary requests to the server and if you have a lot of users, then your website might get crashed.

Sockets

The solution to the above problem is the sockets. Sockets attach a listener to the client-side and emit events from the server. The client is continuously listening to that event, whenever that event is received, the client will perform the action accordingly.

The difference between Polling and Sockets can be explained by this example:

For example, you are going on a trip with your family. Your Dad is driving the car and you are in the back seat. And you are continuously asking your Dad “did we reached the destination ?” every few minutes. This is Polling.

And in the other case, you ask your Dad “please inform me when we reach our destination”. Now you are silently listening to your Dad, who will send an event to you only when you reached your destination. This is Socket.

Learn how to use sockets with Node JS. Complete documentation can be found on their official page.

4. DataTable

This library is used to display data in tabular form. It has a built-in search feature, that allows you to search the entire table. You can search value from any row from any column. The search is case insensitive so you do not have to worry about capital or small letters.

It also has a sorting feature that allows you to sort the data based on any column. It also allows you to decide how many records you want to show on one page (e.g. 25, 50, 75, or 100). It has built-in pagination based on the number of records you are displaying on one page.

Its usage is rather simple. You just need to give your table tag a unique ID:

<table id="table"></table>

Then in your Javascript, simply initialize the library:

$("#table").DataTable();

It also requires jQuery to be included in your project. You can see this library in action from here. You can learn more about it from their official page.

5. RichText

This library applies to your textarea tag. It makes your text area as WYSIWYG (What You See Is What You Get). It enhances the Textarea field and gives it more options like:

  1. Text formatiing (font size, bold, italic, underline, color).
  2. Text alignment (left, center, right, justify).
  3. Headings & paragraphs.
  4. Upload images.
  5. Attach URLs.
  6. Adding tables.
  7. Or you can even add HTML tags too.

Its usage is almost the same as the DataTable library. You need to give your textarea a unique ID:

<textarea id="content" name="content"></textarea>

And in your Javascript, you need to initialize it like this:

$("#content").richText();

This library also requires jQuery to be included. Full documentation of this library can be found here.

6. PDFObject

When working with PDF files, if you want to show PDF files from your website, you have 3 options:

  1. Make the PDF file downloadable, so user can view it offline too.
  2. Display the PDF in a new window or tab.
  3. Embed the PDF in your website specific section.

This library fulfills the 3rd approach. It embeds the PDF file in your website using a div tag. Place a div tag anywhere in your code where you want to show the PDF file:

<div id="pdf"></div>

Then in your Javascript, initialize the library with a URL of the PDf file:

PDFObject.embed("my-file.pdf", "#pdf");

More information can be found on pdfobject.com.

7. Vue JS

Vue JS is more a framework than a library. But since it can be integrated with any project whether it is in core PHP, Laravel, WordPress, or Node JS, it can be called a library. You can learn all functions of it from their official guide. You can check our following projects developed in Vue JS:

  1. Picture Competition Web App – Vue JS, Node JS, Mongo DB
  2. Firebase Storage – Vue JS
  3. Financial Ledger Web App – Vue JS, Node JS, Mongo DB

You will learn a lot about Vue JS from the above projects.

8. Notify JS

This lightweight library is used to display notification pop-ups on the corners of the browser window. This library also requires jQuery to be included before using it. Its usage is extremely easy, you simply need to call the following function when you want to show the notification:

$.notify("New message");

By default, it displays a pop-up on the top right, but you can decide the position of it. You can learn more about this library from here.

9. Chart JS

As the name suggests, this library is used to draw charts and graphs on your website. You can show statistics to the users using this library. It uses canvas to render charts. The following code will create a simple bar chart:

<canvas id="myChart" width="400" height="400"></canvas>
<script>
var ctx = document.getElementById('myChart').getContext('2d');
var myChart = new Chart(ctx, {
    type: 'bar',
    data: {
        labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
        datasets: [{
            label: '# of Votes',
            data: [12, 19, 3, 5, 2, 3],
            backgroundColor: [
                'rgba(255, 99, 132, 0.2)',
                'rgba(54, 162, 235, 0.2)',
                'rgba(255, 206, 86, 0.2)',
                'rgba(75, 192, 192, 0.2)',
                'rgba(153, 102, 255, 0.2)',
                'rgba(255, 159, 64, 0.2)'
            ],
            borderColor: [
                'rgba(255, 99, 132, 1)',
                'rgba(54, 162, 235, 1)',
                'rgba(255, 206, 86, 1)',
                'rgba(75, 192, 192, 1)',
                'rgba(153, 102, 255, 1)',
                'rgba(255, 159, 64, 1)'
            ],
            borderWidth: 1
        }]
    },
    options: {
        scales: {
            y: {
                beginAtZero: true
            }
        }
    }
});
</script>

There are many graphs & charts provided by this library. You can learn the implementation of all chars from their official page.

10. TimeCircles

This is used to create a countdown timer. You can set the date and the timer will start counting down till that time. This also requires jQuery on your website. Its usage is simple, you just need to give a data-date attribute to the div tag where you want to show the countdown timer. data-date attribute’s value will be the date and time when the timer hits zero. Give it a unique ID so it can be accessible in Javascript.

<div data-date="2021-10-12 23:02:55" id="count-down"></div>

Then in your Javascript, initialize the countdown timer with the following function:

$("#count-down").TimeCircles();

Learn How to Create a Dynamic Countdown Timer. Download the library from here.

What’s Next

You should learn all the libraries above and create small programs in them. But then you need to create a web application where you should apply all the above libraries together. We are going to give you a simple task that helps you learn how to combine all libraries together to create great web applications.

Assignment

Create a simple website that inputs date using DateTimePicker, description using RichText, an input field to upload PDF files and a submit button. When the submit button is pressed, it should show a message using Sweetalert that the message has been sent. It should send an event to the server using Socket IO and the other user should receive that event and display the description in a table using DataTable.

Another user should also see a notification using NotifyJS that a new event has been received. You need to show a countdown timer using TimeCircles till the date field value ends. You should also display the uploaded PDF file in a div tag using the PDFObject library.

Complete assignment’s frontend should be done in Vue JS. If you face any problem in doing this assignment, kindly do let us know in the comments section below.

Picture Competition Web App – Node JS, Mongo DB, Vue JS

Picture Competition is a mobile responsive real-time web application developed in Node JS and Mongo DB. Its frontend is designed in Bootstrap and Vue JS. You can create competition between 2 people and the others can vote on the person based on looks or skills etc.

Demo:

https://github.com/adnanafzal565/picture-competition-web-app-node-js

FeaturesFreePremium $100
Login and registrationYesYes
Create competitionsYesYes
Search & sortYesYes
Vote on CompetitionYesYes
Delete CompetitionYesYes
Reset PasswordNoYes
Email VerificationNoYes
Adult Image ValidationNoYes
SMTP Configurations Admin PanelNoYes
Real-time VotesNoYes
Real-time Admin Panel StatisticsNoYes
Real-time CommentsNoYes
User ProfileNoYes
Realtime update via socketsNoYes
NotificationsNoYes
Load more buttonNoYes
Admin panelNoYes
Manage competitionsNoYes
Free customer supportNoYes

Mongo DB Backend

competition’s collection

1. Login and Registration

It uses login authentication using JWT (JsonWebToken). It does not use the browser session due to the following reasons:

  1. Sessions are destroyed once your app is restarted from terminal.
  2. Sessions are not linked with the server, they are stored in browser only.

The benefit of using JWT is that you can always log out a user from your Mongo DB. Just go to the “users” collection and find the specific user. Then empty the “accessToken” field of that user’s document.

Login and Registration

2. Create Competitions

Registered users can create competitions between 2 users. You can enter name and upload 1 picture of each competitor. There is no limit in the number of competitions to create, you can create as many as you want. You can view your created competitions on your “My Competitions” page.

As soon as you create a competition, it will automatically be displayed to all the users as well as to the admin in real-time. Users do not have to refresh the page to see new competitions. It uses Socket IO to display data in real-time.

Create Competition

3. Search

Users can search competitions by the name of competitors. Data is filtered and rendered using Vue JS. There is no need to press the enter key, the data is filtered as soon as the user starts typing in the search box.

Search and Sort

4. Sort

Users can also sort the competitions by any of the following for sorting functions:

  1. Highest votes to lowest.
  2. Lowest votes to highest.
  3. Oldest to newest.
  4. Newest to oldest (default).

5. Vote on Competition

Logged-in users can vote on the competition. You can either vote on one of the competitors. Once the vote is cast on one competitor, it cannot be removed. Also, you can cast your vote on only one of the competitors, not on both of them. It is also real-time, as soon as the vote is cast, it will automatically be displayed to all the users and the counter is incremented. The counter displays the number of votes cast on each competitor.

Votes

6. Delete Competition

Competitions can only be deleted by either of the users who created the competition, or by the admin. Once the competition is deleted, all the uploaded images will be deleted too. As soon as the competition is deleted, it will automatically be removed from all the other users too, without having them refresh the page.

7. Realtime Update using Sockets

Sockets are used for real-time communication. Instead of fetching the data from the server after regular intervals, sockets attach listeners to the client-side. Listeners are listening to the events sent from the server. The server will emit the event and the client will listen to that event and respond accordingly. In this project, sockets are used for the following features:

  1. When competition is created.
  2. When competition is deleted.
  3. To increase the counter after vote is casted to the competition.
  4. Notifications.

8. Notifications

When a competition is deleted by the admin, the admin will write the reason for the deletion. Thus, a notification will be sent to the user along with the reason why his competition was removed. By default, notification status is “unread” and they are highlighted. As soon as the user clicks on any of the notifications, that notification will be marked as “read” and it will no longer be highlighted.

Notifications

9. Load More Button

When the data in the database increases, it is not feasible to load all the data in a single AJAX request. So a “load more” button is created to solve this problem. For example, 10 records are fetched in the first AJAX request. The next 10 records will be fetched when the “load more” button is clicked, and so on.

Load More

10. Admin Panel

Admin panel is created so you (administrator) can delete any competition you find offensive. The default email and password of admin are:

email = admin@gmail.com
password = admin

11. Manage Competitions

Admin can delete competitions that he finds offensive. However, the admin must give the reason why that competition is deleted. A notification will be sent to the user who created that competition and he will be able to view it from the top navigation bar.

12. Reset Password

Now you will be able to reset your password if you ever forgot. You just need to enter your email address and an email will be sent to you with a link to reset the password. We are using the nodemailer module to send an email.

Forgot Password
Reset Password

13. Email Verification

When a new user registers, we are sending a verification email to the user’s entered email address. The user will not be able to log in until he verifies his email address. When a user clicks the link on his email address, he will receive a message that says that his account is verified. Then he will be able to log in successfully.

Email Verification

14. SMTP Configurations from Admin Panel

To send an email, you will need an SMTP server. Every SMTP server requires some configurations to set up that include, host, port, email, and password. You can write these values directly hardcoded in your code, but to update these values in the future, you have to find these values in the code and update them.

In this project, you can set these configurations directly from the admin panel. Once the values are set, new emails will be sent using the new configurations.

SMTP configurations from admin panel

15. Adult Image Validation

This is a must-have feature if you are creating a website that allows users to upload pictures and they will be seen to the world. Anyone can upload an image that contains adult content, and it will not be good for your business. So when the user is uploading pictures while creating competition, the system will automatically check if the image is safe to upload.

If the image is an adult image, then an error will be shown to the user and it will not be uploaded.

16. Admin Panel Stats

Admin can see total users, total competitions, and total votes cast so far. They are also real-time, so when a new user is registered, or new competition is created, or event a new vote is cast, it will automatically be incremented here.

Also, when competition is deleted, the number will automatically get decremented as well, without having the admin refresh the page.

Admin Panel Stats

17. Real-time Comments

Users can comment on each competition. And they are also real-time as well. Once a new comment is added, it will immediately be displayed to all the other users as well. They do not have to refresh the page to see new comments.

Real-time Comments

18. User Profile

Users can now update their names and profile pictures. We are using the fs (file system) module to upload the picture. User can also add their bio, date of birth, country, and social media links. Media links include Facebook, Instagram, google plus, Twitter, and LinkedIn.

User can also change their account password. In order to change the password, the user must enter the current password. The new password should be entered twice for confirmation.

User Profile

19. Free Customer Support

This is not a feature of the project, but it is a free service provided for the pro version only. That means if you find any difficulty in installing or configuring the project, we will help you install it. Also, if you encounter any error or a bug in the released version, then it can be fixed too.

These are all the features we have right now in the picture competition web app. We are open to more ideas. If you have more ideas to add, kindly do let us know.

Share a Draft, Getting Only 5 Posts [FIXED] – WordPress

In this tutorial, we are going to teach you, how you can display all posts in your Share a Draft plugin in WordPress. This plugin is used to share your unpublished posts using a link. In WordPress, by default only published posts are shared. You can download the plugin manually from here.

Video tutorial:

You just need to follow the steps below:

  1. From your WordPress admin panel, go to “Plugins > Plugin Editor”.
  2. Select plugin to edit “Share a Draft”.
  3. Select plugin file “shareadraft.php”.
  4. Search for 2 variables; “$my_unpublished” and “$others_unpublished”.
  5. Add the “nopaging” value in the “get_posts” function of both of these arrays.
$my_unpublished = get_posts( array(
	'post_status' => $unpublished_statuses,
	'author' => $current_user->ID,
	// some environments, like WordPress.com hook on those filters
	// for an extra caching layer
	'suppress_filters' => false,
	"nopaging" => true
) );
$others_unpublished = get_posts( array(
	'post_status' => $unpublished_statuses,
	'author' => -$current_user->ID,
	'suppress_filters' => false,
	'perm' => 'editable',
	"nopaging" => true
) );
  1. Click the “Update File” button and refresh your plugin page.

Then you will be able to view all of your drafted posts.

Learn how to fix your WP Carousel Slider plugin height issue following this tutorial.