news post


Now the category part is done let's go to the news table with a lot of columns. In this table, we created a column named slug that'll store the strings from title column after removing html special characters and blank spaces while multiple words would be concatenated by - symbol as you generally see in all website urls. This value will be sent to the url as get request to pages.

CREATE TABLE news(
id integer unsigned PRIMARY KEY AUTO_INCREMENT,    
category varchar(255) not null,    
title varchar(100) not null,
slug varchar(100) not null,    
image varchar(100),
details text,
meta_key text,
brief text,
`date` datetime,
status tinyint,
visit int,
created_at timestamp,
posted_by varchar(255)
)	

NEWS FORM

Let's create a form to insert news then.

<?php 
$cat = new Category();
$categories = $cat->getCategory();

if (Request::method() && token::check(Request::post('csrf_token'))){
	$obj = new News();	
	$obj -> addNews();
}
?>

<form method="POST" enctype="multipart/form-data">
	<div class="col-sm-12">
		<?= Validation::displayErrors('validationErrors'); ?>		
		<?= Message(); ?>
		<?=token::input();?>
	</div>	
	<div class="col-sm-6 form-group">
		<?php if(count($categories)): ?>
			<?php foreach($categories as $category): ?>
				<label for="<?=$category->id?>">
				<input type="checkbox" name="category[]" value="<?=$category->name?>" id="<?=$category->id?>"> <?=$category->name?>
				</label>
			<?php endforeach; ?>
		<?php else: ?>
			<option>No Category Found</option>
		<?php endif; ?>
	</div>
	<div class="col-sm-3 form-group">		
		<label>Title</label>
		<input type="text" name="title" id="newstitle" class="form-control">
	</div>
	<div class="col-sm-3 form-group">
		<label>Slug</label>
		<input type="text" name="slug" id="newsslug" class="form-control" readonly="">
	</div>
	<div class="col-sm-6 form-group">
		<label>News Date</label>
		<input type="date" name="date" class="form-control">
	</div>	
	<div class="col-sm-6 form-group">
		<label>Featured Image</label>
		<input type="file" name="image" class="btn btn-default">
	</div>
	<div class="col-sm-6 form-group">
		<p><label>Status</label></p>
		<div class=" btn-group" data-toggle="buttons">			
			<label class="btn btn-primary active">
			   <input type="radio" name="status" value="1" id="option1" autocomplete="off" checked> Publish
			</label>
			<label class="btn btn-primary">
			   <input type="radio" name="status" value="0" id="option2" autocomplete="off"> Draft
			</label>
		</div>
	</div>	
	<div class="col-sm-6 form-group">
		<p><label>Keywords</label></p>
		<input type="text" name="meta_key" class="btn btn-default" value="news, nepal" data-role="tagsinput" class="form-control">
	</div>

	<div class="col-sm-12 form-group">
		<p><label>Short Description</label></p>
		<textarea name="brief" class="form-control" rows="5"></textarea>
	</div>

	<div class="col-sm-12 form-group">
		<p><label>Full Description</label></p>
		<textarea name="details" class="form-control" rows="10"></textarea>
	</div>

	<div class="col-sm-12 form-group">
		<button type="submit" class="btn btn-success"><i class="fa fa-newspaper-o"></i> Add News</button>
	</div>
</form>

<script src="<?=HTTP.'Assets/ckeditor/ckeditor.js'?>"></script>
<script>
	CKEDITOR.replace('details', { "filebrowserBrowseUrl": "..\/editor\/ckfinder\/ckfinder.html", "filebrowserImageBrowseUrl": "..\/editor\/ckfinder\/ckfinder.html?type=Images", "filebrowserFlashBrowseUrl": "..\/editor\/ckfinder\/ckfinder.html?type=Flash", "filebrowserUploadUrl": "..\/editor\/ckfinder\/core\/connector\/php\/connector.php?command=QuickUpload&type=Files", "filebrowserImageUploadUrl": "..\/editor\/ckfinder\/core\/connector\/php\/connector.php?command=QuickUpload&type=Images", "filebrowserFlashUploadUrl": "..\/editor\/ckfinder\/core\/connector\/php\/connector.php?command=QuickUpload&type=Flash" });
	CKEDITOR.replace('brief', { "filebrowserBrowseUrl": "..\/editor\/ckfinder\/ckfinder.html", "filebrowserImageBrowseUrl": "..\/editor\/ckfinder\/ckfinder.html?type=Images", "filebrowserFlashBrowseUrl": "..\/editor\/ckfinder\/ckfinder.html?type=Flash", "filebrowserUploadUrl": "..\/editor\/ckfinder\/core\/connector\/php\/connector.php?command=QuickUpload&type=Files", "filebrowserImageUploadUrl": "..\/editor\/ckfinder\/core\/connector\/php\/connector.php?command=QuickUpload&type=Images", "filebrowserFlashUploadUrl": "..\/editor\/ckfinder\/core\/connector\/php\/connector.php?command=QuickUpload&type=Flash" });
