Skip to content

Commit 3282cbc

Browse files
committed
Changed namespace
0 parents  commit 3282cbc

12 files changed

Lines changed: 379 additions & 0 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
vendor

LICENSE

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Copyright (C) 2025 Tirtharaj Pati
2+
3+
This program is free software; you can redistribute it and/or
4+
modify it under the terms of the GNU General Public License
5+
as published by the Free Software Foundation; either version 2
6+
of the License, or (at your option) any later version.
7+
8+
This program is distributed in the hope that it will be useful,
9+
but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
GNU General Public License for more details.

Readme.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# 📬 Latest Post Widget
2+
3+
A lightweight, powerful, and customizable WordPress widget that displays the **latest posts from a specific category** — right in your sidebar or widget-ready area. Built with PSR-12 standards, modern PHP 8+ features, and WordPress best practices.
4+
5+
---
6+
7+
## 🚀 Features
8+
9+
- ✅ Display latest posts from a selected **WordPress category**
10+
- 🖼️ Customize the **widget title** and number of posts
11+
- 🔍 Clean, accessible markup with **clickable post links**
12+
- 🛠️ Built with **PHP OOP**, PSR-compliant structure, and autoloading
13+
- 🌐 Fully translatable & ready for localization
14+
- ⚠️ Handles input validation and errors using `WP_Error` and admin notices
15+
16+
---
17+
18+
## 🧩 How It Works
19+
20+
1. Activate the plugin from the **Plugins** menu.
21+
2. Go to **Appearance → Widgets** or the **Customizer**.
22+
3. Add the **"Latest Post Widget"** to any widget area.
23+
4. Choose:
24+
- A custom **title**
25+
- The **number of posts** to display
26+
- A **category** from your existing blog categories
27+
5. Save & enjoy a beautiful feed of the latest posts.
28+
29+
---
30+
31+
## 🛠️ Plugin Settings
32+
33+
| Setting | Description |
34+
|----------------|----------------------------------------------|
35+
| **Title** | Custom title to display above the post list |
36+
| **Category** | Choose a WordPress category from dropdown |
37+
| **Post Count** | Limit how many recent posts to show |

composer.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "truetirtha/latest-posts-widget",
3+
"description": "This is a simple WordPress plugin.",
4+
"type": "project",
5+
"license": "GPL v2 or later",
6+
"autoload": {
7+
"psr-4": {
8+
"LatestPostWidget\\Includes\\": "includes/"
9+
}
10+
},
11+
"authors": [
12+
{
13+
"name": "Truetirtha",
14+
"email": "patitirtharaj@gmail.com"
15+
}
16+
],
17+
"require": {}
18+
}

includes/Base/Activate.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
/**
3+
*
4+
*@package Paktolus Post Widget
5+
*/
6+
namespace Includes\Base;
7+
8+
class Activate
9+
{
10+
public static function activate()
11+
{
12+
flush_rewrite_rules();
13+
}
14+
}

includes/Base/Deactivate.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
/**
3+
*
4+
*@package Paktolus Post Widget
5+
*/
6+
namespace PaktolusPostWidget\Includes\Base;
7+
8+
class Deactivate
9+
{
10+
public static function deactivate()
11+
{
12+
13+
flush_rewrite_rules();
14+
}
15+
}

includes/Init.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace PaktolusPostWidget\Includes;
5+
6+
use PaktolusPostWidget\Includes\Api\PaktolusWidget;
7+
8+
defined('ABSPATH') || exit;
9+
10+
11+
class Init
12+
{
13+
public function __construct()
14+
{
15+
add_action('widgets_init', [$this, 'register_widget']);
16+
}
17+
18+
public function register_widget(): void
19+
{
20+
if (!class_exists(PaktolusWidget::class)) {
21+
error_log((new \WP_Error('widget_missing', 'Widget class not found'))->get_error_message());
22+
return;
23+
}
24+
register_widget(PaktolusWidget::class);
25+
}
26+
27+
private static function instantiate($classname)
28+
{
29+
return new $classname;
30+
}
31+
32+
public static function register_services()
33+
{
34+
return self::instantiate(Init::class);
35+
}
36+
}

