TIE Books Manager
TIE Books Manager - WordPress plugin: Books CPT with login-restricted viewing and AJAX-filterable [books_list] shortcode
by Sarojinee Tarale · github.com/sarojinee/tie-books-manager · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/sarojinee/tie-books-manager/archive/refs/heads/main.zipReadme
TIE Books Manager
A WordPress plugin that adds a Books custom post type and displays a collection of books that is restricted to logged-in users only. Built as a developer assignment with a focus on functionality, WordPress coding standards, and proper use of hooks, filters, and APIs.
Features
- Custom Post Type Books with custom fields:
- Title (default WordPress field)
- Author (text)
- Genre (dropdown: Fiction, Non-Fiction, Sci-Fi, Biography, Fantasy)
- Published Date (date picker)
- Description (WYSIWYG editor via
wp_editor)
- Access restriction so only logged-in users can view single book pages and the books listing.
- Custom single-book template showing title, author, genre, published date, and description.
[books_list]shortcode that lists all books with title (linked), author, and genre.- Pagination — 5 books per page.
- Bonus implemented: AJAX filtering (no page reload) by Genre and Author.
- Input sanitization, output escaping, and nonce verification throughout.
- Responsive CSS for desktop and mobile.
Requirements
- WordPress 5.8 or higher (uses
wp_editor, block-ready CPT registration). - PHP 7.4 or higher.
Installation & Setup
- Download or clone this repository.
- Copy the
tie-books-managerfolder into your WordPress installation under:wp-content/plugins/tie-books-manager - In the WordPress admin, go to Plugins and Activate "TIE Books Manager".
- On activation the plugin registers the CPT and flushes rewrite rules automatically, so single-book permalinks work immediately.
- (If single book URLs ever return 404) go to Settings → Permalinks and click Save Changes once to refresh rewrite rules.
Usage
Adding Books
- In the admin sidebar, open Books → Add New.
- Enter the Title, then fill in the Book Details meta box: Author, Genre, Published Date, and Description.
- Publish.
Displaying the Books List
- Create a new Page (e.g. "Library").
- Add the shortcode into the content:
[books_list] - Publish and view the page.
- Logged-in users see the filterable, paginated list.
- Logged-out users see the restricted-access message.
Viewing a Single Book
Click a book title in the list (or visit /books/your-book-slug/). The custom single template renders the full details.
Testing Checklist
- Logged-in user
- Visit the page containing
[books_list]→ list of books appears, 5 per page. - Use the Filter By Genre dropdown → list updates via AJAX without reloading.
- Type in Filter By Author → list updates via AJAX (debounced).
- Click a pagination link → next 5 books load via AJAX.
- Click a book title → single book page shows all fields.
- Visit the page containing
- Logged-out user (open an incognito window)
- Visit a single book URL → restricted message is shown.
- Visit the books archive (
/books/) → restricted message is shown. - Visit the
[books_list]page → restricted message is shown instead of the list.
How Access Restriction Was Implemented
Access control is enforced in three complementary layers, so there is no single point of bypass:
-
Front-end pages —
template_redirecthook (includes/access-control.php) On every request, the plugin checksis_singular( 'books' )andis_post_type_archive( 'books' ). If the request targets a single book or the books archive andis_user_logged_in()returnsfalse, the request is halted withwp_die()showing:"You must be logged in to view this content. Please log in or register." Using
template_redirectstops execution before any book content is rendered, so restricted content is never sent to the browser. -
Shortcode output —
[books_list](includes/shortcode.php) The shortcode callback first checksis_user_logged_in(). If the visitor is not logged in, it returns the restricted-access message instead of rendering the list markup. This protects the listing even when the shortcode is embedded on an otherwise-public page. -
AJAX endpoint (
includes/ajax-filter.php) The AJAX handler verifies a nonce withcheck_ajax_referer()and then re-checksis_user_logged_in(), returningwp_send_json_error()for guests. Thewp_ajax_nopriv_*hook is intentionally not registered, so logged-out visitors cannot pull book data directly fromadmin-ajax.php.
Security Notes
- All meta input is sanitized:
sanitize_text_field()for text/date/genre,wp_kses_post()for the rich-text description. - All output is escaped:
esc_html(),esc_attr(),esc_url(),wp_kses_post(). - Meta saving is guarded by a nonce (
tie_books_save_meta), an autosave check, and acurrent_user_can( 'edit_post', $post_id )capability check. - The AJAX request is protected by a nonce created with
wp_create_nonce()and passed viawp_localize_script().
File Structure
tie-books-manager/
├── tie-books-manager.php # Main plugin file: constants, includes, asset enqueue, activation hooks
├── README.md
├── assets/
│ ├── css/books.css # Responsive styles
│ └── js/books.js # AJAX filtering & pagination
├── includes/
│ ├── cpt.php # Registers the Books custom post type
│ ├── metaboxes.php # Book Details meta box + save logic
│ ├── access-control.php # template_redirect login restriction
│ ├── shortcode.php # [books_list] shortcode + filter UI
│ ├── ajax-filter.php # AJAX handler for genre/author filtering & pagination
│ └── template-loader.php # Loads the custom single-book template
└── templates/
└── single-book.php # Single book front-end template
Notes on Design Choices
- The book Description is stored as post meta (rendered with
wp_editorin admin andwp_kses_poston the front end) rather than the default content editor, keeping all book fields grouped in one meta box. The CPT therefore only declarestitlesupport. - Pagination uses a hash-based base (
#page=%#%) so that page links are handled entirely client-side by AJAX, avoiding full page reloads. - Author filtering uses a
LIKEcomparison for partial-match convenience; genre uses an exact=match against the predefined values.
Author
Sarojinee Tarale