</script>	

First of all, we'll instantiate the category class and call the getCategory() function to fetch all data from the categories table to insert along with news data.

Then, we'll instantiate the News controller class if the csrf token value from post request matches the one in session and call the function addNews() to validate and add data.

Then, all the form fields to add data to the database table. In the meta_key field we used tagsinput plugin to add multiple data.

Finally, we added the ckeditor.js file and the script to upload image through the editor.

Now, let's create the controller to handle all requests related to news table.

VALIDATE AND ADD NEWS

Here's the validation and database along with validation class instantiation part of the news controller file.

<?php
class news extends model
{
	protected $table = "news";
	protected $key = "id";
	protected $field = "*";
	private $_validation = null;

	protected $validationRules = [		
		'title' => [
			'required' => true,
			'unique' => 'news.title',
			'label' => 'Title',
		],
		'slug' => [
			'required' => true,
			'label' => 'Slug',
		],
		'date' => [
			'required' => true,
			'label' => 'Date',
		],
		'status' => [
			'required' => true,
			'label' => 'Status',
		],
		'brief' => [
			'required' => true,
			'label' => 'Short Description',
		],
		'details' => [
			'required' => true,
			'label' => 'Full Description',
		],
		'meta_key' => [
			'required' => true,
			'label' => 'Keywords',
		]		
	];

	public function __construct()
	{	
		parent::__construct();
		$this->_validation = new Validation();
	}
}		

Now, let's see the function to add news.

public function addnews()
{

	$data['title'] = Request::post('title');
	$data['slug'] = Request::post('slug');
	$data['date'] = Request::post('date');
	$data['status'] = Request::post('status');
	$data['details'] = Request::post('details');
	$data['brief'] = Request::post('brief');
	$data['meta_key'] = Request::post('meta_key');
	$data['posted_by'] = session::get('authenticated_username');

	$categoryIds = $_POST['category'];
	$categoryName = implode(',',$categoryIds);		
	$data['category'] = $categoryName;

	try{
		$this->_validation->validate($this->validationRules);
		if($this->_validation->isValid()){
			$configs['upload_size'] = 10000000;
			$configs['upload_ext'] = 'jpg|jpeg|png|gif';
			$configs['upload_path'] = ROOT.'admin/Upload/News/';
			Upload::initialize($configs);
			$filename = Upload::load($_FILES['image']);
			if($filename){
				$data['image'] = $filename;
				
				if($this->save($data)){
					session::put('success','Added News Post Successfully');
					return Redirect::to('admin/addNews/');
				}											
			}else{
				session::put('validationErrors',Upload::getErrors());
				return Redirect::to('admin/addNews/');
			}
		}else{
			session::put('validationErrors',$this->_validation->getErrors());
			return Redirect::to('admin/addNews/');
		}
	}catch(Exception $e){
		die($e->getMessage());
	}
}	

First, we added all the data sent from the post in array $data except the category ids that's stored in another variable and imploded to convert array value to string. The posted_by column will get the active user's name as it's value from session. The value in visit column is not supposed to be updated from here as it will get updated everytime the post is viewed in frontend.

Then, we validated those data. If the data get validated, we'll go through the image validation process else we'll display the data validation error and redirect the user back to the form page.

If the image validation becomes successful we'll upload the image to defined directory and add another key image with its name as value for that key and insert the array values from $data to the news table and redirect the user and display success message. If not, the user will be redirected back to the form page with upload errors from session displayed.

DISPLAY NEWS

Let's create a function to display the data from news table in display page and create that display page as well.

public function getNews(){
	return $this->get($id); 
}	

Same function that we created in the parent controller is called here. If any id is sent single data row will b returned and in case the id is not sent all result set from the table will be returned.

UPDATE NEWS STATUS

Let's make the news status updatable from the display page just like we did in user status before working on the display table.