includes/api/LatestPostsWidget.php

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Includes\Api;
5+
6+
use WP_Widget;
7+
use WP_Error;
8+
use WP_Query;
9+
10+
defined('ABSPATH') || exit;
11+
12+
class LatestPostsWidget extends WP_Widget
13+
{
14+
public function __construct()
15+
{
16+
parent::__construct(
17+
18+
// Base ID
19+
'latest-post-widget',
20+
21+
// Widget name to show in the UI
22+
__('Latest Post Widget', 'latestpostwidget'),
23+
24+
// Description
25+
['description' => __('Displays recent posts from a selected category.', 'latestpostwidget')]
26+
);
27+
}
28+
29+
/**
30+
* Front-end display of widget.
31+
*
32+
* @see WP_Widget::widget()
33+
*
34+
* @param array $args Widget arguments.
35+
* @param array $instance Saved values from database.
36+
*/
37+
public function widget($args, $instance)
38+
{
39+
// extract($args);
40+
echo $args['before_widget'];
41+
42+
$defaultTitle = __('Latest Posts from Category');
43+
$title = !empty($instance['title']) ? $instance['title'] : $defaultTitle;
44+
45+
$title = apply_filters('widget_title', $title, $instance);
46+
$number = (int) ($instance['number'] ?? 5);
47+
$category = $instance['category'] ?? '';
48+
49+
if (!empty($title)) {
50+
echo $args['before_title'] . esc_html($title) . $args['after_title'];
51+
}
52+
53+
if (empty($category)) {
54+
echo '<p>' . esc_html__('No category selected', 'paktoluspostwidget') . '</p>';
55+
echo $args['after_widget'];
56+
return;
57+
}
58+
59+
$query = new WP_Query(
60+
[
61+
'cat' => (int) $category,
62+
'posts_per_page' => $number,
63+
'post_status' => 'publish'
64+
]
65+
);
66+
67+
68+
if ($query->have_posts()):
69+
echo '<ul>';
70+
while ($query->have_posts()):
71+
$query->the_post();
72+
echo '<li><a href="' . esc_url(get_permalink()) . '">' . esc_html(get_the_title()) . ' </a></li>';
73+
endwhile;
74+
echo '</ul>';
75+
else:
76+
echo '<p>' . esc_html__('No posts found', 'paktoluspostwidget') . '</p>';
77+
endif;
78+
79+
80+
wp_reset_postdata();
81+
echo $args['after_widget'];
82+
}
83+
84+
/**
85+
* Back-end widget form.
86+
*
87+
* @see WP_Widget::form()
88+
*
89+
* @param array $instance Previously saved values from database.
90+
*/
91+
public function form($instance)
92+
{
93+
$title = esc_attr($instance['title'] ?? __('Latest Posts from Category', 'paktoluspostwidget'));
94+
$number = esc_attr($instance['number'] ?? 5);
95+
$category = $instance['category'] ?? '';
96+
97+
$categories = get_categories(['hide_empty' => false]);
98+
99+
?>
100+
<p>
101+
<label
102+
for="<?= esc_attr($this->get_field_id('title')); ?>"><?= esc_html__('Title:', 'paktoluspostwidget'); ?></label>
103+
<input class="widefat" id="<?= esc_attr($this->get_field_id('title')); ?>"
104+
name="<?= esc_attr($this->get_field_name('title')); ?>" type="text" value="<?= $title; ?>">
105+
</p>
106+
<p>
107+
<label
108+
for="<?= esc_attr($this->get_field_id('number')); ?>"><?= esc_html__('Number of posts to show:', 'paktoluspostwidget'); ?>
109+
</label>
110+
<input class="tiny-text" id="<?= esc_attr($this->get_field_id('number')); ?>"
111+
name="<?= esc_attr($this->get_field_name('number')); ?>" type="number" step="1" min="1" value="<?= $number; ?>"
112+
size="3">
113+
</p>
114+
<p>
115+
<label for="<?= esc_attr($this->get_field_id('category')); ?>"><?= esc_html__('Category:', 'paktoluspostwidget'); ?>
116+
</label>
117+
<select class="widefat" id="<?= esc_attr($this->get_field_id('category')); ?>"
118+
name="<?= esc_attr($this->get_field_name('category')); ?>">
119+
<option value=""><?= esc_html__('Select a Category', 'paktoluspostwidget'); ?></option>
120+
<?php foreach ($categories as $cat): ?>
121+
<option value="<?= esc_attr($cat->term_id); ?>" <?= selected((int) $category, $cat->term_id, false); ?>>
122+
<?= esc_html($cat->name); ?>
123+
</option>
124+
<?php endforeach; ?>
125+
</select>
126+
</p>
127+
<?php
128+
}
129+
/**
130+
* Sanitize widget form values as they are saved.
131+
*
132+
* @see WP_Widget::update()
133+
*
134+
* @param array $new_instance Values just sent to be saved.
135+
* @param array $old_instance Previously saved values from database.
136+
*
137+
* @return array Updated safe values to be saved.
138+
*/
139+
public function update($new_instance, $old_instance)
140+
{
141+
$instance = [];
142+
143+
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
144+
$instance['number'] = (int) ($new_instance['number'] ?? 5);
145+
$instance['category'] = (int) ($new_instance['category'] ?? 0);
146+
147+
if ($instance['number'] <= 0) {
148+
$instance['number'] = 5;
149+
}
150+
151+
return $instance;
152+
}
153+
}

includes/logger/ErrorLogger.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace PaktolusPostWidget\Includes\Logger;
4+
use WP_Error;
5+
class ErrorLogger extends WP_Error
6+
{
7+
public static function log($error)
8+
{
9+
if ($error instanceof WP_Error) {
10+
foreach ($error->get_error_message() as $msg) {
11+
error_log('[PaktolusPostWidget]' . $msg);
12+
}
13+
} else if (is_string($error)) {
14+
error_log('[PaktolusPostWidget]' . $error);
15+
}
16+
}
17+
18+
public static function admin_notice($error)
19+
{
20+
if ($error->has_errrors()) {
21+
set_transient('paktolus_widget_errors', $error->get_error_message(), 30);
22+
}
23+
}
24+
}

index.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?php
2+
// silence is golden

0 commit comments

Comments
 (0)