public function updateNewsStatus(){
	
	$id = (int)Request::post('_id');
	if(empty($id)) return false;

	if(isset($_POST['disable'])){
		$data['status'] = 0;
		$message = 'User was disabled';
	}

	if(isset($_POST['enable'])){
		$data['status'] = 1;	
		$message = 'User was enabled';
	}

	if($this->save($data,$id)){
		session::put('success',$message);
		return true;
	}
}	

Now, let's have a look at the display page that's got the table to diplay news.

<?php 
$obj = new news();	
$news = $obj->getNews();
if(Request::method()){
	$obj->updateNewsStatus();
}
?>

<div class="col-sm-12">
	<?= Message(); ?>
	<table class="table table-bordered table-striped table-hover">
		<thead>
			<tr>
				<th width="">S.N.</th>
				<th width="">Title</th>
				<th width="">Status</th>
				<th width="">Image</th>
				<th width="">Category</th>
				<th width="">Views</th>
				<th width="">Author</th>
				<th width="">Action</th>
			</tr>
		</thead>
		<tbody>
			<?php
			if(empty($news)){ ?>
			<td colspan="6">No Data Found <a href="main.php?page=addNews">Add News</a></td>
			<?php }else{ ?>
			<?php foreach($news as $key => $post): ?>	
				<tr>
					<td><?= ++$key ?></td>
					<td><?= ucfirst($post->title)?></td>
					<td>
						<form method="post">
							<input type="hidden" name="_id" value="<?=$post->id?>">
							<?php if($post->status == 0): ?>
								<button type="submit" name="enable" class="btn btn-sm btn-primary">Publish</button>
							<?php else: ?>
								<button type="submit" name="disable" class="btn btn-sm btn-danger">Unpublish</button>	
							<?php endif; ?>
						</form>
					</td>
					<td><img src="<?=HTTP.'admin/Upload/News/'.$post->image?>" width="50"></td>
					<?php $categories = explode(',',$post->cats); ?>
					<td>
						<?php foreach($categories as $category): ?>
							<span class="label label-primary"><?=$category?></span>
						<?php endforeach; ?>	
						
					</td>
					<td><?=(int)$post->visit?></td>
					<td><?= $post->username ?></td>
					
					<td>
						<a href="main.php?page=editNews&nid=<?=$post->id?>" class="btn btn-sm btn-danger"><i class="fa fa-edit"></i>
						<a href="delete.php?nid=<?=$post->id?>" onclick="return confirm('Are you sure?')" class="btn btn-sm btn-danger" style="margin-left: 5px;"><i class="fa fa-trash"></i></a> 
					</td>
				</tr>
			<?php endforeach; ?>	
			<?php } ?>
		</tbody>
	</table>
</div>	

DELETE NEWS

Quite similar to the process of deleting user, here too, we need to delete the images uploaded with the news row while deleting them. Let's create functions for them then.


private function deleteAssociate($id)
{
	if(empty($id)) return false;
	$news = $this->getNews($id);
	$img_name = $news->image;
	$img_path = ROOT.'admin/Upload/News/'.$img_name;		

	return unlink($img_path);
}

public function deleteNews()
{
	$id = (int)Request::get('nid');
	if(empty($id)) return false;

	if ($this->deleteAssociate($id) && $this->delete($id)){
		session::put('success','News was deleted.');
		return Redirect::to('admin/displayNews/');
	}else{
		session::put('error','Unable to delete news');
		return Redirect::to('admin/displayNews/');
	}
}	

As we did earlier in user controller, we created the function to delete associated image while deleting the result set from database. Now, we need to update the delete.php file and add another clause to delete data.

if(!empty(Request::get('u_id'))){
	$user = new User();
	$user->deleteUser();
}elseif(!empty(Request::get('cid'))){
	$category = new category();
	$category->deleteCategory();		
}elseif(!empty(Request::get('nid'))){
	$news = new news();
	$news->deletenews();
}else{
	echo 'Id not set';
}	

UPDATE NEWS

Let's create the controller function and form to update news then.

public function updateNews(){

	$data['date'] = $date;
	$data['title'] = Request::post('title');
	$data['slug'] = Request::post('slug');		
	$data['status'] = Request::post('status');
	$data['details'] = Request::post('details');
	$data['brief'] = Request::post('brief');
	$data['meta_key'] = Request::post('meta_key');
	$data['posted_by'] = session::get('authenticated_username');

	$categoryIds = $_POST['category'];
	$categoryName = implode(',',$categoryIds);		
	$data['category'] = $categoryName;

	$id = (int)Request::post('id');

	try{
		$this->validationRules['title']['unique'] = "news.title.id.".$id;
		$this->_validation->validate($this->validationRules);
		
		if($this->_validation->isValid()){
			
			if(!empty($_FILES['image']['name'])){
				$configs['upload_size'] = 2000000;
				$configs['upload_ext'] = 'jpg|jpeg|png|gif';
				$configs['upload_path'] = ROOT.'admin/Upload/News/';
				Upload::initialize($configs);
				$filename = Upload::load($_FILES['image']);

				if($filename && $this->deleteAssociate($id)){
					$data['image'] = $filename;						
				}else{
					session::put('validationErrors',Upload::getErrors());
					return Redirect::to('admin/addNews/');
				}
			}										
					
			if($this->save($data,$id)){
				session::put('success','News Post updated successfully');
				return Redirect::to('admin/addNews/');
			}	

		}else{
			session::put('validationErrors',$this->_validation->getErrors());
			return Redirect::to('admin/addNews/');
		}		
	}catch(Exception $e){
		die($e->getMessage());
	}
}	

Identical to the addNews() function, the only differene is we pass the data row id along with the request data in order to update the result set. Let's have a look at the form then.

<?php 
$id = Request::get('nid');
$obj = new news();
$news = $obj->getNews($id);
$selectedCats = explode(',',$news->category);
$keywords = explode(',',$news->meta_key);

$cat = new Category();
$categories = $cat->getCategory();

if (Request::method() && token::check(Request::post('csrf_token'))){
	
	$obj -> updateNews();
}
?>

<form method="POST" enctype="multipart/form-data">
	<div class="col-sm-12">
		<?= Validation::displayErrors('validationErrors'); ?>		
		<?= Message(); ?>
		<?=token::input();?>
		<input type="hidden" name="id" value="<?=$id?>">
	</div>		
	<div class="col-sm-6 form-group">
		<label for="categories">Category</label>
		<div class="form-group">
			<?php if(count($categories)): ?>
				<?php foreach($categories as $category): ?>
					<label for="<?=$category->id?>">
						<input type="checkbox" <?= (in_array($category->name,$selectedCats)) ? 'checked=""' : '' ; ?> name="category[]" value="<?=$category->name?>" id="<?=$category->id?>"> <?=$category->name?>
					</label>					
				<?php endforeach; ?>
				<?php else: ?>
					<option>No Category Found</option>
			<?php endif; ?>
		</div>	
	</div>
	<div class="col-sm-3 form-group">		
		<label>Title</label>
		<input type="text" name="title" value="<?=$news->title?>" id="newstitle" class="form-control">
	</div>
	<div class="col-sm-3 form-group">
		<label>Slug</label>
		<input type="text" name="slug" id="newsslug" value="<?=$news->slug?>" class="form-control" readonly="">
	</div>	
	<div class="col-sm-4 form-group">
		<label>News Date</label>
		<input type="date" name="date" value="<?=$news->date?>" class="form-control">
	</div>	
	<div class="col-sm-2 form-group">
		<p><label>Status</label></p>
		<div class=" btn-group" data-toggle="buttons">	
			<label class="btn btn-primary <?= ($news->status == 1) ? 'active' : '' ; ?>">
			   <input type="radio" name="status" value="1" id="option1" autocomplete="off"> Publish
			</label>
			<label class="btn btn-primary <?= ($news->status == 0) ? 'active' : '' ; ?>">
			   <input type="radio" name="status" value="0" id="option2" autocomplete="off"> Draft
			</label>
		</div>
	</div>
	<div class="col-sm-6 form-group">
		<label>Featured Image</label>
		<input type="file" name="image" class="btn btn-default">		
	</div>
	<div class="col-sm-6 form-group">
		<p><label>Keywords</label></p>
		<input type="text" name="meta_key" class="btn btn-default" value="<?php foreach ($keywords as $keyword) : ?> <?=$keyword.','?> <?php endforeach; ?>" data-role="tagsinput" class="form-control">
	</div>
	<div class="col-sm-6 form-group">
		<p><img src="<?=HTTP.'admin/Upload/News/' . $news->image?>" width="100"></p>
	</div>
	<div class="col-sm-12 form-group">
		<p><label>Short Description</label></p>
		<textarea name="brief" class="form-control" rows="5">
			<?=$news->brief?>
		</textarea>
	</div>
	<div class="col-sm-12 form-group">
		<p><label>Full Description</label></p>
		<textarea name="details" class="form-control" rows="10">
			<?=$news->details?>
		</textarea>
	</div>
	<div class="col-sm-12 form-group">
		<button type="submit" class="btn btn-success"><i class="fa fa-newspaper-o"></i> Update News</button>
	</div>
</form>

We exploded the categories stored in table column and stored it as array. Then, we used the ternary operator in category form field checkboxes to get them selected if the value being sent form that input field matches the one we've got in our data row.

We added toggle buttons to update news status from display page as we did for user status earlier.

Similar to the category situation, we have string value stored in the database column meta-key, we'll explode them on the basis of , symbol and store them in an array and display all of them in each field using foreach loop.

PAGINATION CLASS

Pagination is very useful in cases where we might have a lot of rows of contents like the one here in news table. Viewing the table contents and searching for the one we want to delete or update or change status may be a lot more ardous. Let's create a pagination model and then add it in the news display page then.

<?php 
class Pagination
{
	private static $_limit, $_total_rows, $_page; 	

	public static function initialize($configs){
		self::$_limit = $configs['limit'];
		self::$_total_rows = $configs['total_rows'];

		self::$_page = Request::get('p') !== null ? Request::get('p') : 1; 
		
		return (self::$_page-1) * self::$_limit;
	}	
}	

In this function, we defined static null values from numeric value data limit and total data count and defined the page uri too.

initialize() will accept array values as parameter for the the first two variables.

We'll chek for the url pattern in url &p= from where we'll get the page value to display data. If no such pattern and value is found in th url, we'll assume it as &p=1 and process the request.

Finally, it's the arithmatic logic to calculate the offset value that we need to use in our SQL select query to display limited data.

Now, let's create a links to display available and active pages in the view file.

public static function links(){
	$pageCount = ceil(self::$_total_rows / self::$_limit); 
	$url = $_SERVER['REQUEST_URI'];
	$url = preg_replace('/&p=./','', $url);

	if($pageCount > 1){

		$output = "";
		
		$next = self::$_page+1;
		$pre = self::$_page-1;

		$output .= "<ul class='pagination'>";

		if(self::$_page > 1){
			$output .= "<li><a href='{$url}&p={$pre}'>«</a></li>";
		}else{
			$output .= "<li class='disabled'><a href='{$url}&p={$pre}'>«</a></li>";
		}

		for($i=1;$i<=$pageCount;$i++){
			if(self::$_page == $i){
				$output .= "<li class='active'><a>{$i}</a></li>";	
			}else{
				$output .= "<li><a href='{$url}&p={$i}'>{$i}</a></li>";	
			}
		}

		if(self::$_page < $pageCount){
			$output .= "<li><a href='{$url}&p={$next}'>»</a></li>";
		}else{
			$output .= "<li class='disabled'><a href='{$url}&p={$next}'>»</a></li>";
		}
		$output .= '</ul>';
		return $output;
	}
	return $output = '';
}	

We used ceil() function to round off a positive numeric value for cases where decimal values might be produced by the algorithm that retrieves the total number of pages with data.

We also retrieved the url value to concatenate our pagination links to it but we need to replace the earlier page values from url if present. Hence, we used the preg_replace() function that searches for the pattern defined between the / / symbol and replaces with the one defined in the second parameter. If the second parameter is empty, it'll remove those patterns from the url.

If page count is more than one then, we'll add the page values for the previous and next page links.

Then, we defined the case where the previous page link should be disabled.

We also displayed the page numbers as links between the previous and next page links and also added class active and removed the page link from the page value that's currently active using for loop along with if else statement.

Finally, the condition is defined where the next page link is disabled. And the HTML tag output is returned from the model.

PAGE UPDATES FOR PAGINATION

We need to go back to our controller page and modify the getNews() function to activate pagination because the page is displaying all data rows by default if the id is not sent through the parameter.

NEWS CONTROLLER
public function getNews($id=""){

	if(empty($id)){
		
		$this->limit = 20;

		$configs['limit'] = $this->limit;
		$configs['total_rows'] = $this->countRow();

		$offset = Pagination::initialize($configs);
		$this->offset = $offset;

		$this->pagination = Pagination::links();
	}
	return $this->get($id);

}

If the id is not set now, the limit value would be saved as 20 while the total row count will be retrieved form the SQL COUNT query, we prepared in model as count() function. Then the offset value will be calculated by the initialize() function we created in Pagination model earlier.

Then, the links() function is called that'll create pagination links in HTML format and return.

Now, we can go to the display page and call the pagination class where we'd like to place it, generally at the base of the table or after the display table is closed.

<?=$obj->pagination;?>