Initial commit of slide manager plugin
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
# Hi-School Slide Manager Frontend Dashboard Setup
|
||||
|
||||
## Quick Setup Guide
|
||||
|
||||
### 1. Create the Dashboard Page
|
||||
|
||||
1. Go to WordPress Admin → Pages → Add New
|
||||
2. Set the page title to: **Slide Dashboard**
|
||||
3. Set the page slug to: **slide-dashboard**
|
||||
4. In the Page Attributes section, select the template: **Slide Dashboard**
|
||||
5. Publish the page
|
||||
|
||||
### 2. Full-Width Dashboard Features
|
||||
|
||||
The dashboard now includes:
|
||||
|
||||
- **🎯 Custom Header Bar**: Professional navigation with Home, Admin, and Logout buttons
|
||||
- **📊 Status Bar**: Real-time statistics for active slides, connected sites, and last update
|
||||
- **📝 Enhanced Tabs**: Create Slides, Manage Slides, Preview Banner, and Analytics
|
||||
- **🔄 Bulk Actions**: Save all slides, save as drafts, import/export functionality
|
||||
- **📱 Responsive Design**: Works perfectly on all devices
|
||||
|
||||
### 3. Navigation Features
|
||||
|
||||
**Header Navigation:**
|
||||
- 🏠 **Home Button**: Quick return to site homepage
|
||||
- ⚙️ **Admin Button**: Access WordPress admin dashboard
|
||||
- 👤 **User Menu**: Shows current user and logout option
|
||||
|
||||
**Tab Navigation:**
|
||||
- ➕ **Create Slides**: Add up to 5 slide forms simultaneously
|
||||
- 📋 **Manage Slides**: View, edit, and delete existing slides
|
||||
- 👁️ **Preview Banner**: See how slides appear on companion sites
|
||||
- 📈 **Analytics**: View slide performance and statistics
|
||||
|
||||
### 4. Set Up Members Plugin (if not already configured)
|
||||
|
||||
1. Install and activate the Members plugin
|
||||
2. Go to Users → Roles → Add New Role
|
||||
3. Create a role called "Slide Manager" with these capabilities:
|
||||
- `read`
|
||||
- `edit_posts`
|
||||
- `upload_files`
|
||||
- `hssm_manage_slides` (custom capability)
|
||||
|
||||
### 5. Test the Dashboard
|
||||
|
||||
1. Create a test user with the "Slide Manager" role
|
||||
2. Navigate to `/slide-dashboard/` on your site
|
||||
3. You should see the full-width slide management interface
|
||||
|
||||
### 6. Dashboard Workflow
|
||||
|
||||
**Creating Slides:**
|
||||
1. Click "Add New Slide Form" (up to 5 forms at once)
|
||||
2. Fill in slide details: title, subtitle, content, client
|
||||
3. Upload background image with live preview
|
||||
4. Add up to 5 CTA buttons per slide
|
||||
5. Select target sites (Hi-School, One Stop, Ace)
|
||||
6. Set scheduling dates and auto-publish options
|
||||
7. Save individual slides or use "Save All Slides"
|
||||
|
||||
**Managing Slides:**
|
||||
1. Filter by site, client, or status
|
||||
2. Edit existing slides inline
|
||||
3. Delete slides with confirmation
|
||||
4. Export slide data for backup
|
||||
|
||||
**Preview System:**
|
||||
1. Select target site and preview date
|
||||
2. See real-time banner preview
|
||||
3. Email preview to stakeholders
|
||||
4. Download screenshots for approval
|
||||
|
||||
### 5. Template Override (Optional)
|
||||
|
||||
If you want to customize the dashboard template:
|
||||
|
||||
1. Copy `templates/page-slide-dashboard.php` to your active theme
|
||||
2. Customize as needed - the plugin will use the theme version
|
||||
|
||||
### 6. API Key Management
|
||||
|
||||
- Go to WordPress Admin → Slides → Settings
|
||||
- Manage API keys for companion sites
|
||||
- Test REST API endpoints
|
||||
|
||||
### 7. Companion Site Integration
|
||||
|
||||
Use these REST API endpoints on companion sites:
|
||||
|
||||
- `GET /wp-json/hssm/v1/slides/{site}` - Get slides for a specific site
|
||||
- `GET /wp-json/hssm/v1/sites` - Get available sites
|
||||
- `GET /wp-json/hssm/v1/slide/{id}` - Get specific slide
|
||||
- `GET /wp-json/hssm/v1/health` - Check API health
|
||||
|
||||
### 8. Divi 5 Group Carousel Integration
|
||||
|
||||
The slides are designed to work with Divi 5's Group Carousel module:
|
||||
|
||||
1. Add a Group Carousel module to your header
|
||||
2. Use the REST API to fetch slide data
|
||||
3. Populate carousel items with slide content
|
||||
4. Apply background images and CTA buttons
|
||||
|
||||
### 9. Troubleshooting
|
||||
|
||||
**Dashboard not loading?**
|
||||
- Check if user has proper permissions
|
||||
- Verify Members plugin is active
|
||||
- Check error logs for PHP errors
|
||||
|
||||
**AJAX not working?**
|
||||
- Verify nonce generation
|
||||
- Check user permissions
|
||||
- Enable WordPress debug mode
|
||||
|
||||
**Template not found?**
|
||||
- Ensure template file exists in plugin directory
|
||||
- Check file permissions
|
||||
- Verify page template is set correctly
|
||||
@@ -0,0 +1,26 @@
|
||||
# Notifications
|
||||
|
||||
The notifications Tab will have notification cofigurations with the following fields:
|
||||
|
||||
Notifications for "Editor": This will be the person approving the Slides. When the slides are created, this person will be sent a notification with a .
|
||||
Notifications for "ITCGuys" This notification will be sent when the slide is Approved for an I.T.C.G to review and test the date range to make sure slide is correctly set. When the slides go live, a notification will be sent to make sure slide is live and correctly set. After confirming the slide is live ther will be a link to a 'send "LIVE" confirmation' which will be sent to Editor and Creator of the slide, notifying them that it has been checked.
|
||||
|
||||
Notification Tab in the Slide Dashboard
|
||||
|
||||
**Email fields:**
|
||||
- Editor:
|
||||
- I.T.C.Guys:
|
||||
|
||||
The creator will be the Author of the slides.
|
||||
|
||||
Notification Triggers:
|
||||
- Notification to "Editor" when slide is sucsessfully scheduled or Published Immidiately
|
||||
- This notification will contain a generated link to the slide-preview page with params containing the slide-title, the publish-date that the slide is scheduled to be live, and the site slugs which when clicked, will display the slides that will be live on that date.
|
||||
- Notification when the slide has been approved sent to the creator (author).
|
||||
- Notification sent when slide goes live sent to ITCG reps who will double-check that it has been published, clear the cache if needed.
|
||||
- This notification will have a link to the site(s) the slide has been published on, and a link to a confirmation page (this page will run a script to generate and send the LAST notification to Editor and Author)
|
||||
- Final Notification sent to "Editor" and "Author": Generated by a script, will contain a link to the sites that have the slide, it will also have a link to the dashboard with the params in the link.
|
||||
|
||||
We need a page "Slide is Live" with text that reads "I confirm that {slide-title} is Live. with a green confirmation button that will the generate and send the Last email.
|
||||
|
||||
We need a tab on the Slide Dashoard for the Email fields to set for notifications.
|
||||
@@ -0,0 +1,110 @@
|
||||
# Hi-School Slide Manager
|
||||
|
||||
A WordPress plugin to manage slides for Hi-School websites.
|
||||
|
||||
## Features
|
||||
|
||||
- Custom post type for slides management
|
||||
- Easy-to-use admin interface
|
||||
- Shortcode support for displaying slides
|
||||
- Responsive design
|
||||
- Image upload support
|
||||
- Customizable slide display
|
||||
|
||||
## Installation
|
||||
|
||||
1. Upload the plugin files to the `/wp-content/plugins/hi-school-slide-manager/` directory
|
||||
2. Activate the plugin through the 'Plugins' screen in WordPress
|
||||
3. Use the 'Slides' menu in the WordPress admin to manage your slides
|
||||
|
||||
## Usage
|
||||
|
||||
### Admin Interface
|
||||
|
||||
1. Navigate to "Slides" in your WordPress admin menu
|
||||
2. Create new slides with titles, content, and images
|
||||
3. Manage existing slides through the standard WordPress interface
|
||||
|
||||
### Shortcode
|
||||
|
||||
Display slides on any page or post using the shortcode:
|
||||
|
||||
```
|
||||
[hssm_slides]
|
||||
```
|
||||
|
||||
Shortcode parameters:
|
||||
- `count` - Number of slides to display (default: 5)
|
||||
- `category` - Filter slides by category (optional)
|
||||
|
||||
Examples:
|
||||
```
|
||||
[hssm_slides count="3"]
|
||||
[hssm_slides count="10" category="featured"]
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
hi-school-slide-manager/
|
||||
├── hi-school-slide-manager.php # Main plugin file
|
||||
├── includes/
|
||||
│ ├── HSSM_Autoloader.php # Autoloader class
|
||||
│ └── HSSM_Plugin.php # Main plugin class
|
||||
├── assets/
|
||||
│ ├── css/
|
||||
│ │ ├── frontend.css # Frontend styles
|
||||
│ │ └── admin.css # Admin styles
|
||||
│ └── js/
|
||||
│ ├── frontend.js # Frontend JavaScript
|
||||
│ └── admin.js # Admin JavaScript
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Hooks and Filters
|
||||
|
||||
### Actions
|
||||
- `hssm_before_slide_display` - Fired before displaying a slide
|
||||
- `hssm_after_slide_display` - Fired after displaying a slide
|
||||
|
||||
### Filters
|
||||
- `hssm_slide_content` - Filter slide content before display
|
||||
- `hssm_shortcode_atts` - Filter shortcode attributes
|
||||
|
||||
## Customization
|
||||
|
||||
### CSS Classes
|
||||
|
||||
The plugin uses the following CSS classes for styling:
|
||||
- `.hssm-slides-container` - Main container for slides
|
||||
- `.hssm-slide` - Individual slide wrapper
|
||||
- `.hssm-slide-content` - Slide content area
|
||||
- `.hssm-slide-image` - Slide image container
|
||||
|
||||
### Custom Templates
|
||||
|
||||
You can override the slide display by creating custom templates in your theme:
|
||||
- `single-hssm_slide.php` - Single slide template
|
||||
- `archive-hssm_slide.php` - Slide archive template
|
||||
|
||||
## Requirements
|
||||
|
||||
- WordPress 5.0 or higher
|
||||
- PHP 7.4 or higher
|
||||
|
||||
## License
|
||||
|
||||
This plugin is licensed under the GPL v2 or later.
|
||||
|
||||
## Support
|
||||
|
||||
For support and questions, please contact the plugin developer.
|
||||
|
||||
## Changelog
|
||||
|
||||
### 1.0.0
|
||||
- Initial release
|
||||
- Basic slide management functionality
|
||||
- Shortcode support
|
||||
- Admin interface
|
||||
- Responsive design
|
||||
@@ -0,0 +1,409 @@
|
||||
/**
|
||||
* Hi-School Slide Manager - Admin Slides Dashboard Styles
|
||||
*/
|
||||
|
||||
/* General dashboard elements */
|
||||
.hssm-dashboard-main {
|
||||
background: #f0f2f5;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.hssm-tab-header h2 {
|
||||
color: #333;
|
||||
font-size: 20px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
/* Styles for the slide list within the 'Manage Slides' tab */
|
||||
.hssm-slides-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.hssm-slide-item {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.hssm-slide-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.hssm-slide-item .hssm-slide-info {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.hssm-slide-item .hssm-slide-title {
|
||||
font-size: 1.1em !important;
|
||||
font-weight: 600;
|
||||
color: #2c3338 !important;
|
||||
margin-bottom: 5px !important;
|
||||
line-height: 1.3 !important;
|
||||
}
|
||||
|
||||
.hssm-slide-item .hssm-slide-subtitle {
|
||||
font-size: 0.9em;
|
||||
color: #555d66;
|
||||
margin-bottom: 10px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.hssm-slide-item .hssm-slide-meta {
|
||||
font-size: 0.85em;
|
||||
color: #72777c;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.hssm-slide-item .hssm-status-badge {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8em;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.hssm-slide-item .hssm-status-pending {
|
||||
background-color: #ffba00;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.hssm-slide-item .hssm-status-publish {
|
||||
background-color: #46b450;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.hssm-slide-item .hssm-slide-thumbnail {
|
||||
width: 100%;
|
||||
padding-bottom: 56.25%; /* 16:9 aspect ratio */
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 4px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.hssm-slide-item .hssm-slide-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* Styles for the slide forms in the 'Create Slides' tab */
|
||||
.hssm-slide-form {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.hssm-form-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.hssm-form-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1.2em;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.hssm-remove-form {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #a00;
|
||||
cursor: pointer;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.hssm-form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.hssm-form-section h4 {
|
||||
font-size: 1em;
|
||||
color: #555d66;
|
||||
margin-top: 0;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 5px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.hssm-field-row {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.hssm-field-row .hssm-field {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.hssm-field label {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
margin-bottom: 5px;
|
||||
color: #333;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.hssm-input, .hssm-textarea, .hssm-select {
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9em;
|
||||
color: #333;
|
||||
background-color: #f9f9f9;
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.07);
|
||||
}
|
||||
|
||||
.hssm-input[type="date"] {
|
||||
padding: 7px 10px;
|
||||
}
|
||||
|
||||
.hssm-input:focus, .hssm-textarea:focus, .hssm-select:focus {
|
||||
border-color: #007cba;
|
||||
box-shadow: 0 0 0 1px #007cba;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.hssm-textarea {
|
||||
min-height: 80px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.hssm-image-preview-small {
|
||||
width: 100%;
|
||||
height: 120px; /* Smaller preview */
|
||||
background-color: #eee;
|
||||
border: 1px dashed #ccc;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
margin-bottom: 10px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hssm-image-preview-small .hssm-placeholder {
|
||||
color: #888;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.hssm-image-preview-small img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.hssm-image-controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.hssm-button-row-compact {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.hssm-button-row-compact .hssm-input-small {
|
||||
flex: 1;
|
||||
padding: 6px 8px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.hssm-btn-tiny {
|
||||
padding: 4px 8px;
|
||||
font-size: 0.8em;
|
||||
background-color: #dc3232;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hssm-btn-tiny:hover {
|
||||
background-color: #b30000;
|
||||
}
|
||||
|
||||
.hssm-checkboxes-compact label {
|
||||
font-size: 0.9em;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.hssm-checkbox-compact input[type="checkbox"] {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.hssm-form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
/* Override some general plugin button styles for admin context if needed */
|
||||
.hssm-btn-primary {
|
||||
background-color: #007cba;
|
||||
color: #fff;
|
||||
border-color: #007cba;
|
||||
}
|
||||
|
||||
.hssm-btn-primary:hover {
|
||||
background-color: #006799;
|
||||
border-color: #006799;
|
||||
}
|
||||
|
||||
.hssm-btn-secondary {
|
||||
background-color: #6c757d;
|
||||
color: #fff;
|
||||
border-color: #6c757d;
|
||||
}
|
||||
|
||||
.hssm-btn-secondary:hover {
|
||||
background-color: #5a6268;
|
||||
border-color: #545b62;
|
||||
}
|
||||
|
||||
.hssm-btn-success {
|
||||
background-color: #28a745;
|
||||
color: #fff;
|
||||
border-color: #28a745;
|
||||
}
|
||||
|
||||
.hssm-btn-success:hover {
|
||||
background-color: #218838;
|
||||
border-color: #1e7e34;
|
||||
}
|
||||
|
||||
.hssm-btn-outline {
|
||||
background-color: transparent;
|
||||
color: #6c757d;
|
||||
border: 1px solid #6c757d;
|
||||
}
|
||||
|
||||
.hssm-btn-outline:hover {
|
||||
background-color: #6c757d;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.hssm-btn-large {
|
||||
padding: 12px 25px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.hssm-btn-small {
|
||||
padding: 6px 12px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.hssm-btn .dashicons {
|
||||
vertical-align: middle;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/* Specific overrides for the slide preview elements that should look like dashboard items */
|
||||
/* This applies to the list *below* the main slider preview on the slide preview page */
|
||||
body.page-template-page-slide-preview .hssm-slides-list.hssm-mt-20 .hssm-slide-item {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
background: #fdfdfd;
|
||||
border: 1px solid #eee;
|
||||
color: #333; /* Ensure general text is legible */
|
||||
}
|
||||
|
||||
body.page-template-page-slide-preview .hssm-slides-list.hssm-mt-20 .hssm-slide-thumbnail {
|
||||
width: 120px;
|
||||
min-width: 120px;
|
||||
height: 70px; /* Adjusted for smaller, list-like thumbnail */
|
||||
padding-bottom: 0;
|
||||
border-radius: 4px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body.page-template-page-slide-preview .hssm-slides-list.hssm-mt-20 .hssm-slide-info {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
body.page-template-page-slide-preview .hssm-slides-list.hssm-mt-20 .hssm-slide-title {
|
||||
font-size: 1.1em !important;
|
||||
color: #23282d !important;
|
||||
margin-bottom: 3px !important;
|
||||
}
|
||||
|
||||
body.page-template-page-slide-preview .hssm-slides-list.hssm-mt-20 .hssm-slide-subtitle {
|
||||
font-size: 0.9em;
|
||||
color: #555d66;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
body.page-template-page-slide-preview .hssm-slides-list.hssm-mt-20 .hssm-slide-meta {
|
||||
font-size: 0.8em;
|
||||
color: #72777c;
|
||||
}
|
||||
|
||||
body.page-template-page-slide-preview .hssm-slides-list.hssm-mt-20 .hssm-slide-actions {
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
gap: 5px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
body.page-template-page-slide-preview .hssm-slides-list.hssm-mt-20 .hssm-slide-item .hssm-slide-title {
|
||||
background-color: #007cba;
|
||||
color: #ffffff;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
body.page-template-page-slide-preview .hssm-slides-list.hssm-mt-20 .hssm-slide-item .hssm-slide-subtitle,
|
||||
body.page-template-page-slide-preview .hssm-slides-list.hssm-mt-20 .hssm-slide-item .hssm-slide-meta {
|
||||
background-color: #444;
|
||||
color: #ffffff;
|
||||
padding: 3px 8px;
|
||||
border-radius: 3px;
|
||||
display: inline-block;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
body.page-template-page-slide-preview .hssm-slides-list.hssm-mt-20 .hssm-btn {
|
||||
padding: 5px 10px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Hi-School Slide Manager - Admin Styles
|
||||
*/
|
||||
|
||||
.hssm-admin-page {
|
||||
max-width: 1200px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.hssm-admin-header {
|
||||
background: #fff;
|
||||
border: 1px solid #ccd0d4;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.hssm-admin-header h1 {
|
||||
margin: 0;
|
||||
color: #23282d;
|
||||
}
|
||||
|
||||
.hssm-settings-form {
|
||||
background: #fff;
|
||||
border: 1px solid #ccd0d4;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.hssm-form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.hssm-form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: 600;
|
||||
color: #23282d;
|
||||
}
|
||||
|
||||
.hssm-form-group input,
|
||||
.hssm-form-group select,
|
||||
.hssm-form-group textarea {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.hssm-form-group textarea {
|
||||
min-height: 100px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.hssm-btn {
|
||||
background: #0073aa;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.hssm-btn:hover {
|
||||
background: #005a87;
|
||||
}
|
||||
|
||||
.hssm-btn:focus {
|
||||
outline: 2px solid #0073aa;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Hi-School Slide Manager - Frontend Styles
|
||||
*/
|
||||
|
||||
.hssm-slides-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.hssm-slide {
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
flex: 1;
|
||||
min-width: 300px;
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.hssm-slide:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.hssm-slide h3 {
|
||||
margin: 0 0 15px 0;
|
||||
color: #333;
|
||||
font-size: 1.4em;
|
||||
border-bottom: 2px solid #0073aa;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.hssm-slide-content {
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.6;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.hssm-slide-image {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hssm-slide-image img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.hssm-slides-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.hssm-slide {
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
/**
|
||||
* HSSM Slider Styles
|
||||
* Custom classes and styles based on client specifications
|
||||
* Optimized for Classic Divi environments
|
||||
*/
|
||||
|
||||
:root {
|
||||
--hssm-ratio: 42.85714%; /* 900 / 2100 * 100 */
|
||||
--hssm-red: #ff0000;
|
||||
--hssm-bg-overlay: rgba(0, 0, 0, 0.3);
|
||||
--hssm-shadow: 0.08em 0.08em 0px rgba(0, 0, 0, 0.6);
|
||||
|
||||
/* Default Sizes */
|
||||
--hssm-title-size: 32px;
|
||||
--hssm-subtitle-size: 20px;
|
||||
--hssm-content-size: 18px;
|
||||
--hssm-arrow-size: 50px;
|
||||
}
|
||||
|
||||
/* Force 80% width container */
|
||||
.hssm-slider-container {
|
||||
position: relative !important;
|
||||
width: 80% !important;
|
||||
max-width: 1980px !important;
|
||||
margin: 60px auto !important;
|
||||
box-sizing: border-box !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.hssm-slider {
|
||||
position: relative !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.hssm-viewport {
|
||||
position: relative !important;
|
||||
overflow: hidden !important;
|
||||
width: 100% !important;
|
||||
border-radius: 20px !important;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35) !important;
|
||||
background: #000 !important;
|
||||
}
|
||||
|
||||
.hssm-track {
|
||||
display: flex !important;
|
||||
transition: transform 0.6s cubic-bezier(0.45, 0.05, 0.55, 0.95) !important;
|
||||
width: 100% !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.hssm-slide {
|
||||
flex: 0 0 100% !important;
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
position: relative !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* Aspect Ratio Maintainer - Very Aggressive */
|
||||
.hssm-slide-inner {
|
||||
position: relative !important;
|
||||
width: 100% !important;
|
||||
height: 0 !important;
|
||||
padding-bottom: var(--hssm-ratio) !important;
|
||||
background-size: cover !important;
|
||||
background-position: center !important;
|
||||
background-repeat: no-repeat !important;
|
||||
overflow: hidden !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
/* Fallback for modern browsers */
|
||||
@supports (aspect-ratio: 21 / 9) {
|
||||
.hssm-slide-inner {
|
||||
padding-bottom: 0 !important;
|
||||
height: auto !important;
|
||||
aspect-ratio: 2100 / 900 !important;
|
||||
min-height: 300px !important; /* Safety min-height */
|
||||
}
|
||||
}
|
||||
|
||||
.hssm-slide-content-wrapper {
|
||||
position: absolute !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
bottom: 0 !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
justify-content: center !important;
|
||||
align-items: center !important;
|
||||
padding: 40px 5% !important;
|
||||
z-index: 5 !important;
|
||||
}
|
||||
|
||||
.hssm-slide-content {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
align-items: center !important;
|
||||
text-align: center !important;
|
||||
width: 100% !important;
|
||||
margin: 0 auto !important;
|
||||
}
|
||||
|
||||
/* Spacing between rows */
|
||||
.hssm-slide-content > * {
|
||||
margin-bottom: 30px !important;
|
||||
}
|
||||
|
||||
.hssm-slide-content > *:last-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* Title Styles - 80% width */
|
||||
.hssm-slide-title {
|
||||
font-size: var(--hssm-title-size) !important;
|
||||
color: #ffffff !important;
|
||||
font-weight: 600 !important;
|
||||
line-height: 1.3em !important;
|
||||
letter-spacing: 1px !important;
|
||||
max-width: 80% !important;
|
||||
margin: 0 0 30px 0 !important;
|
||||
}
|
||||
|
||||
.hssm-slide-title span {
|
||||
background: var(--hssm-bg-overlay) !important;
|
||||
padding: 5px 15px !important;
|
||||
border-radius: 8px !important;
|
||||
text-shadow: var(--hssm-shadow) !important;
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
/* Subtitle Styles - 80% width */
|
||||
.hssm-slide-subtitle {
|
||||
font-size: var(--hssm-subtitle-size) !important;
|
||||
color: #ffffff !important;
|
||||
font-weight: 600 !important;
|
||||
line-height: 1em !important;
|
||||
letter-spacing: 1px !important;
|
||||
max-width: 80% !important;
|
||||
margin: 0 0 30px 0 !important;
|
||||
}
|
||||
|
||||
.hssm-slide-subtitle span {
|
||||
background: var(--hssm-bg-overlay) !important;
|
||||
padding: 5px 12px !important;
|
||||
border-radius: 6px !important;
|
||||
text-shadow: var(--hssm-shadow) !important;
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
/* Description / Content Styles - 50% width */
|
||||
.hssm-slide-description {
|
||||
font-size: var(--hssm-content-size) !important;
|
||||
color: #ffffff !important;
|
||||
font-weight: 500 !important;
|
||||
line-height: 1.6em !important;
|
||||
letter-spacing: 1px !important;
|
||||
max-width: 50% !important;
|
||||
margin: 0 0 30px 0 !important;
|
||||
}
|
||||
|
||||
.hssm-slide-description span {
|
||||
background: var(--hssm-bg-overlay) !important;
|
||||
padding: 10px 20px !important;
|
||||
border-radius: 8px !important;
|
||||
text-shadow: var(--hssm-shadow) !important;
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
/* Button Container */
|
||||
.hssm-button-container {
|
||||
display: flex !important;
|
||||
flex-wrap: wrap !important;
|
||||
gap: 15px !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
.hssm-button {
|
||||
background: var(--hssm-red) !important;
|
||||
color: #ffffff !important;
|
||||
font-size: 16px !important;
|
||||
font-weight: 600 !important;
|
||||
padding: 12px 25px !important;
|
||||
border-radius: 8px !important;
|
||||
text-decoration: none !important;
|
||||
transition: transform 0.2s ease, background 0.2s ease !important;
|
||||
text-transform: uppercase !important;
|
||||
display: inline-block !important;
|
||||
border: none !important;
|
||||
box-shadow: 0 4px 10px rgba(0,0,0,0.2) !important;
|
||||
}
|
||||
|
||||
.hssm-button:hover {
|
||||
transform: scale(1.05) !important;
|
||||
background: #e60000 !important;
|
||||
}
|
||||
|
||||
/* Arrows */
|
||||
.hssm-arrow {
|
||||
position: absolute !important;
|
||||
top: 50% !important;
|
||||
transform: translateY(-50%) !important;
|
||||
width: var(--hssm-arrow-size) !important;
|
||||
height: var(--hssm-arrow-size) !important;
|
||||
background: var(--hssm-red) !important;
|
||||
border: none !important;
|
||||
border-radius: 50% !important;
|
||||
color: white !important;
|
||||
cursor: pointer !important;
|
||||
z-index: 10 !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4) !important;
|
||||
transition: all 0.3s ease !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.hssm-arrow:hover {
|
||||
background: #d00000 !important;
|
||||
transform: translateY(-50%) scale(1.1) !important;
|
||||
}
|
||||
|
||||
.hssm-arrow-prev { left: -25px !important; }
|
||||
.hssm-arrow-next { right: -25px !important; }
|
||||
|
||||
.hssm-arrow-icon {
|
||||
display: block !important;
|
||||
width: 14px !important;
|
||||
height: 14px !important;
|
||||
border-top: 4px solid white !important;
|
||||
border-right: 4px solid white !important;
|
||||
}
|
||||
|
||||
.hssm-arrow-prev .hssm-arrow-icon {
|
||||
transform: rotate(-135deg) !important;
|
||||
margin-left: 6px !important;
|
||||
}
|
||||
|
||||
.hssm-arrow-next .hssm-arrow-icon {
|
||||
transform: rotate(45deg) !important;
|
||||
margin-right: 4px !important;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
|
||||
/* X-Large Screen Breakpoint: 1921px and up */
|
||||
@media (min-width: 1921px) {
|
||||
:root {
|
||||
--hssm-title-size: 35.2px;
|
||||
--hssm-subtitle-size: 22px;
|
||||
--hssm-content-size: 19.8px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1400px) {
|
||||
.hssm-slider-container { width: 90% !important; }
|
||||
.hssm-slide-description { max-width: 70% !important; }
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
:root {
|
||||
--hssm-arrow-size: 44px;
|
||||
}
|
||||
.hssm-slider-container { width: 92% !important; margin: 40px auto !important; }
|
||||
.hssm-slide-description { max-width: 85% !important; }
|
||||
.hssm-arrow-prev { left: -22px !important; }
|
||||
.hssm-arrow-next { right: -22px !important; }
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.hssm-slider-container { width: 95% !important; margin: 30px auto !important; }
|
||||
.hssm-viewport { border-radius: 12px !important; }
|
||||
.hssm-slide-description { max-width: 95% !important; }
|
||||
.hssm-slide-title, .hssm-slide-subtitle { max-width: 95% !important; }
|
||||
.hssm-arrow { width: 40px !important; height: 40px !important; }
|
||||
.hssm-arrow-prev { left: -15px !important; }
|
||||
.hssm-arrow-next { right: -15px !important; }
|
||||
|
||||
:root {
|
||||
--hssm-title-size: 24px;
|
||||
--hssm-subtitle-size: 16px;
|
||||
--hssm-content-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.hssm-slider-container { width: 100% !important; border-radius: 0 !important; margin: 20px 0 !important; }
|
||||
.hssm-viewport { border-radius: 0 !important; }
|
||||
.hssm-arrow { width: 32px !important; height: 32px !important; }
|
||||
.hssm-arrow-prev { left: 5px !important; }
|
||||
.hssm-arrow-next { right: 5px !important; }
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* Hi-School Slide Manager - Admin Sortable functionality
|
||||
*
|
||||
* Handles drag-and-drop ordering of slides in the admin list table
|
||||
*/
|
||||
|
||||
jQuery(document).ready(function($) {
|
||||
'use strict';
|
||||
|
||||
// Only initialize on the slides list page
|
||||
if (typeof hssmAjax === 'undefined' || !$('.wp-list-table tbody').length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize sortable functionality
|
||||
initializeSortable();
|
||||
|
||||
/**
|
||||
* Initialize jQuery UI sortable on the slides table
|
||||
*/
|
||||
function initializeSortable() {
|
||||
const $tbody = $('.wp-list-table tbody');
|
||||
|
||||
// Make the table body sortable
|
||||
$tbody.sortable({
|
||||
handle: '.hssm-drag-handle',
|
||||
placeholder: 'ui-sortable-placeholder',
|
||||
helper: function(e, tr) {
|
||||
var $helper = tr.clone();
|
||||
$helper.children().each(function(index) {
|
||||
$(this).width(tr.children().eq(index).width());
|
||||
});
|
||||
return $helper;
|
||||
},
|
||||
start: function(e, ui) {
|
||||
ui.placeholder.height(ui.item.height());
|
||||
ui.item.addClass('ui-sortable-helper');
|
||||
},
|
||||
stop: function(e, ui) {
|
||||
ui.item.removeClass('ui-sortable-helper');
|
||||
updateSlideOrder();
|
||||
},
|
||||
tolerance: 'pointer',
|
||||
cursor: 'move',
|
||||
opacity: 0.8,
|
||||
distance: 5
|
||||
});
|
||||
|
||||
// Add visual feedback
|
||||
$tbody.on('mouseenter', '.hssm-drag-handle', function() {
|
||||
$(this).closest('tr').addClass('hssm-sortable-hover');
|
||||
});
|
||||
|
||||
$tbody.on('mouseleave', '.hssm-drag-handle', function() {
|
||||
$(this).closest('tr').removeClass('hssm-sortable-hover');
|
||||
});
|
||||
|
||||
// Add CSS for hover effect
|
||||
if (!$('#hssm-sortable-styles').length) {
|
||||
$('<style id="hssm-sortable-styles">')
|
||||
.text(`
|
||||
.wp-list-table tbody tr.hssm-sortable-hover {
|
||||
background-color: #f0f8ff;
|
||||
}
|
||||
.wp-list-table .hssm-drag-handle {
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
.wp-list-table .hssm-drag-handle:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.wp-list-table tbody tr.ui-sortable-helper td {
|
||||
background-color: #f9f9f9;
|
||||
border-color: #0073aa;
|
||||
}
|
||||
`)
|
||||
.appendTo('head');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update slide order via AJAX
|
||||
*/
|
||||
function updateSlideOrder() {
|
||||
const slideIds = [];
|
||||
|
||||
// Collect all slide IDs in their new order
|
||||
$('.wp-list-table tbody tr').each(function() {
|
||||
const postId = $(this).attr('id');
|
||||
if (postId && postId.startsWith('post-')) {
|
||||
const slideId = parseInt(postId.replace('post-', ''));
|
||||
if (slideId) {
|
||||
slideIds.push(slideId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (slideIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading indicator
|
||||
showLoadingIndicator();
|
||||
|
||||
// Send AJAX request to update order
|
||||
$.ajax({
|
||||
url: hssmAjax.ajaxurl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'hssm_update_slide_order',
|
||||
nonce: hssmAjax.nonce,
|
||||
slide_ids: slideIds
|
||||
},
|
||||
success: function(response) {
|
||||
hideLoadingIndicator();
|
||||
|
||||
if (response.success) {
|
||||
showNotification('Slide order updated successfully!', 'success');
|
||||
|
||||
// Update the order column values
|
||||
updateOrderColumnValues(slideIds);
|
||||
} else {
|
||||
showNotification('Failed to update slide order: ' + (response.data?.message || 'Unknown error'), 'error');
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
hideLoadingIndicator();
|
||||
showNotification('Error updating slide order: ' + error, 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the order column values in the table
|
||||
*/
|
||||
function updateOrderColumnValues(slideIds) {
|
||||
slideIds.forEach(function(slideId, index) {
|
||||
const $row = $('#post-' + slideId);
|
||||
const $orderCell = $row.find('.column-order');
|
||||
if ($orderCell.length) {
|
||||
$orderCell.text(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show loading indicator
|
||||
*/
|
||||
function showLoadingIndicator() {
|
||||
if ($('#hssm-loading-indicator').length === 0) {
|
||||
$('<div id="hssm-loading-indicator" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.8); color: white; padding: 20px; border-radius: 5px; z-index: 9999; font-size: 16px;">')
|
||||
.html('<span class="dashicons dashicons-update" style="animation: rotation 1s infinite linear; margin-right: 10px;"></span> Updating slide order...')
|
||||
.appendTo('body');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide loading indicator
|
||||
*/
|
||||
function hideLoadingIndicator() {
|
||||
$('#hssm-loading-indicator').remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show notification message
|
||||
*/
|
||||
function showNotification(message, type) {
|
||||
const $notice = $('<div class="notice notice-' + type + ' is-dismissible hssm-notice">')
|
||||
.html('<p>' + message + '</p>')
|
||||
.hide();
|
||||
|
||||
// Insert after the page title
|
||||
$('.wrap h1').first().after($notice);
|
||||
|
||||
// Animate in
|
||||
$notice.slideDown(300);
|
||||
|
||||
// Add dismiss functionality
|
||||
$notice.on('click', '.notice-dismiss', function() {
|
||||
$notice.slideUp(300, function() {
|
||||
$(this).remove();
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-dismiss success messages after 3 seconds
|
||||
if (type === 'success') {
|
||||
setTimeout(function() {
|
||||
$notice.find('.notice-dismiss').click();
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
// Add rotation animation for loading spinner
|
||||
if (!$('#hssm-animation-styles').length) {
|
||||
$('<style id="hssm-animation-styles">')
|
||||
.text(`
|
||||
@keyframes rotation {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(359deg); }
|
||||
}
|
||||
.hssm-notice {
|
||||
margin: 10px 0;
|
||||
}
|
||||
`)
|
||||
.appendTo('head');
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* Hi-School Slide Manager - Admin JavaScript
|
||||
*/
|
||||
|
||||
jQuery(document).ready(function($) {
|
||||
'use strict';
|
||||
|
||||
// Initialize admin functionality
|
||||
initializeAdmin();
|
||||
|
||||
/**
|
||||
* Initialize admin functionality
|
||||
*/
|
||||
function initializeAdmin() {
|
||||
// Handle form submissions
|
||||
$('.hssm-settings-form').on('submit', handleFormSubmission);
|
||||
|
||||
// Handle media upload buttons
|
||||
$('.hssm-upload-button').on('click', handleMediaUpload);
|
||||
|
||||
// Handle slide preview
|
||||
$('.hssm-preview-button').on('click', handleSlidePreview);
|
||||
|
||||
// Initialize sortable slides if library is available
|
||||
if (typeof $.fn.sortable !== 'undefined') {
|
||||
initializeSortable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle form submission
|
||||
*/
|
||||
function handleFormSubmission(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var $form = $(this);
|
||||
var $submitBtn = $form.find('[type="submit"]');
|
||||
var originalText = $submitBtn.text();
|
||||
|
||||
// Show loading state
|
||||
$submitBtn.text('Saving...').prop('disabled', true);
|
||||
|
||||
// Simulate form processing (replace with actual AJAX call)
|
||||
setTimeout(function() {
|
||||
$submitBtn.text('Saved!').removeClass('hssm-btn').addClass('hssm-btn-success');
|
||||
|
||||
setTimeout(function() {
|
||||
$submitBtn.text(originalText).removeClass('hssm-btn-success').addClass('hssm-btn').prop('disabled', false);
|
||||
}, 2000);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle media upload
|
||||
*/
|
||||
function handleMediaUpload(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var $button = $(this);
|
||||
var $input = $button.siblings('input[type="hidden"]');
|
||||
var $preview = $button.siblings('.hssm-image-preview');
|
||||
|
||||
// WordPress media uploader
|
||||
if (typeof wp !== 'undefined' && wp.media) {
|
||||
var mediaUploader = wp.media({
|
||||
title: 'Choose Slide Image',
|
||||
button: {
|
||||
text: 'Use this image'
|
||||
},
|
||||
multiple: false
|
||||
});
|
||||
|
||||
mediaUploader.on('select', function() {
|
||||
var attachment = mediaUploader.state().get('selection').first().toJSON();
|
||||
$input.val(attachment.id);
|
||||
$preview.html('<img src="' + attachment.url + '" style="max-width: 200px; height: auto;">');
|
||||
});
|
||||
|
||||
mediaUploader.open();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle slide preview
|
||||
*/
|
||||
function handleSlidePreview(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var $button = $(this);
|
||||
var slideData = gatherSlideData($button.closest('.hssm-slide-form'));
|
||||
|
||||
// Create preview modal
|
||||
var $modal = $('<div class="hssm-modal">');
|
||||
var $modalContent = $('<div class="hssm-modal-content">');
|
||||
var $closeBtn = $('<span class="hssm-modal-close">×</span>');
|
||||
|
||||
$modalContent.append($closeBtn);
|
||||
$modalContent.append('<h3>Slide Preview</h3>');
|
||||
$modalContent.append('<div class="hssm-slide-preview">' + generateSlideHTML(slideData) + '</div>');
|
||||
|
||||
$modal.append($modalContent);
|
||||
$('body').append($modal);
|
||||
|
||||
// Show modal
|
||||
$modal.fadeIn();
|
||||
|
||||
// Close modal handlers
|
||||
$closeBtn.on('click', function() {
|
||||
$modal.fadeOut(function() {
|
||||
$modal.remove();
|
||||
});
|
||||
});
|
||||
|
||||
$modal.on('click', function(e) {
|
||||
if (e.target === this) {
|
||||
$modal.fadeOut(function() {
|
||||
$modal.remove();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize sortable slides
|
||||
*/
|
||||
function initializeSortable() {
|
||||
$('.hssm-slides-list').sortable({
|
||||
handle: '.hssm-slide-handle',
|
||||
placeholder: 'hssm-slide-placeholder',
|
||||
update: function(event, ui) {
|
||||
// Handle slide reordering
|
||||
updateSlideOrder();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather slide data from form
|
||||
*/
|
||||
function gatherSlideData($form) {
|
||||
return {
|
||||
title: $form.find('[name="slide_title"]').val(),
|
||||
content: $form.find('[name="slide_content"]').val(),
|
||||
image: $form.find('[name="slide_image"]').val()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate slide HTML for preview
|
||||
*/
|
||||
function generateSlideHTML(data) {
|
||||
var html = '<div class="hssm-slide">';
|
||||
html += '<h3>' + (data.title || 'Untitled Slide') + '</h3>';
|
||||
html += '<div class="hssm-slide-content">' + (data.content || 'No content') + '</div>';
|
||||
if (data.image) {
|
||||
html += '<div class="hssm-slide-image"><img src="' + data.image + '" alt="Slide Image"></div>';
|
||||
}
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update slide order after sorting
|
||||
*/
|
||||
function updateSlideOrder() {
|
||||
var order = [];
|
||||
$('.hssm-slides-list .hssm-slide-item').each(function() {
|
||||
order.push($(this).data('slide-id'));
|
||||
});
|
||||
|
||||
// Send order to server (implement AJAX call)
|
||||
console.log('New slide order:', order);
|
||||
}
|
||||
});
|
||||
|
||||
// Add modal styles
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.hssm-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.hssm-modal-content {
|
||||
background-color: #fff;
|
||||
margin: 5% auto;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
width: 80%;
|
||||
max-width: 600px;
|
||||
position: relative;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.hssm-modal-close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 15px;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.hssm-modal-close:hover,
|
||||
.hssm-modal-close:focus {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.hssm-btn-success {
|
||||
background: #46b450 !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Hi-School Slide Manager - Frontend JavaScript
|
||||
*/
|
||||
|
||||
jQuery(document).ready(function($) {
|
||||
'use strict';
|
||||
|
||||
// Initialize slide functionality
|
||||
initializeSlides();
|
||||
|
||||
/**
|
||||
* Initialize slide functionality
|
||||
*/
|
||||
function initializeSlides() {
|
||||
$('.hssm-slides-container').each(function() {
|
||||
var $container = $(this);
|
||||
var $slides = $container.find('.hssm-slide');
|
||||
|
||||
// Add fade-in animation
|
||||
$slides.each(function(index) {
|
||||
var $slide = $(this);
|
||||
setTimeout(function() {
|
||||
$slide.addClass('hssm-fade-in');
|
||||
}, index * 100);
|
||||
});
|
||||
|
||||
// Add click handlers for interactive slides
|
||||
$slides.on('click', function() {
|
||||
var $slide = $(this);
|
||||
$slide.toggleClass('hssm-slide-expanded');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle slide interactions
|
||||
*/
|
||||
$(document).on('click', '.hssm-slide-toggle', function(e) {
|
||||
e.preventDefault();
|
||||
var $button = $(this);
|
||||
var $slide = $button.closest('.hssm-slide');
|
||||
var $content = $slide.find('.hssm-slide-content');
|
||||
|
||||
$content.slideToggle(300);
|
||||
$button.text($content.is(':visible') ? 'Hide' : 'Show');
|
||||
});
|
||||
|
||||
/**
|
||||
* Handle keyboard navigation
|
||||
*/
|
||||
$(document).on('keydown', '.hssm-slide', function(e) {
|
||||
if (e.which === 13 || e.which === 32) { // Enter or Space
|
||||
e.preventDefault();
|
||||
$(this).click();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add CSS classes via JavaScript for animations
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.hssm-slide {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition: opacity 0.5s ease, transform 0.5s ease;
|
||||
}
|
||||
|
||||
.hssm-slide.hssm-fade-in {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.hssm-slide.hssm-slide-expanded {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
});
|
||||
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* Hi-School Slide Manager Preview JavaScript
|
||||
*
|
||||
* Handles interactions on the slide preview page
|
||||
*
|
||||
* @package Hi_School_Slide_Manager
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
// Preview object
|
||||
const HSSMPreview = {
|
||||
|
||||
// Initialize
|
||||
init: function() {
|
||||
this.bindEvents();
|
||||
this.initDatepicker();
|
||||
},
|
||||
|
||||
// Initialize Datepicker
|
||||
initDatepicker: function() {
|
||||
if ($('#hssm_date').length === 0) return;
|
||||
|
||||
$('#hssm_date').datepicker({
|
||||
dateFormat: 'yy-mm-dd',
|
||||
beforeShowDay: function(date) {
|
||||
var y = date.getFullYear();
|
||||
var m = date.getMonth() + 1;
|
||||
var d = date.getDate();
|
||||
|
||||
// Pad month and day
|
||||
var dateString = y + '-' + (m < 10 ? '0' : '') + m + '-' + (d < 10 ? '0' : '') + d;
|
||||
|
||||
if (typeof hssmEvents !== 'undefined' && hssmEvents[dateString]) {
|
||||
var classes = [];
|
||||
var events = hssmEvents[dateString];
|
||||
|
||||
if (events.indexOf('start') !== -1) {
|
||||
classes.push('hssm-datepicker-start');
|
||||
}
|
||||
if (events.indexOf('end') !== -1) {
|
||||
classes.push('hssm-datepicker-end');
|
||||
}
|
||||
|
||||
return [true, classes.join(' '), ''];
|
||||
}
|
||||
|
||||
return [true, '', ''];
|
||||
},
|
||||
onSelect: function(dateText) {
|
||||
// Trigger form submit
|
||||
$(this).closest('form').submit();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Bind event handlers
|
||||
bindEvents: function() {
|
||||
$(document).on('click', '#email-preview', this.emailPreview.bind(this));
|
||||
$(document).on('click', '#download-screenshot', this.downloadScreenshot.bind(this));
|
||||
$(document).on('click', '#copy-preview-link', this.copyPreviewLink.bind(this));
|
||||
$(document).on('click', '.hssm-approve-slide', this.approveSlide.bind(this));
|
||||
},
|
||||
|
||||
// Email preview functionality
|
||||
emailPreview: function(e) {
|
||||
e.preventDefault();
|
||||
this.showMessage('Email preview functionality coming soon!', 'info');
|
||||
},
|
||||
|
||||
// Download screenshot functionality
|
||||
downloadScreenshot: function(e) {
|
||||
e.preventDefault();
|
||||
this.showMessage('Screenshot download functionality coming soon!', 'info');
|
||||
},
|
||||
|
||||
// Copy preview link
|
||||
copyPreviewLink: function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const previewUrl = window.location.href;
|
||||
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(previewUrl).then(() => {
|
||||
this.showMessage('Preview link copied to clipboard!', 'success');
|
||||
});
|
||||
} else {
|
||||
this.showMessage('Preview link: ' + previewUrl, 'info');
|
||||
}
|
||||
},
|
||||
|
||||
// Approve slide
|
||||
approveSlide: function(e) {
|
||||
e.preventDefault();
|
||||
const $btn = $(e.currentTarget);
|
||||
const slideId = $btn.data('id');
|
||||
|
||||
if (!confirm(hssmPreview.strings.approve_confirm)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$btn.prop('disabled', true).addClass('hssm-loading');
|
||||
|
||||
$.ajax({
|
||||
url: hssmPreview.ajaxurl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'hssm_approve_slide',
|
||||
nonce: hssmPreview.nonce,
|
||||
slide_id: slideId,
|
||||
approve_action: 'schedule' // Smart approve: schedules if future date exists, otherwise publishes
|
||||
},
|
||||
success: (response) => {
|
||||
if (response.success) {
|
||||
this.showMessage(hssmPreview.strings.approve_success, 'success');
|
||||
// Reload page to reflect changes
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1500);
|
||||
} else {
|
||||
this.showMessage(response.data.message || hssmPreview.strings.approve_error, 'error');
|
||||
$btn.prop('disabled', false).removeClass('hssm-loading');
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
this.showMessage(hssmPreview.strings.approve_error, 'error');
|
||||
$btn.prop('disabled', false).removeClass('hssm-loading');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Show message to user
|
||||
showMessage: function(message, type = 'info') {
|
||||
// Create message container if it doesn't exist
|
||||
let $messages = $('#hssm-preview-messages');
|
||||
|
||||
if ($messages.length === 0) {
|
||||
$messages = $('<div id="hssm-preview-messages" class="hssm-messages"></div>');
|
||||
$('.hssm-preview-actions').before($messages);
|
||||
}
|
||||
|
||||
const messageHtml = `
|
||||
<div class="hssm-message ${type}">
|
||||
${message}
|
||||
</div>
|
||||
`;
|
||||
|
||||
$messages.append(messageHtml);
|
||||
|
||||
// Auto-remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
$messages.find('.hssm-message').last().fadeOut(300, function() {
|
||||
$(this).remove();
|
||||
});
|
||||
}, 5000);
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize when document is ready
|
||||
$(document).ready(function() {
|
||||
HSSMPreview.init();
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* HSSM Slider JavaScript
|
||||
* Handles navigation and autoplay for the [hssm_slider] shortcode
|
||||
* Custom classes version
|
||||
*/
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
var HSSMSlider = function(container) {
|
||||
this.$container = $(container);
|
||||
this.$track = this.$container.find('.hssm-track');
|
||||
this.$slides = this.$container.find('.hssm-slide');
|
||||
this.slideCount = this.$slides.length;
|
||||
this.currentIndex = 0;
|
||||
this.autoplay = this.$container.data('autoplay') === true || this.$container.data('autoplay') === 'true';
|
||||
this.interval = parseInt(this.$container.data('interval')) || 5000;
|
||||
this.timer = null;
|
||||
|
||||
if (this.slideCount <= 1) return;
|
||||
|
||||
this.init();
|
||||
};
|
||||
|
||||
HSSMSlider.prototype.init = function() {
|
||||
var self = this;
|
||||
|
||||
// Arrows
|
||||
this.$container.find('.hssm-arrow-next').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
self.stopAutoplay();
|
||||
self.next();
|
||||
self.startAutoplay();
|
||||
});
|
||||
|
||||
this.$container.find('.hssm-arrow-prev').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
self.stopAutoplay();
|
||||
self.prev();
|
||||
self.startAutoplay();
|
||||
});
|
||||
|
||||
// Touch swipe support
|
||||
var touchStartX = 0;
|
||||
var touchStartY = 0;
|
||||
|
||||
this.$container.on('touchstart', function(e) {
|
||||
touchStartX = e.originalEvent.touches[0].pageX;
|
||||
touchStartY = e.originalEvent.touches[0].pageY;
|
||||
});
|
||||
|
||||
this.$container.on('touchend', function(e) {
|
||||
var touchEndX = e.originalEvent.changedTouches[0].pageX;
|
||||
var touchEndY = e.originalEvent.changedTouches[0].pageY;
|
||||
|
||||
// Calculate distance
|
||||
var dx = touchStartX - touchEndX;
|
||||
var dy = touchStartY - touchEndY;
|
||||
|
||||
// Only swipe if horizontal movement is greater than vertical (prevents swipe on scroll)
|
||||
if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 50) {
|
||||
if (dx > 0) {
|
||||
self.next();
|
||||
} else {
|
||||
self.prev();
|
||||
}
|
||||
self.stopAutoplay();
|
||||
self.startAutoplay();
|
||||
}
|
||||
});
|
||||
|
||||
this.startAutoplay();
|
||||
};
|
||||
|
||||
HSSMSlider.prototype.update = function() {
|
||||
var offset = -this.currentIndex * 100;
|
||||
this.$track.css('transform', 'translateX(' + offset + '%)');
|
||||
};
|
||||
|
||||
HSSMSlider.prototype.next = function() {
|
||||
this.currentIndex = (this.currentIndex + 1) % this.slideCount;
|
||||
this.update();
|
||||
};
|
||||
|
||||
HSSMSlider.prototype.prev = function() {
|
||||
this.currentIndex = (this.currentIndex - 1 + this.slideCount) % this.slideCount;
|
||||
this.update();
|
||||
};
|
||||
|
||||
HSSMSlider.prototype.startAutoplay = function() {
|
||||
if (!this.autoplay) return;
|
||||
var self = this;
|
||||
this.timer = setInterval(function() {
|
||||
self.next();
|
||||
}, this.interval);
|
||||
};
|
||||
|
||||
HSSMSlider.prototype.stopAutoplay = function() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
};
|
||||
|
||||
$(document).ready(function() {
|
||||
$('.hssm-slider-container').each(function() {
|
||||
new HSSMSlider(this);
|
||||
});
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: Hi-School Slide Manager
|
||||
* Plugin URI: https://hi-school.com/slide-manager
|
||||
* Description: Multi-site slide management system for Hi-School websites. Allows staff to create and schedule slides on the main site, display them as headers on companion sites via REST API, with advanced preview and editing capabilities.
|
||||
* Version: 1.1.1
|
||||
* Author: Hi-School Development Team
|
||||
* Author URI: https://hi-school.com
|
||||
* License: GPL v2 or later
|
||||
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
* Text Domain: hi-school-slide-manager
|
||||
* Domain Path: /languages
|
||||
* Network: false
|
||||
* Requires at least: 5.0
|
||||
* Tested up to: 6.4
|
||||
* Requires PHP: 7.4
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 'Direct access forbidden.' );
|
||||
}
|
||||
|
||||
// Plugin constants
|
||||
define( 'HSSM_VERSION', '1.1.1' );
|
||||
define( 'HSSM_PLUGIN_FILE', __FILE__ );
|
||||
define( 'HSSM_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||
define( 'HSSM_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
||||
define( 'HSSM_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
|
||||
|
||||
// Database table names (if needed for future features)
|
||||
// define( 'HSSM_SLIDES_TABLE', 'hssm_slides' );
|
||||
// define( 'HSSM_SITES_TABLE', 'hssm_companion_sites' );
|
||||
|
||||
// Include core files
|
||||
require_once HSSM_PLUGIN_DIR . 'includes/class-hssm-autoloader.php';
|
||||
require_once HSSM_PLUGIN_DIR . 'includes/class-hssm-main.php';
|
||||
|
||||
/**
|
||||
* Plugin activation hook
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
function hssm_activate() {
|
||||
// Create custom capability
|
||||
$admin_role = get_role( 'administrator' );
|
||||
if ( $admin_role ) {
|
||||
$admin_role->add_cap( 'hssm_manage_slides' );
|
||||
}
|
||||
|
||||
// Also add to editor role
|
||||
$editor_role = get_role( 'editor' );
|
||||
if ( $editor_role ) {
|
||||
$editor_role->add_cap( 'hssm_manage_slides' );
|
||||
}
|
||||
|
||||
// Flush rewrite rules to ensure our CPT permalinks work
|
||||
flush_rewrite_rules();
|
||||
}
|
||||
register_activation_hook( __FILE__, 'hssm_activate' );
|
||||
|
||||
/**
|
||||
* Plugin deactivation hook
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
function hssm_deactivate() {
|
||||
// Remove custom capability
|
||||
$admin_role = get_role( 'administrator' );
|
||||
if ( $admin_role ) {
|
||||
$admin_role->remove_cap( 'hssm_manage_slides' );
|
||||
}
|
||||
|
||||
$editor_role = get_role( 'editor' );
|
||||
if ( $editor_role ) {
|
||||
$editor_role->remove_cap( 'hssm_manage_slides' );
|
||||
}
|
||||
|
||||
// Flush rewrite rules
|
||||
flush_rewrite_rules();
|
||||
}
|
||||
register_deactivation_hook( __FILE__, 'hssm_deactivate' );
|
||||
|
||||
/**
|
||||
* Initialize the Hi-School Slide Manager plugin
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
function hssm_init() {
|
||||
// Check if we're on the main site (this plugin should only run on main site)
|
||||
if ( is_multisite() && ! is_main_site() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize autoloader
|
||||
HSSM_Autoloader::init();
|
||||
|
||||
// Get the main plugin instance and initialize
|
||||
$hssm = HSSM_Main::get_instance();
|
||||
$hssm->init();
|
||||
}
|
||||
|
||||
// Hook into WordPress
|
||||
add_action( 'plugins_loaded', 'hssm_init' );
|
||||
|
||||
// Activation and deactivation hooks
|
||||
register_activation_hook( __FILE__, array( 'HSSM_Main', 'activate' ) );
|
||||
register_deactivation_hook( __FILE__, array( 'HSSM_Main', 'deactivate' ) );
|
||||
register_uninstall_hook( __FILE__, array( 'HSSM_Main', 'uninstall' ) );
|
||||
@@ -0,0 +1,551 @@
|
||||
<?php
|
||||
/**
|
||||
* HSSM Admin Class
|
||||
*
|
||||
* Handles admin functionality including settings pages and API key management
|
||||
*
|
||||
* @package Hi_School_Slide_Manager
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 'Direct access forbidden.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* HSSM Admin Class
|
||||
*
|
||||
* Admin interface and settings management
|
||||
*/
|
||||
class HSSM_Admin {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct() {
|
||||
// Hook is registered in main class
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize admin functionality
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function init() {
|
||||
// This is called during the admin_init hook from HSSM_Main
|
||||
$this->register_settings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add admin menus
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function add_menus() {
|
||||
// Add settings page under the Slides menu
|
||||
add_submenu_page(
|
||||
'edit.php?post_type=hssm_slide',
|
||||
__( 'Slide Manager Settings', 'hi-school-slide-manager' ),
|
||||
__( 'Settings', 'hi-school-slide-manager' ),
|
||||
'manage_options',
|
||||
'hssm-settings',
|
||||
array( $this, 'settings_page' )
|
||||
);
|
||||
|
||||
// Add API documentation page
|
||||
add_submenu_page(
|
||||
'edit.php?post_type=hssm_slide',
|
||||
__( 'API Documentation', 'hi-school-slide-manager' ),
|
||||
__( 'API Docs', 'hi-school-slide-manager' ),
|
||||
'manage_options',
|
||||
'hssm-api-docs',
|
||||
array( $this, 'api_docs_page' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register plugin settings
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function register_settings() {
|
||||
// Register settings
|
||||
register_setting( 'hssm_settings', 'hssm_plugin_mode' );
|
||||
register_setting( 'hssm_settings', 'hssm_api_url' );
|
||||
register_setting( 'hssm_settings', 'hssm_client_site_slug' );
|
||||
register_setting( 'hssm_settings', 'hssm_notification_emails' );
|
||||
register_setting( 'hssm_settings', 'hssm_cache_duration' );
|
||||
register_setting( 'hssm_settings', 'hssm_api_keys' );
|
||||
|
||||
// Add settings sections
|
||||
add_settings_section(
|
||||
'hssm_general',
|
||||
__( 'General Settings', 'hi-school-slide-manager' ),
|
||||
array( $this, 'general_section_callback' ),
|
||||
'hssm-settings'
|
||||
);
|
||||
|
||||
add_settings_section(
|
||||
'hssm_notifications',
|
||||
__( 'Email Notifications', 'hi-school-slide-manager' ),
|
||||
array( $this, 'notifications_section_callback' ),
|
||||
'hssm-settings'
|
||||
);
|
||||
|
||||
add_settings_section(
|
||||
'hssm_api',
|
||||
__( 'API Settings', 'hi-school-slide-manager' ),
|
||||
array( $this, 'api_section_callback' ),
|
||||
'hssm-settings'
|
||||
);
|
||||
|
||||
// Add settings fields
|
||||
add_settings_field(
|
||||
'hssm_plugin_mode',
|
||||
__( 'Plugin Mode', 'hi-school-slide-manager' ),
|
||||
array( $this, 'plugin_mode_field' ),
|
||||
'hssm-settings',
|
||||
'hssm_general'
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'hssm_api_url',
|
||||
__( 'Manager API URL', 'hi-school-slide-manager' ),
|
||||
array( $this, 'api_url_field' ),
|
||||
'hssm-settings',
|
||||
'hssm_general'
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'hssm_client_site_slug',
|
||||
__( 'Client Site Slug', 'hi-school-slide-manager' ),
|
||||
array( $this, 'client_site_slug_field' ),
|
||||
'hssm-settings',
|
||||
'hssm_general'
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'hssm_cache_duration',
|
||||
__( 'Cache Duration', 'hi-school-slide-manager' ),
|
||||
array( $this, 'cache_duration_field' ),
|
||||
'hssm-settings',
|
||||
'hssm_general'
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'hssm_notification_emails',
|
||||
__( 'Notification Recipients', 'hi-school-slide-manager' ),
|
||||
array( $this, 'notification_emails_field' ),
|
||||
'hssm-settings',
|
||||
'hssm_notifications'
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'hssm_api_keys',
|
||||
__( 'API Keys', 'hi-school-slide-manager' ),
|
||||
array( $this, 'api_keys_field' ),
|
||||
'hssm-settings',
|
||||
'hssm_api'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings page callback
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function settings_page() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle form submission
|
||||
if ( isset( $_POST['submit'] ) || isset( $_POST['generate_api_key'] ) || isset( $_POST['delete_api_key'] ) ) {
|
||||
check_admin_referer( 'hssm_settings' );
|
||||
$this->handle_settings_save();
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
|
||||
|
||||
<?php settings_errors(); ?>
|
||||
|
||||
<form method="post" action="">
|
||||
<?php
|
||||
wp_nonce_field( 'hssm_settings' );
|
||||
// Use a consistent page slug for do_settings_sections
|
||||
do_settings_sections( 'hssm-settings' );
|
||||
submit_button();
|
||||
?>
|
||||
</form>
|
||||
|
||||
<div class="hssm-api-endpoint-info" style="margin-top: 30px; padding: 20px; background: #f9f9f9; border: 1px solid #ddd;">
|
||||
<h3><?php _e( 'API Endpoints', 'hi-school-slide-manager' ); ?></h3>
|
||||
<p><?php _e( 'Use these endpoints for companion sites:', 'hi-school-slide-manager' ); ?></p>
|
||||
<ul>
|
||||
<li><code><?php echo esc_html( rest_url( 'hssm/v1/slides/{site}' ) ); ?></code> - Get slides for a site</li>
|
||||
<li><code><?php echo esc_html( rest_url( 'hssm/v1/sites' ) ); ?></code> - Get available sites</li>
|
||||
<li><code><?php echo esc_html( rest_url( 'hssm/v1/slide/{id}' ) ); ?></code> - Get single slide</li>
|
||||
<li><code><?php echo esc_html( rest_url( 'hssm/v1/health' ) ); ?></code> - Health check (no auth required)</li>
|
||||
</ul>
|
||||
<p><strong><?php _e( 'Authentication:', 'hi-school-slide-manager' ); ?></strong> <?php _e( 'Include API key in X-HSSM-API-Key header or api_key parameter', 'hi-school-slide-manager' ); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* API documentation page
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function api_docs_page() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php _e( 'Hi-School Slide Manager API Documentation', 'hi-school-slide-manager' ); ?></h1>
|
||||
|
||||
<div class="hssm-api-docs">
|
||||
<h2><?php _e( 'Authentication', 'hi-school-slide-manager' ); ?></h2>
|
||||
<p><?php _e( 'All API endpoints (except health check) require authentication using an API key.', 'hi-school-slide-manager' ); ?></p>
|
||||
<p><?php _e( 'Include the API key in one of these ways:', 'hi-school-slide-manager' ); ?></p>
|
||||
<ul>
|
||||
<li><?php _e( 'Header:', 'hi-school-slide-manager' ); ?> <code>X-HSSM-API-Key: your-api-key</code></li>
|
||||
<li><?php _e( 'Query parameter:', 'hi-school-slide-manager' ); ?> <code>?api_key=your-api-key</code></li>
|
||||
</ul>
|
||||
|
||||
<h2><?php _e( 'Endpoints', 'hi-school-slide-manager' ); ?></h2>
|
||||
|
||||
<h3><code>GET /wp-json/hssm/v1/slides/{site}</code></h3>
|
||||
<p><?php _e( 'Get slides for a specific site.', 'hi-school-slide-manager' ); ?></p>
|
||||
<h4><?php _e( 'Parameters:', 'hi-school-slide-manager' ); ?></h4>
|
||||
<ul>
|
||||
<li><code>site</code> (required) - Site slug: hi-school, one-stop, or ace</li>
|
||||
<li><code>date</code> (optional) - Date to filter by (YYYY-MM-DD)</li>
|
||||
<li><code>limit</code> (optional) - Max slides to return (1-50, default: 10)</li>
|
||||
<li><code>status</code> (optional) - Filter by status: active, scheduled, all (default: active)</li>
|
||||
</ul>
|
||||
<h4><?php _e( 'Example:', 'hi-school-slide-manager' ); ?></h4>
|
||||
<pre><code>GET <?php echo esc_html( rest_url( 'hssm/v1/slides/hi-school?limit=5&date=2025-12-01' ) ); ?>
|
||||
Headers: X-HSSM-API-Key: your-api-key</code></pre>
|
||||
|
||||
<h3><code>GET /wp-json/hssm/v1/sites</code></h3>
|
||||
<p><?php _e( 'Get all available sites.', 'hi-school-slide-manager' ); ?></p>
|
||||
<h4><?php _e( 'Example:', 'hi-school-slide-manager' ); ?></h4>
|
||||
<pre><code>GET <?php echo esc_html( rest_url( 'hssm/v1/sites' ) ); ?>
|
||||
Headers: X-HSSM-API-Key: your-api-key</code></pre>
|
||||
|
||||
<h3><code>GET /wp-json/hssm/v1/slide/{id}</code></h3>
|
||||
<p><?php _e( 'Get a specific slide by ID.', 'hi-school-slide-manager' ); ?></p>
|
||||
<h4><?php _e( 'Parameters:', 'hi-school-slide-manager' ); ?></h4>
|
||||
<ul>
|
||||
<li><code>id</code> (required) - Slide ID</li>
|
||||
</ul>
|
||||
|
||||
<h3><code>GET /wp-json/hssm/v1/health</code></h3>
|
||||
<p><?php _e( 'Health check endpoint (no authentication required).', 'hi-school-slide-manager' ); ?></p>
|
||||
|
||||
<h2><?php _e( 'Response Format', 'hi-school-slide-manager' ); ?></h2>
|
||||
<p><?php _e( 'All responses are in JSON format. Slide objects include:', 'hi-school-slide-manager' ); ?></p>
|
||||
<pre><code>{
|
||||
"id": 123,
|
||||
"title": "Slide Title",
|
||||
"subtitle": "Slide Subtitle",
|
||||
"content": "Slide content...",
|
||||
"buttons": [
|
||||
{
|
||||
"text": "Learn More",
|
||||
"url": "https://example.com"
|
||||
}
|
||||
],
|
||||
"order": 0,
|
||||
"background_image": {
|
||||
"id": 456,
|
||||
"url": "https://example.com/image.jpg",
|
||||
"width": 1920,
|
||||
"height": 800,
|
||||
"alt": "Image description",
|
||||
"sizes": {
|
||||
"full": "https://example.com/image.jpg",
|
||||
"large": "https://example.com/image-1024x428.jpg"
|
||||
}
|
||||
},
|
||||
"schedule": {
|
||||
"start_date": "2025-01-01 00:00:00",
|
||||
"end_date": "2025-12-31 23:59:59",
|
||||
"auto_publish": true,
|
||||
"auto_unpublish": true
|
||||
},
|
||||
"target_sites": [
|
||||
{
|
||||
"slug": "hi-school",
|
||||
"name": "Hi-School Pharmacy"
|
||||
}
|
||||
],
|
||||
"client": {
|
||||
"slug": "client-name",
|
||||
"name": "Client Name"
|
||||
},
|
||||
"status": "publish",
|
||||
"created": "2025-01-01 12:00:00",
|
||||
"modified": "2025-01-01 12:00:00"
|
||||
}
|
||||
</code></pre>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.hssm-api-docs {
|
||||
max-width: 800px;
|
||||
}
|
||||
.hssm-api-docs h3 {
|
||||
background: #f0f0f0;
|
||||
padding: 10px;
|
||||
border-left: 4px solid #0073aa;
|
||||
}
|
||||
.hssm-api-docs pre {
|
||||
background: #f9f9f9;
|
||||
padding: 15px;
|
||||
border: 1px solid #ddd;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.hssm-api-docs code {
|
||||
background: #f9f9f9;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.hssm-api-docs pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue admin assets
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param string $hook Current admin page hook
|
||||
*/
|
||||
public function enqueue_assets( $hook ) {
|
||||
// Enqueue general admin styles for all HSSM admin pages
|
||||
if ( strpos( $hook, 'hssm' ) !== false || ( isset($_GET['post_type']) && $_GET['post_type'] === 'hssm_slide' ) ) {
|
||||
wp_enqueue_style(
|
||||
'hssm-admin',
|
||||
HSSM_PLUGIN_URL . 'assets/css/admin.css',
|
||||
array(),
|
||||
HSSM_VERSION
|
||||
);
|
||||
wp_enqueue_script(
|
||||
'hssm-admin',
|
||||
HSSM_PLUGIN_URL . 'assets/js/admin.js',
|
||||
array( 'jquery' ),
|
||||
HSSM_VERSION,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// Conditionally enqueue dashboard-specific slide styles and dequeue public slider styles
|
||||
global $pagenow;
|
||||
if ( ( $pagenow === 'edit.php' && isset($_GET['post_type']) && $_GET['post_type'] === 'hssm_slide' ) || is_page_template('page-slide-dashboard.php') || is_page_template('page-slide-preview.php') ) {
|
||||
wp_enqueue_style(
|
||||
'hssm-admin-slides-dashboard',
|
||||
HSSM_PLUGIN_URL . 'assets/css/admin-slides-dashboard.css',
|
||||
array(),
|
||||
HSSM_VERSION
|
||||
);
|
||||
// Dequeue the public slider styles on these admin-facing pages to prevent conflicts
|
||||
// This is largely redundant after changes in HSSM_Main::enqueue_frontend_assets,
|
||||
// but harmless to keep as a failsafe.
|
||||
wp_dequeue_style('hssm-slider');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle settings save
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function handle_settings_save() {
|
||||
// Handle general settings save
|
||||
if ( isset( $_POST['submit'] ) ) {
|
||||
if ( isset( $_POST['hssm_plugin_mode'] ) ) {
|
||||
update_option( 'hssm_plugin_mode', sanitize_text_field( $_POST['hssm_plugin_mode'] ) );
|
||||
}
|
||||
if ( isset( $_POST['hssm_api_url'] ) ) {
|
||||
update_option( 'hssm_api_url', esc_url_raw( $_POST['hssm_api_url'] ) );
|
||||
}
|
||||
if ( isset( $_POST['hssm_client_site_slug'] ) ) {
|
||||
update_option( 'hssm_client_site_slug', sanitize_text_field( $_POST['hssm_client_site_slug'] ) );
|
||||
}
|
||||
if ( isset( $_POST['hssm_cache_duration'] ) ) {
|
||||
update_option( 'hssm_cache_duration', absint( $_POST['hssm_cache_duration'] ) );
|
||||
}
|
||||
if ( isset( $_POST['hssm_notification_emails'] ) ) {
|
||||
$emails = explode( "\n", str_replace( "\r", "", $_POST['hssm_notification_emails'] ) );
|
||||
$emails = array_map( 'sanitize_email', $emails );
|
||||
$emails = array_filter( $emails );
|
||||
update_option( 'hssm_notification_emails', $emails );
|
||||
}
|
||||
add_settings_error( 'hssm_settings', 'settings_saved', __( 'Settings saved successfully.', 'hi-school-slide-manager' ), 'success' );
|
||||
}
|
||||
|
||||
// Handle API key generation
|
||||
if ( isset( $_POST['generate_api_key'] ) ) {
|
||||
$api_keys = get_option( 'hssm_api_keys', array() );
|
||||
$api_keys[] = wp_generate_password( 32, false );
|
||||
update_option( 'hssm_api_keys', $api_keys );
|
||||
add_settings_error( 'hssm_settings', 'api_key_generated', __( 'New API key generated successfully.', 'hi-school-slide-manager' ), 'success' );
|
||||
}
|
||||
|
||||
// Handle API key deletion
|
||||
if ( isset( $_POST['delete_api_key'] ) && isset( $_POST['api_key_index'] ) ) {
|
||||
$api_keys = get_option( 'hssm_api_keys', array() );
|
||||
$index = (int) $_POST['api_key_index'];
|
||||
if ( isset( $api_keys[ $index ] ) ) {
|
||||
unset( $api_keys[ $index ] );
|
||||
$api_keys = array_values( $api_keys ); // Reindex array
|
||||
update_option( 'hssm_api_keys', $api_keys );
|
||||
add_settings_error( 'hssm_settings', 'api_key_deleted', __( 'API key deleted successfully.', 'hi-school-slide-manager' ), 'success' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* General section callback
|
||||
*/
|
||||
public function general_section_callback() {
|
||||
echo '<p>' . __( 'General plugin settings.', 'hi-school-slide-manager' ) . '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifications section callback
|
||||
*/
|
||||
public function notifications_section_callback() {
|
||||
echo '<p>' . __( 'Configure email notifications for new slides.', 'hi-school-slide-manager' ) . '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* API section callback
|
||||
*/
|
||||
public function api_section_callback() {
|
||||
echo '<p>' . __( 'Manage API keys for companion sites.', 'hi-school-slide-manager' ) . '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin mode field
|
||||
*/
|
||||
public function plugin_mode_field() {
|
||||
$value = get_option( 'hssm_plugin_mode', 'manager' );
|
||||
?>
|
||||
<select name="hssm_plugin_mode" id="hssm_plugin_mode">
|
||||
<option value="manager" <?php selected( $value, 'manager' ); ?>><?php _e( 'Manager (Source Site)', 'hi-school-slide-manager' ); ?></option>
|
||||
<option value="client" <?php selected( $value, 'client' ); ?>><?php _e( 'Client (Companion Site)', 'hi-school-slide-manager' ); ?></option>
|
||||
</select>
|
||||
<p class="description"><?php _e( 'Choose "Manager" for the main site where slides are created, or "Client" for sites that just display slides from the manager.', 'hi-school-slide-manager' ); ?></p>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* API URL field
|
||||
*/
|
||||
public function api_url_field() {
|
||||
$value = get_option( 'hssm_api_url', '' );
|
||||
echo '<input type="url" name="hssm_api_url" value="' . esc_attr( $value ) . '" class="regular-text" placeholder="https://main-site.com/wp-json/" /> ';
|
||||
echo '<p class="description">' . __( 'The REST API base URL of the Manager site.', 'hi-school-slide-manager' ) . '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Client site slug field
|
||||
*/
|
||||
public function client_site_slug_field() {
|
||||
$value = get_option( 'hssm_client_site_slug', '' );
|
||||
?>
|
||||
<select name="hssm_client_site_slug" id="hssm_client_site_slug">
|
||||
<option value=""><?php _e( 'Select site...', 'hi-school-slide-manager' ); ?></option>
|
||||
<?php
|
||||
$sites = HSSM_Main::get_companion_sites();
|
||||
foreach ( $sites as $site ) {
|
||||
echo '<option value="' . esc_attr( $site['slug'] ) . '" ' . selected( $value, $site['slug'], false ) . '>' . esc_html( $site['name'] ) . '</option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<p class="description"><?php _e( 'Identify this site to the Manager API.', 'hi-school-slide-manager' ); ?></p>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache duration field
|
||||
*/
|
||||
public function cache_duration_field() {
|
||||
$value = get_option( 'hssm_cache_duration', 3600 );
|
||||
echo '<input type="number" name="hssm_cache_duration" value="' . esc_attr( $value ) . '" min="300" max="86400" /> ';
|
||||
echo '<span class="description">' . __( 'Recommended cache duration for API responses (seconds)', 'hi-school-slide-manager' ) . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification emails field
|
||||
*/
|
||||
public function notification_emails_field() {
|
||||
$emails = get_option( 'hssm_notification_emails', array() );
|
||||
echo '<textarea name="hssm_notification_emails" rows="5" class="regular-text">' . esc_textarea( implode( "\n", $emails ) ) . '</textarea><br>';
|
||||
echo '<span class="description">' . __( 'Enter email addresses (one per line) to receive notifications when new slides are published.', 'hi-school-slide-manager' ) . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* API keys field
|
||||
*/
|
||||
public function api_keys_field() {
|
||||
$api_keys = get_option( 'hssm_api_keys', array() );
|
||||
|
||||
echo '<div class="hssm-api-keys">';
|
||||
|
||||
if ( empty( $api_keys ) ) {
|
||||
echo '<p>' . __( 'No API keys generated yet.', 'hi-school-slide-manager' ) . '</p>';
|
||||
} else {
|
||||
echo '<table class="widefat">';
|
||||
echo '<thead><tr><th>' . __( 'API Key', 'hi-school-slide-manager' ) . '</th><th>' . __( 'Actions', 'hi-school-slide-manager' ) . '</th></tr></thead>';
|
||||
echo '<tbody>';
|
||||
|
||||
foreach ( $api_keys as $index => $key ) {
|
||||
echo '<tr>';
|
||||
echo '<td><code>' . esc_html( $key ) . '</code></td>';
|
||||
echo '<td>';
|
||||
echo '<button type="button" class="button copy-api-key" data-key="' . esc_attr( $key ) . '">' . __( 'Copy', 'hi-school-slide-manager' ) . '</button> ';
|
||||
echo '<button type="submit" name="delete_api_key" value="1" class="button button-link-delete" onclick="return confirm(\'' . esc_js( __( 'Are you sure you want to delete this API key?', 'hi-school-slide-manager' ) ) . '\')">' . __( 'Delete', 'hi-school-slide-manager' ) . '</button>';
|
||||
echo '<input type="hidden" name="api_key_index" value="' . esc_attr( $index ) . '" />';
|
||||
echo '</td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
|
||||
echo '</tbody></table>';
|
||||
}
|
||||
|
||||
echo '<p><button type="submit" name="generate_api_key" value="1" class="button button-secondary">' . __( 'Generate New API Key', 'hi-school-slide-manager' ) . '</button></p>';
|
||||
echo '<p class="description">' . __( 'API keys are used by companion sites to authenticate and fetch slides.', 'hi-school-slide-manager' ) . '</p>';
|
||||
|
||||
echo '</div>';
|
||||
|
||||
// Add JavaScript for copy functionality
|
||||
?>
|
||||
<script>
|
||||
jQuery(document).ready(function($) {
|
||||
$(".copy-api-key").on("click", function() {
|
||||
var key = $(this).data("key");
|
||||
navigator.clipboard.writeText(key).then(function() {
|
||||
alert("API key copied to clipboard!");
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/**
|
||||
* HSSM API Client Class
|
||||
*
|
||||
* Handles fetching slides from the remote Manager site API
|
||||
*
|
||||
* @package Hi_School_Slide_Manager
|
||||
* @since 1.1.0
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 'Direct access forbidden.' );
|
||||
}
|
||||
|
||||
class HSSM_API_Client {
|
||||
|
||||
/**
|
||||
* Get slides for this site
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @return array|WP_Error Array of slides or WP_Error on failure
|
||||
*/
|
||||
public function get_slides() {
|
||||
$api_url = get_option( 'hssm_api_url' );
|
||||
$site_slug = get_option( 'hssm_client_site_slug' );
|
||||
$api_key = 'HI_SCHOOL_FIXED_API_KEY'; // Hardcoded fixed API key
|
||||
$cache_duration = get_option( 'hssm_cache_duration', 3600 );
|
||||
|
||||
if ( empty( $api_url ) || empty( $site_slug ) || empty( $api_key ) ) { // Added $api_key check here
|
||||
return new WP_Error( 'missing_config', __( 'API URL, Site Slug, or API Key not configured.', 'hi-school-slide-manager' ) ); // Updated error message
|
||||
}
|
||||
|
||||
// Check cache first
|
||||
$cache_key = 'hssm_remote_slides_' . $site_slug;
|
||||
$cached_slides = get_transient( $cache_key );
|
||||
if ( false !== $cached_slides ) {
|
||||
return $cached_slides;
|
||||
}
|
||||
|
||||
// Build the request URL
|
||||
// Ensure trailing slash on API URL and remove leading slash from endpoint
|
||||
$endpoint = sprintf( 'hssm/v1/slides/%s', $site_slug );
|
||||
$full_url = trailingslashit( $api_url ) . $endpoint;
|
||||
|
||||
$args = array(
|
||||
'timeout' => 15,
|
||||
'headers' => array(
|
||||
'X-HSSM-API-Key' => $api_key,
|
||||
'Accept' => 'application/json',
|
||||
),
|
||||
);
|
||||
|
||||
$response = wp_remote_get( $full_url, $args );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$code = wp_remote_retrieve_response_code( $response );
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
|
||||
if ( 200 !== $code ) {
|
||||
return new WP_Error( 'api_error', sprintf( __( 'API returned code %d: %s', 'hi-school-slide-manager' ), $code, $body ) );
|
||||
}
|
||||
|
||||
$data = json_decode( $body, true );
|
||||
if ( ! is_array( $data ) || ! isset( $data['slides'] ) ) {
|
||||
return new WP_Error( 'invalid_response', __( 'Invalid API response format.', 'hi-school-slide-manager' ) );
|
||||
}
|
||||
|
||||
$slides = $data['slides'];
|
||||
|
||||
// Cache the results
|
||||
set_transient( $cache_key, $slides, $cache_duration );
|
||||
|
||||
return $slides;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the slides cache
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public function clear_cache() {
|
||||
$site_slug = get_option( 'hssm_client_site_slug' );
|
||||
if ( ! empty( $site_slug ) ) {
|
||||
delete_transient( 'hssm_remote_slides_' . $site_slug );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* HSSM Autoloader Class
|
||||
*
|
||||
* Autoloader for the Hi-School Slide Manager plugin classes
|
||||
*
|
||||
* @package Hi_School_Slide_Manager
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 'Direct access forbidden.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* HSSM Autoloader Class
|
||||
*
|
||||
* Handles automatic loading of plugin classes
|
||||
*/
|
||||
class HSSM_Autoloader {
|
||||
|
||||
/**
|
||||
* Initialize the autoloader
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function init() {
|
||||
spl_autoload_register( array( __CLASS__, 'autoload' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Autoload classes
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param string $class_name The class name to load
|
||||
*/
|
||||
public static function autoload( $class_name ) {
|
||||
// Only autoload classes that start with HSSM_
|
||||
if ( strpos( $class_name, 'HSSM_' ) !== 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert class name to file path
|
||||
$file_name = str_replace( '_', '-', strtolower( $class_name ) ) . '.php';
|
||||
$file_path = HSSM_PLUGIN_DIR . 'includes/class-' . $file_name;
|
||||
|
||||
// Include the file if it exists
|
||||
if ( file_exists( $file_path ) ) {
|
||||
require_once $file_path;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,520 @@
|
||||
<?php
|
||||
/**
|
||||
* HSSM Main Class
|
||||
*
|
||||
* Main plugin class that orchestrates the Hi-School Slide Manager functionality
|
||||
*
|
||||
* @package Hi_School_Slide_Manager
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 'Direct access forbidden.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* HSSM Main Class
|
||||
*
|
||||
* Core plugin functionality and initialization
|
||||
*/
|
||||
class HSSM_Main {
|
||||
|
||||
/**
|
||||
* Plugin instance
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var HSSM_Main
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Slides CPT manager
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var HSSM_Slides_CPT
|
||||
*/
|
||||
public $slides_cpt;
|
||||
|
||||
/**
|
||||
* REST API manager
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var HSSM_REST_API
|
||||
*/
|
||||
public $rest_api;
|
||||
|
||||
/**
|
||||
* Frontend manager
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var HSSM_Frontend
|
||||
*/
|
||||
public $frontend;
|
||||
|
||||
/**
|
||||
* Shortcode manager
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @var HSSM_Shortcode
|
||||
*/
|
||||
public $shortcode;
|
||||
|
||||
/**
|
||||
* Admin manager
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var HSSM_Admin
|
||||
*/
|
||||
public $admin;
|
||||
|
||||
/**
|
||||
* Email notifications manager
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var HSSM_Email_Notifications
|
||||
*/
|
||||
public $email_notifications;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function __construct() {
|
||||
// Private constructor to enforce singleton
|
||||
}
|
||||
|
||||
/**
|
||||
* Get singleton instance
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @return HSSM_Main
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the plugin
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function init() {
|
||||
// Initialize core components
|
||||
$this->init_components();
|
||||
|
||||
// Hook into WordPress
|
||||
$this->init_hooks();
|
||||
|
||||
// Load text domain for translations
|
||||
$this->load_textdomain();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize plugin components
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function init_components() {
|
||||
// Manually require class files (since they don't follow exact autoloader pattern)
|
||||
require_once HSSM_PLUGIN_DIR . 'includes/class-hssm-slides-cpt.php';
|
||||
require_once HSSM_PLUGIN_DIR . 'includes/class-hssm-rest-api.php';
|
||||
require_once HSSM_PLUGIN_DIR . 'includes/class-hssm-admin.php';
|
||||
require_once HSSM_PLUGIN_DIR . 'includes/class-hssm-frontend.php';
|
||||
require_once HSSM_PLUGIN_DIR . 'includes/class-hssm-notifications.php';
|
||||
require_once HSSM_PLUGIN_DIR . 'includes/class-hssm-api-client.php';
|
||||
require_once HSSM_PLUGIN_DIR . 'includes/class-hssm-slider.php';
|
||||
require_once HSSM_PLUGIN_DIR . 'includes/class-hssm-shortcode.php';
|
||||
|
||||
// Initialize slides custom post type
|
||||
$this->slides_cpt = new HSSM_Slides_CPT();
|
||||
|
||||
// Initialize REST API endpoints
|
||||
$this->rest_api = new HSSM_REST_API();
|
||||
|
||||
// Initialize admin functionality
|
||||
$this->admin = new HSSM_Admin();
|
||||
|
||||
// Initialize frontend functionality
|
||||
$this->frontend = new HSSM_Frontend();
|
||||
|
||||
// Initialize shortcode
|
||||
$this->shortcode = new HSSM_Shortcode();
|
||||
|
||||
// Initialize email notifications
|
||||
$this->email_notifications = new HSSM_Notifications();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize WordPress hooks
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function init_hooks() {
|
||||
$plugin_mode = get_option( 'hssm_plugin_mode', 'manager' );
|
||||
|
||||
// Core WordPress hooks
|
||||
if ( 'manager' === $plugin_mode ) {
|
||||
add_action( 'init', array( $this, 'register_post_types' ) );
|
||||
add_action( 'init', array( $this, 'register_taxonomies' ) );
|
||||
add_action( 'rest_api_init', array( $this, 'register_rest_routes' ) );
|
||||
}
|
||||
|
||||
// Frontend hooks
|
||||
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_assets' ), 20 );
|
||||
add_action( 'template_redirect', array( $this, 'handle_frontend_pages' ) );
|
||||
|
||||
// Admin hooks
|
||||
add_action( 'admin_init', array( $this, 'admin_init' ) );
|
||||
add_action( 'admin_menu', array( $this, 'add_admin_menus' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
|
||||
|
||||
// AJAX hooks for authenticated users
|
||||
// Note: Main AJAX handlers are in HSSM_Frontend class
|
||||
add_action( 'wp_ajax_hssm_upload_image', array( $this, 'ajax_upload_image' ) );
|
||||
add_action( 'wp_ajax_hssm_preview_slides', array( $this, 'ajax_preview_slides' ) );
|
||||
add_action( 'wp_ajax_hssm_send_preview_email', array( $this, 'ajax_send_preview_email' ) );
|
||||
|
||||
// Cron hooks for scheduled slides
|
||||
add_action( 'hssm_check_scheduled_slides', array( $this, 'check_scheduled_slides' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load plugin text domain
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function load_textdomain() {
|
||||
load_plugin_textdomain(
|
||||
'hi-school-slide-manager',
|
||||
false,
|
||||
dirname( HSSM_PLUGIN_BASENAME ) . '/languages'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register custom post types
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function register_post_types() {
|
||||
if ( $this->slides_cpt ) {
|
||||
$this->slides_cpt->register();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register custom taxonomies
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function register_taxonomies() {
|
||||
if ( $this->slides_cpt ) {
|
||||
$this->slides_cpt->register_taxonomies();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register REST API routes
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function register_rest_routes() {
|
||||
if ( $this->rest_api ) {
|
||||
$this->rest_api->register_routes();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue frontend assets
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function enqueue_frontend_assets() {
|
||||
// Prevent slider assets from loading on admin-facing "frontend" pages
|
||||
if ( is_page_template('page-slide-dashboard.php') || is_page_template('page-slide-preview.php') ) {
|
||||
// These pages will load their own specific styles (admin-slides-dashboard.css) or rely on explicit shortcode enqueueing.
|
||||
return;
|
||||
}
|
||||
|
||||
// Enqueue slider assets globally so they are available for the shortcode on regular frontend pages
|
||||
wp_enqueue_style(
|
||||
'hssm-slider',
|
||||
HSSM_PLUGIN_URL . 'assets/css/slider.css',
|
||||
array(),
|
||||
HSSM_VERSION
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'hssm-slider',
|
||||
HSSM_PLUGIN_URL . 'assets/js/slider.js',
|
||||
array( 'jquery' ),
|
||||
HSSM_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
if ( $this->frontend ) {
|
||||
$this->frontend->enqueue_assets();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle frontend page routing
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function handle_frontend_pages() {
|
||||
if ( $this->frontend ) {
|
||||
$this->frontend->handle_page_routing();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize admin functionality
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function admin_init() {
|
||||
if ( $this->admin ) {
|
||||
$this->admin->init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add admin menus
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function add_admin_menus() {
|
||||
if ( $this->admin ) {
|
||||
$this->admin->add_menus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue admin assets
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param string $hook Current admin page hook
|
||||
*/
|
||||
public function enqueue_admin_assets( $hook ) {
|
||||
if ( $this->admin ) {
|
||||
$this->admin->enqueue_assets( $hook );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get companion sites configuration
|
||||
*
|
||||
* Centralized source of truth for companion sites
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @return array List of companion sites
|
||||
*/
|
||||
public static function get_companion_sites() {
|
||||
return array(
|
||||
array(
|
||||
'slug' => 'hi-school',
|
||||
'name' => 'Hi-School Pharmacy',
|
||||
'description' => 'Hi-School Pharmacy companion site'
|
||||
),
|
||||
array(
|
||||
'slug' => 'one-stop',
|
||||
'name' => 'One Stop Hardware',
|
||||
'description' => 'One Stop Hardware companion site'
|
||||
),
|
||||
array(
|
||||
'slug' => 'ace',
|
||||
'name' => 'Ace Hardware',
|
||||
'description' => 'Ace Hardware companion site'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for scheduled slides that need to be activated/deactivated
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function check_scheduled_slides() {
|
||||
if ( $this->slides_cpt ) {
|
||||
$this->slides_cpt->check_scheduled_slides();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin activation
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function activate() {
|
||||
// Set up cron jobs
|
||||
self::setup_cron_jobs();
|
||||
|
||||
// Flush rewrite rules
|
||||
flush_rewrite_rules();
|
||||
|
||||
// Set default options
|
||||
self::set_default_options();
|
||||
|
||||
// Create default taxonomy terms
|
||||
self::create_default_terms();
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin deactivation
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function deactivate() {
|
||||
// Clear cron jobs
|
||||
wp_clear_scheduled_hook( 'hssm_check_scheduled_slides' );
|
||||
|
||||
// Flush rewrite rules
|
||||
flush_rewrite_rules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin uninstall
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function uninstall() {
|
||||
// Remove plugin options
|
||||
self::remove_plugin_options();
|
||||
|
||||
// Remove user meta
|
||||
self::remove_user_meta();
|
||||
|
||||
// Remove taxonomy terms and slides
|
||||
self::remove_plugin_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default taxonomy terms for sites
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private static function create_default_terms() {
|
||||
// Load the CPT class manually during activation
|
||||
require_once HSSM_PLUGIN_DIR . 'includes/class-hssm-slides-cpt.php';
|
||||
|
||||
// Create a temporary instance to set up default terms
|
||||
$slides_cpt = new HSSM_Slides_CPT();
|
||||
|
||||
// Register the taxonomies first
|
||||
$slides_cpt->register_taxonomies();
|
||||
|
||||
// Default terms are created automatically in the register_taxonomies method
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup cron jobs
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private static function setup_cron_jobs() {
|
||||
if ( ! wp_next_scheduled( 'hssm_check_scheduled_slides' ) ) {
|
||||
wp_schedule_event( time(), 'hourly', 'hssm_check_scheduled_slides' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default plugin options
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private static function set_default_options() {
|
||||
$default_options = array(
|
||||
'hssm_plugin_mode' => 'manager',
|
||||
'hssm_api_url' => '',
|
||||
'hssm_client_site_slug' => '',
|
||||
'hssm_notification_emails' => array( get_option( 'admin_email' ) ),
|
||||
'hssm_default_slide_duration' => 30, // days
|
||||
'hssm_cache_duration' => 3600, // 1 hour
|
||||
'hssm_api_version' => 'v1',
|
||||
'hssm_preview_page_slug' => 'slide-preview',
|
||||
'hssm_manager_page_slug' => 'slide-manager',
|
||||
'hssm_api_keys' => array( wp_generate_password( 32, false ) ), // Generate initial API key
|
||||
);
|
||||
|
||||
foreach ( $default_options as $option_name => $option_value ) {
|
||||
if ( ! get_option( $option_name ) ) {
|
||||
add_option( $option_name, $option_value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove plugin options
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private static function remove_plugin_options() {
|
||||
$options = array(
|
||||
'hssm_notification_emails',
|
||||
'hssm_default_slide_duration',
|
||||
'hssm_cache_duration',
|
||||
'hssm_api_version',
|
||||
'hssm_preview_page_slug',
|
||||
'hssm_manager_page_slug',
|
||||
'hssm_api_keys',
|
||||
);
|
||||
|
||||
foreach ( $options as $option ) {
|
||||
delete_option( $option );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove user meta
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private static function remove_user_meta() {
|
||||
global $wpdb;
|
||||
|
||||
$wpdb->delete(
|
||||
$wpdb->usermeta,
|
||||
array( 'meta_key' => 'hssm_user_preferences' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove plugin data including slides and taxonomy terms
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private static function remove_plugin_data() {
|
||||
// Get all slide posts
|
||||
$slides = get_posts( array(
|
||||
'post_type' => 'hssm_slide',
|
||||
'numberposts' => -1,
|
||||
'post_status' => 'any'
|
||||
) );
|
||||
|
||||
// Delete all slides
|
||||
foreach ( $slides as $slide ) {
|
||||
wp_delete_post( $slide->ID, true );
|
||||
}
|
||||
|
||||
// Remove taxonomy terms
|
||||
$taxonomies = array( 'hssm_site', 'hssm_client' );
|
||||
foreach ( $taxonomies as $taxonomy ) {
|
||||
$terms = get_terms( array(
|
||||
'taxonomy' => $taxonomy,
|
||||
'hide_empty' => false,
|
||||
) );
|
||||
|
||||
if ( ! is_wp_error( $terms ) ) {
|
||||
foreach ( $terms as $term ) {
|
||||
wp_delete_term( $term->term_id, $taxonomy );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
<?php
|
||||
/**
|
||||
* HSSM Notifications Class
|
||||
*
|
||||
* Handles all email notifications for the slide manager
|
||||
*
|
||||
* @package Hi_School_Slide_Manager
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 'Direct access forbidden.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* HSSM Notifications Class
|
||||
*/
|
||||
class HSSM_Notifications {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct() {
|
||||
// We'll hook into actions that trigger notifications
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notification settings
|
||||
*
|
||||
* @return array Array of email addresses for different roles
|
||||
*/
|
||||
public static function get_settings() {
|
||||
$defaults = array(
|
||||
'editor_emails' => '',
|
||||
'itc_emails' => ''
|
||||
);
|
||||
|
||||
$settings = get_option( 'hssm_notification_settings', $defaults );
|
||||
|
||||
// Ensure we have an array and it contains our expected keys
|
||||
if ( ! is_array( $settings ) ) {
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
return wp_parse_args( $settings, $defaults );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save notification settings
|
||||
*
|
||||
* @param array $settings Array of settings
|
||||
* @return bool True if saved successfully
|
||||
*/
|
||||
public static function save_settings( $settings ) {
|
||||
$clean_settings = array(
|
||||
'editor_emails' => sanitize_textarea_field( $settings['editor_emails'] ),
|
||||
'itc_emails' => sanitize_textarea_field( $settings['itc_emails'] )
|
||||
);
|
||||
|
||||
return update_option( 'hssm_notification_settings', $clean_settings );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get emails as array from string
|
||||
*
|
||||
* @param string $emails_string Comma or newline separated emails
|
||||
* @return array Array of valid email addresses
|
||||
*/
|
||||
private function get_emails_array( $emails_string ) {
|
||||
if ( empty( $emails_string ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Replace newlines with commas
|
||||
$emails_string = str_replace( array( "\r", "\n" ), ',', $emails_string );
|
||||
$emails = explode( ',', $emails_string );
|
||||
|
||||
$clean_emails = array();
|
||||
foreach ( $emails as $email ) {
|
||||
$email = sanitize_email( trim( $email ) );
|
||||
if ( is_email( $email ) ) {
|
||||
$clean_emails[] = $email;
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique( $clean_emails );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send notification to Editor when slide is Scheduled or Published Immediately
|
||||
*
|
||||
* @param int $slide_id Slide ID
|
||||
* @param string $status 'scheduled' or 'published'
|
||||
*/
|
||||
public function notify_editor_slide_created( $slide_id, $status ) {
|
||||
$settings = self::get_settings();
|
||||
$emails = $this->get_emails_array( $settings['editor_emails'] );
|
||||
|
||||
if ( empty( $emails ) ) {
|
||||
error_log( 'HSSM: No Editor emails configured for notification.' );
|
||||
return;
|
||||
}
|
||||
|
||||
$slide = get_post( $slide_id );
|
||||
if ( ! $slide ) return;
|
||||
|
||||
$target_sites = wp_get_post_terms( $slide_id, 'hssm_site', array( 'fields' => 'slugs' ) );
|
||||
$site_slugs = ! empty( $target_sites ) ? implode( ',', $target_sites ) : '';
|
||||
|
||||
// Determine publish date
|
||||
$publish_date = ( $status === 'scheduled' )
|
||||
? get_post_meta( $slide_id, '_hssm_start_date', true )
|
||||
: current_time( 'Y-m-d' );
|
||||
|
||||
// Construct preview link
|
||||
// page-slide-preview.php logic usually takes 'date' and 'site' params
|
||||
// We'll link to the preview page. Assuming there is a page with the template, or we use a custom URL structure if defined.
|
||||
// The instructions mentioned: "generated link to the slide-preview page with params containing the slide-title, the publish-date... and the site slugs"
|
||||
|
||||
$preview_page_url = home_url( '/slide-preview/' ); // Basic assumption, adjust if needed based on setup
|
||||
// Better: find the page using the template
|
||||
$pages = get_pages(array(
|
||||
'meta_key' => '_wp_page_template',
|
||||
'meta_value' => 'page-slide-preview.php'
|
||||
));
|
||||
|
||||
if ( ! empty( $pages ) ) {
|
||||
$preview_page_url = get_permalink( $pages[0]->ID );
|
||||
}
|
||||
|
||||
$preview_link = add_query_arg( array(
|
||||
'preview_title' => urlencode( $slide->post_title ),
|
||||
'date' => $publish_date,
|
||||
'sites' => $site_slugs,
|
||||
'preview_mode' => 'editor_check'
|
||||
), $preview_page_url );
|
||||
|
||||
$subject = sprintf( 'New Slide %s: "%s"', ucfirst( $status ), $slide->post_title );
|
||||
|
||||
$message = "A new slide has been " . ( $status === 'scheduled' ? 'Scheduled' : 'Published Immediately' ) . ".\n\n";
|
||||
$message .= "Title: " . $slide->post_title . "\n";
|
||||
$message .= "Target Date: " . $publish_date . "\n";
|
||||
$message .= "Target Sites: " . implode( ', ', $target_sites ) . "\n\n";
|
||||
$message .= "Preview Link: " . $preview_link . "\n\n";
|
||||
$message .= "Please review the slide.";
|
||||
|
||||
foreach ( $emails as $email ) {
|
||||
wp_mail( $email, $subject, $message );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send notification to Creator (Author) when slide is Approved
|
||||
*
|
||||
* @param int $slide_id Slide ID
|
||||
*/
|
||||
public function notify_creator_slide_approved( $slide_id ) {
|
||||
$slide = get_post( $slide_id );
|
||||
if ( ! $slide ) return;
|
||||
|
||||
$author_email = get_the_author_meta( 'user_email', $slide->post_author );
|
||||
|
||||
if ( ! is_email( $author_email ) ) return;
|
||||
|
||||
$subject = 'Your Slide Has Been Approved: "' . $slide->post_title . '"';
|
||||
|
||||
$message = "Good news! Your slide has been approved.\n\n";
|
||||
$message .= "Title: " . $slide->post_title . "\n";
|
||||
$message .= "It is now scheduled/published according to your settings.";
|
||||
|
||||
wp_mail( $author_email, $subject, $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send notification to ITCGuys when slide goes LIVE
|
||||
*
|
||||
* @param int $slide_id Slide ID
|
||||
*/
|
||||
public function notify_itc_slide_live( $slide_id ) {
|
||||
$settings = self::get_settings();
|
||||
$emails = $this->get_emails_array( $settings['itc_emails'] );
|
||||
|
||||
if ( empty( $emails ) ) return;
|
||||
|
||||
$slide = get_post( $slide_id );
|
||||
if ( ! $slide ) return;
|
||||
|
||||
// Generate Confirmation Link
|
||||
// This link needs to trigger the "Slide is Live" confirmation page
|
||||
// We probably need a specialized page or endpoint for this.
|
||||
// Let's assume we use the Dashboard page with a specific action param or a separate page.
|
||||
// User requested: "We need a page 'Slide is Live' ... with a green confirmation button"
|
||||
// Let's use the preview page or dashboard page with a specific query arg to show this UI.
|
||||
// Or better, a dedicated endpoint. For now, let's point to the dashboard with a "confirm_live" param.
|
||||
|
||||
$confirm_url = home_url( '/slide-live-confirmation/' ); // Ideally we create this page or rewrite rule
|
||||
// For now, let's use the dashboard url with arguments, and we'll handle the UI there or create a rewrite.
|
||||
// Actually, creating a specific page 'Slide is Live' was requested.
|
||||
// I'll create a new template `page-slide-confirmation.php` later.
|
||||
|
||||
// Find confirmation page if exists, or construct URL
|
||||
$pages = get_pages(array(
|
||||
'meta_key' => '_wp_page_template',
|
||||
'meta_value' => 'page-slide-confirmation.php'
|
||||
));
|
||||
$confirmation_page_url = !empty($pages) ? get_permalink($pages[0]->ID) : home_url('/?hssm_action=confirm_live');
|
||||
|
||||
$confirm_link = add_query_arg( array(
|
||||
'slide_id' => $slide_id,
|
||||
'token' => wp_create_nonce( 'hssm_confirm_live_' . $slide_id ) // Note: Nonces depend on user session, might be tricky for email links if user isn't logged in.
|
||||
// Better to use a hash that doesn't expire or relies on a shared secret if ITC guys are not logged in.
|
||||
// Assuming ITC guys are logged in users for now or we just use a hash.
|
||||
), $confirmation_page_url );
|
||||
|
||||
// Since email links are clicked outside sessions often, maybe just a hash of slide ID + secret salt.
|
||||
$token = hash( 'sha256', $slide_id . 'hssm_secret_salt' );
|
||||
$confirm_link = add_query_arg( array(
|
||||
'slide_id' => $slide_id,
|
||||
'hssm_token' => $token
|
||||
), $confirmation_page_url );
|
||||
|
||||
|
||||
$target_sites = wp_get_post_terms( $slide_id, 'hssm_site', array( 'fields' => 'slugs' ) );
|
||||
|
||||
$subject = 'ACTION REQUIRED: Slide is LIVE - "' . $slide->post_title . '"';
|
||||
|
||||
$message = "A slide has just gone LIVE.\n\n";
|
||||
$message .= "Title: " . $slide->post_title . "\n";
|
||||
$message .= "Sites: " . implode( ', ', $target_sites ) . "\n\n";
|
||||
$message .= "Please check the sites to ensure it is displaying correctly.\n";
|
||||
$message .= "Once verified, click the link below to confirm to the Editor and Author:\n\n";
|
||||
$message .= $confirm_link;
|
||||
|
||||
foreach ( $emails as $email ) {
|
||||
wp_mail( $email, $subject, $message );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send FINAL notification to Editor and Author (triggered by ITC confirmation)
|
||||
*
|
||||
* @param int $slide_id Slide ID
|
||||
*/
|
||||
public function notify_all_confirmed_live( $slide_id ) {
|
||||
$slide = get_post( $slide_id );
|
||||
if ( ! $slide ) return;
|
||||
|
||||
// Editor Emails
|
||||
$settings = self::get_settings();
|
||||
$editor_emails = $this->get_emails_array( $settings['editor_emails'] );
|
||||
|
||||
// Author Email
|
||||
$author_email = get_the_author_meta( 'user_email', $slide->post_author );
|
||||
|
||||
$recipients = array_merge( $editor_emails, array( $author_email ) );
|
||||
$recipients = array_unique( array_filter( $recipients ) );
|
||||
|
||||
if ( empty( $recipients ) ) return;
|
||||
|
||||
$subject = 'CONFIRMED: Slide is Live and Verified - "' . $slide->post_title . '"';
|
||||
|
||||
$message = "The slide has been verified as LIVE by the ITC team.\n\n";
|
||||
$message .= "Title: " . $slide->post_title . "\n";
|
||||
$message .= "Status: Live & Verified\n\n";
|
||||
$message .= "You can view the slide on the live sites.";
|
||||
|
||||
foreach ( $recipients as $email ) {
|
||||
wp_mail( $email, $subject, $message );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,532 @@
|
||||
<?php
|
||||
/**
|
||||
* HSSM REST API Class
|
||||
*
|
||||
* Handles REST API endpoints for companion sites to fetch slides
|
||||
*
|
||||
* @package Hi_School_Slide_Manager
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 'Direct access forbidden.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* HSSM REST API Class
|
||||
*
|
||||
* Custom REST API endpoints for slide distribution to companion sites
|
||||
*/
|
||||
class HSSM_REST_API {
|
||||
|
||||
/**
|
||||
* API namespace
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var string
|
||||
*/
|
||||
const NAMESPACE = 'hssm/v1';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct() {
|
||||
// Hook is registered in main class
|
||||
}
|
||||
|
||||
/**
|
||||
* Register REST API routes
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function register_routes() {
|
||||
// Get slides for a specific site
|
||||
register_rest_route( self::NAMESPACE, '/slides/(?P<site>[a-zA-Z0-9-]+)', array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_slides_for_site' ),
|
||||
'permission_callback' => array( $this, 'check_api_permissions' ),
|
||||
'args' => array(
|
||||
'site' => array(
|
||||
'required' => true,
|
||||
'validate_callback' => array( $this, 'validate_site_slug' ),
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'description' => 'Site slug (hi-school, one-stop, ace)',
|
||||
),
|
||||
'date' => array(
|
||||
'required' => false,
|
||||
'validate_callback' => array( $this, 'validate_date' ),
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'description' => 'Date to get slides for (YYYY-MM-DD format)',
|
||||
),
|
||||
'limit' => array(
|
||||
'required' => false,
|
||||
'default' => 10,
|
||||
'validate_callback' => array( $this, 'validate_limit' ),
|
||||
'sanitize_callback' => 'absint',
|
||||
'description' => 'Maximum number of slides to return (1-50)',
|
||||
),
|
||||
'status' => array(
|
||||
'required' => false,
|
||||
'default' => 'active',
|
||||
'validate_callback' => array( $this, 'validate_status' ),
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'description' => 'Slide status: active, scheduled, all',
|
||||
),
|
||||
),
|
||||
) );
|
||||
|
||||
// Get all available sites
|
||||
register_rest_route( self::NAMESPACE, '/sites', array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_available_sites' ),
|
||||
'permission_callback' => array( $this, 'check_api_permissions' ),
|
||||
) );
|
||||
|
||||
// Get slide by ID
|
||||
register_rest_route( self::NAMESPACE, '/slide/(?P<id>\d+)', array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_slide_by_id' ),
|
||||
'permission_callback' => array( $this, 'check_api_permissions' ),
|
||||
'args' => array(
|
||||
'id' => array(
|
||||
'required' => true,
|
||||
'validate_callback' => array( $this, 'validate_slide_id' ),
|
||||
'sanitize_callback' => 'absint',
|
||||
'description' => 'Slide ID',
|
||||
),
|
||||
),
|
||||
) );
|
||||
|
||||
// Health check endpoint
|
||||
register_rest_route( self::NAMESPACE, '/health', array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'health_check' ),
|
||||
'permission_callback' => '__return_true', // Public endpoint
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get slides for a specific site
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param WP_REST_Request $request Request object
|
||||
* @return WP_REST_Response|WP_Error Response object
|
||||
*/
|
||||
public function get_slides_for_site( $request ) {
|
||||
$site_slug = $request->get_param( 'site' );
|
||||
$date = $request->get_param( 'date' );
|
||||
$limit = $request->get_param( 'limit' );
|
||||
$status = $request->get_param( 'status' );
|
||||
|
||||
// Get the site term
|
||||
$site_term = get_term_by( 'slug', $site_slug, 'hssm_site' );
|
||||
if ( ! $site_term ) {
|
||||
return new WP_Error( 'invalid_site', 'Invalid site slug', array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
// Build query args
|
||||
$query_args = array(
|
||||
'post_type' => 'hssm_slide',
|
||||
'posts_per_page' => $limit,
|
||||
'post_status' => 'publish',
|
||||
'meta_key' => '_hssm_order',
|
||||
'orderby' => 'meta_value_num',
|
||||
'order' => 'ASC',
|
||||
'tax_query' => array(
|
||||
array(
|
||||
'taxonomy' => 'hssm_site',
|
||||
'field' => 'term_id',
|
||||
'terms' => $site_term->term_id,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Add date filtering if specified
|
||||
if ( $date ) {
|
||||
$target_date = $date . ' 23:59:59';
|
||||
$query_args['meta_query'] = array(
|
||||
'relation' => 'AND',
|
||||
array(
|
||||
'relation' => 'OR',
|
||||
array(
|
||||
'key' => '_hssm_start_date',
|
||||
'value' => '',
|
||||
'compare' => '='
|
||||
),
|
||||
array(
|
||||
'key' => '_hssm_start_date',
|
||||
'value' => $target_date,
|
||||
'compare' => '<='
|
||||
),
|
||||
array(
|
||||
'key' => '_hssm_start_date',
|
||||
'compare' => 'NOT EXISTS'
|
||||
)
|
||||
),
|
||||
array(
|
||||
'relation' => 'OR',
|
||||
array(
|
||||
'key' => '_hssm_end_date',
|
||||
'value' => '',
|
||||
'compare' => '='
|
||||
),
|
||||
array(
|
||||
'key' => '_hssm_end_date',
|
||||
'value' => $target_date,
|
||||
'compare' => '>='
|
||||
),
|
||||
array(
|
||||
'key' => '_hssm_end_date',
|
||||
'compare' => 'NOT EXISTS'
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Handle status filtering
|
||||
if ( $status === 'scheduled' ) {
|
||||
$current_time = current_time( 'mysql' );
|
||||
$query_args['meta_query'] = array(
|
||||
array(
|
||||
'key' => '_hssm_start_date',
|
||||
'value' => $current_time,
|
||||
'compare' => '>'
|
||||
)
|
||||
);
|
||||
} elseif ( $status === 'all' ) {
|
||||
$query_args['post_status'] = array( 'publish', 'future', 'draft' );
|
||||
}
|
||||
|
||||
$slides_query = new WP_Query( $query_args );
|
||||
$slides_data = array();
|
||||
|
||||
if ( $slides_query->have_posts() ) {
|
||||
while ( $slides_query->have_posts() ) {
|
||||
$slides_query->the_post();
|
||||
$slides_data[] = $this->format_slide_data( get_post() );
|
||||
}
|
||||
wp_reset_postdata();
|
||||
}
|
||||
|
||||
$response_data = array(
|
||||
'slides' => $slides_data,
|
||||
'site' => array(
|
||||
'slug' => $site_slug,
|
||||
'name' => $site_term->name,
|
||||
'description' => $site_term->description,
|
||||
),
|
||||
'meta' => array(
|
||||
'total_slides' => count( $slides_data ),
|
||||
'query_date' => $date ?: current_time( 'Y-m-d' ),
|
||||
'status_filter' => $status,
|
||||
'timestamp' => current_time( 'c' ),
|
||||
'cache_duration' => get_option( 'hssm_cache_duration', 3600 ),
|
||||
),
|
||||
);
|
||||
|
||||
return rest_ensure_response( $response_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available sites
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param WP_REST_Request $request Request object
|
||||
* @return WP_REST_Response Response object
|
||||
*/
|
||||
public function get_available_sites( $request ) {
|
||||
$sites = get_terms( array(
|
||||
'taxonomy' => 'hssm_site',
|
||||
'hide_empty' => false,
|
||||
) );
|
||||
|
||||
$sites_data = array();
|
||||
if ( ! is_wp_error( $sites ) ) {
|
||||
foreach ( $sites as $site ) {
|
||||
$sites_data[] = array(
|
||||
'slug' => $site->slug,
|
||||
'name' => $site->name,
|
||||
'description' => $site->description,
|
||||
'slide_count' => $this->get_site_slide_count( $site->term_id ),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return rest_ensure_response( array(
|
||||
'sites' => $sites_data,
|
||||
'total' => count( $sites_data ),
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get slide by ID
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param WP_REST_Request $request Request object
|
||||
* @return WP_REST_Response|WP_Error Response object
|
||||
*/
|
||||
public function get_slide_by_id( $request ) {
|
||||
$slide_id = $request->get_param( 'id' );
|
||||
$slide = get_post( $slide_id );
|
||||
|
||||
if ( ! $slide || $slide->post_type !== 'hssm_slide' ) {
|
||||
return new WP_Error( 'slide_not_found', 'Slide not found', array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $this->format_slide_data( $slide ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Health check endpoint
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param WP_REST_Request $request Request object
|
||||
* @return WP_REST_Response Response object
|
||||
*/
|
||||
public function health_check( $request ) {
|
||||
return rest_ensure_response( array(
|
||||
'status' => 'healthy',
|
||||
'plugin' => 'Hi-School Slide Manager',
|
||||
'version' => HSSM_VERSION,
|
||||
'timestamp' => current_time( 'c' ),
|
||||
'endpoints' => array(
|
||||
'slides' => rest_url( self::NAMESPACE . '/slides/{site}' ),
|
||||
'sites' => rest_url( self::NAMESPACE . '/sites' ),
|
||||
'slide' => rest_url( self::NAMESPACE . '/slide/{id}' ),
|
||||
),
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check API permissions
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param WP_REST_Request $request Request object
|
||||
* @return bool|WP_Error Permission check result
|
||||
*/
|
||||
public function check_api_permissions( $request ) {
|
||||
// Get the API key from header or query parameter
|
||||
$api_key = $request->get_header( 'X-HSSM-API-Key' );
|
||||
if ( ! $api_key ) {
|
||||
$api_key = $request->get_param( 'api_key' );
|
||||
}
|
||||
|
||||
if ( ! $api_key ) {
|
||||
return new WP_Error( 'missing_api_key', 'API key required', array( 'status' => 401 ) );
|
||||
}
|
||||
|
||||
// Validate API key
|
||||
$valid_keys = get_option( 'hssm_api_keys', array() );
|
||||
if ( ! in_array( $api_key, $valid_keys ) ) {
|
||||
return new WP_Error( 'invalid_api_key', 'Invalid API key', array( 'status' => 403 ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format slide data for API response
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param WP_Post $slide Slide post object
|
||||
* @return array Formatted slide data
|
||||
*/
|
||||
private function format_slide_data( $slide ) {
|
||||
$slide_data = array(
|
||||
'id' => $slide->ID,
|
||||
'title' => $slide->post_title,
|
||||
'subtitle' => get_post_meta( $slide->ID, '_hssm_subtitle', true ),
|
||||
'content' => $slide->post_content,
|
||||
'excerpt' => $slide->post_excerpt,
|
||||
'buttons' => get_post_meta( $slide->ID, '_hssm_buttons', true ) ?: array(),
|
||||
'order' => (int) get_post_meta( $slide->ID, '_hssm_order', true ),
|
||||
'background_image' => $this->get_slide_background_image( $slide->ID ),
|
||||
'schedule' => array(
|
||||
'start_date' => get_post_meta( $slide->ID, '_hssm_start_date', true ),
|
||||
'end_date' => get_post_meta( $slide->ID, '_hssm_end_date', true ),
|
||||
'auto_publish' => get_post_meta( $slide->ID, '_hssm_auto_publish', true ) === '1',
|
||||
'auto_unpublish' => get_post_meta( $slide->ID, '_hssm_auto_unpublish', true ) === '1',
|
||||
),
|
||||
'target_sites' => $this->get_slide_target_sites( $slide->ID ),
|
||||
'client' => $this->get_slide_client( $slide->ID ),
|
||||
'status' => $slide->post_status,
|
||||
'created' => $slide->post_date,
|
||||
'modified' => $slide->post_modified,
|
||||
);
|
||||
|
||||
return $slide_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get slide background image data
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param int $slide_id Slide ID
|
||||
* @return array|null Background image data
|
||||
*/
|
||||
private function get_slide_background_image( $slide_id ) {
|
||||
$thumbnail_id = get_post_thumbnail_id( $slide_id );
|
||||
if ( ! $thumbnail_id ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$image_data = wp_get_attachment_image_src( $thumbnail_id, 'full' );
|
||||
if ( ! $image_data ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array(
|
||||
'id' => $thumbnail_id,
|
||||
'url' => $image_data[0],
|
||||
'width' => $image_data[1],
|
||||
'height' => $image_data[2],
|
||||
'alt' => get_post_meta( $thumbnail_id, '_wp_attachment_image_alt', true ),
|
||||
'sizes' => array(
|
||||
'full' => wp_get_attachment_image_src( $thumbnail_id, 'full' )[0],
|
||||
'large' => wp_get_attachment_image_src( $thumbnail_id, 'large' )[0],
|
||||
'medium' => wp_get_attachment_image_src( $thumbnail_id, 'medium' )[0],
|
||||
'thumbnail' => wp_get_attachment_image_src( $thumbnail_id, 'thumbnail' )[0],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get slide target sites
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param int $slide_id Slide ID
|
||||
* @return array Target sites data
|
||||
*/
|
||||
private function get_slide_target_sites( $slide_id ) {
|
||||
$sites = wp_get_post_terms( $slide_id, 'hssm_site' );
|
||||
$sites_data = array();
|
||||
|
||||
if ( ! is_wp_error( $sites ) ) {
|
||||
foreach ( $sites as $site ) {
|
||||
$sites_data[] = array(
|
||||
'slug' => $site->slug,
|
||||
'name' => $site->name,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $sites_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get slide client
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param int $slide_id Slide ID
|
||||
* @return array|null Client data
|
||||
*/
|
||||
private function get_slide_client( $slide_id ) {
|
||||
$clients = wp_get_post_terms( $slide_id, 'hssm_client' );
|
||||
|
||||
if ( ! is_wp_error( $clients ) && ! empty( $clients ) ) {
|
||||
$client = $clients[0];
|
||||
return array(
|
||||
'slug' => $client->slug,
|
||||
'name' => $client->name,
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get slide count for a site
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param int $site_term_id Site term ID
|
||||
* @return int Slide count
|
||||
*/
|
||||
private function get_site_slide_count( $site_term_id ) {
|
||||
$query = new WP_Query( array(
|
||||
'post_type' => 'hssm_slide',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids',
|
||||
'tax_query' => array(
|
||||
array(
|
||||
'taxonomy' => 'hssm_site',
|
||||
'field' => 'term_id',
|
||||
'terms' => $site_term_id,
|
||||
),
|
||||
),
|
||||
) );
|
||||
|
||||
return $query->found_posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate site slug parameter
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param string $value Site slug
|
||||
* @param WP_REST_Request $request Request object
|
||||
* @param string $param Parameter name
|
||||
* @return bool Validation result
|
||||
*/
|
||||
public function validate_site_slug( $value, $request, $param ) {
|
||||
$companion_sites = HSSM_Main::get_companion_sites();
|
||||
$valid_sites = wp_list_pluck( $companion_sites, 'slug' );
|
||||
return in_array( $value, $valid_sites );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate date parameter
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param string $value Date string
|
||||
* @param WP_REST_Request $request Request object
|
||||
* @param string $param Parameter name
|
||||
* @return bool Validation result
|
||||
*/
|
||||
public function validate_date( $value, $request, $param ) {
|
||||
return (bool) strtotime( $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate limit parameter
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param int $value Limit value
|
||||
* @param WP_REST_Request $request Request object
|
||||
* @param string $param Parameter name
|
||||
* @return bool Validation result
|
||||
*/
|
||||
public function validate_limit( $value, $request, $param ) {
|
||||
return is_numeric( $value ) && $value >= 1 && $value <= 50;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate status parameter
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param string $value Status value
|
||||
* @param WP_REST_Request $request Request object
|
||||
* @param string $param Parameter name
|
||||
* @return bool Validation result
|
||||
*/
|
||||
public function validate_status( $value, $request, $param ) {
|
||||
$valid_statuses = array( 'active', 'scheduled', 'all' );
|
||||
return in_array( $value, $valid_statuses );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate slide ID parameter
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param int $value Slide ID
|
||||
* @param WP_REST_Request $request Request object
|
||||
* @param string $param Parameter name
|
||||
* @return bool Validation result
|
||||
*/
|
||||
public function validate_slide_id( $value, $request, $param ) {
|
||||
return is_numeric( $value ) && $value > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/**
|
||||
* HSSM Shortcode Class
|
||||
*
|
||||
* Handles the [hssm_slider] shortcode
|
||||
*
|
||||
* @package Hi_School_Slide_Manager
|
||||
* @since 1.1.0
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 'Direct access forbidden.' );
|
||||
}
|
||||
|
||||
class HSSM_Shortcode {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public function __construct() {
|
||||
add_shortcode( 'hssm_slider', array( $this, 'render_shortcode' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the [hssm_slider] shortcode
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @param array $atts Shortcode attributes
|
||||
* @return string Shortcode output
|
||||
*/
|
||||
public function render_shortcode( $atts ) {
|
||||
$plugin_mode = get_option( 'hssm_plugin_mode', 'manager' );
|
||||
$slides = array();
|
||||
|
||||
if ( 'client' === $plugin_mode ) {
|
||||
// Fetch from remote API
|
||||
$api_client = new HSSM_API_Client();
|
||||
$result = $api_client->get_slides();
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
if ( current_user_can( 'manage_options' ) ) {
|
||||
return '<div class="hssm-error">' . sprintf( __( 'HSSM API Error: %s', 'hi-school-slide-manager' ), $result->get_error_message() ) . '</div>';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
$slides = $result;
|
||||
} else {
|
||||
// Manager mode - Fetch locally
|
||||
$site_slug = isset( $atts['site'] ) ? sanitize_text_field( $atts['site'] ) : get_option( 'hssm_client_site_slug', 'hi-school' );
|
||||
$date = isset( $atts['date'] ) ? sanitize_text_field( $atts['date'] ) : current_time( 'Y-m-d' );
|
||||
|
||||
// Build query args similar to what's in the REST API or Frontend class
|
||||
$args = array(
|
||||
'post_type' => 'hssm_slide',
|
||||
'posts_per_page' => isset( $atts['limit'] ) ? intval( $atts['limit'] ) : 10,
|
||||
'post_status' => array( 'publish', 'future', 'pending' ),
|
||||
'meta_key' => '_hssm_order',
|
||||
'orderby' => 'meta_value_num',
|
||||
'order' => 'ASC',
|
||||
);
|
||||
|
||||
if ( ! empty( $site_slug ) ) {
|
||||
$args['tax_query'] = array(
|
||||
array(
|
||||
'taxonomy' => 'hssm_site',
|
||||
'field' => 'slug',
|
||||
'terms' => $site_slug,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Add date filtering
|
||||
$target_date = $date . ' 23:59:59';
|
||||
$args['meta_query'] = array(
|
||||
'relation' => 'AND',
|
||||
array(
|
||||
'relation' => 'OR',
|
||||
array( 'key' => '_hssm_start_date', 'value' => '', 'compare' => '=' ),
|
||||
array( 'key' => '_hssm_start_date', 'value' => $target_date, 'compare' => '<=' ),
|
||||
array( 'key' => '_hssm_start_date', 'compare' => 'NOT EXISTS' )
|
||||
),
|
||||
array(
|
||||
'relation' => 'OR',
|
||||
array( 'key' => '_hssm_end_date', 'value' => '', 'compare' => '=' ),
|
||||
array( 'key' => '_hssm_end_date', 'value' => $target_date, 'compare' => '>=' ),
|
||||
array( 'key' => '_hssm_end_date', 'compare' => 'NOT EXISTS' )
|
||||
)
|
||||
);
|
||||
|
||||
$query = new WP_Query( $args );
|
||||
if ( $query->have_posts() ) {
|
||||
while ( $query->have_posts() ) {
|
||||
$query->the_post();
|
||||
$slide_id = get_the_ID();
|
||||
|
||||
// Format data to match what the API returns
|
||||
$slides[] = array(
|
||||
'id' => $slide_id,
|
||||
'title' => get_the_title(),
|
||||
'subtitle' => get_post_meta( $slide_id, '_hssm_subtitle', true ),
|
||||
'content' => get_the_content(),
|
||||
'buttons' => get_post_meta( $slide_id, '_hssm_buttons', true ),
|
||||
'featured_image' => get_the_post_thumbnail_url( $slide_id, 'full' ),
|
||||
'hide_all_text' => get_post_meta( $slide_id, '_hssm_hide_all_text', true ) === '1',
|
||||
);
|
||||
}
|
||||
wp_reset_postdata();
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $slides ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$slider = new HSSM_Slider();
|
||||
return $slider->render( $slides, $atts );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
/**
|
||||
* HSSM Slider Rendering Class
|
||||
*
|
||||
* Handles rendering of the slider HTML/CSS/JS based on provided slide data
|
||||
*
|
||||
* @package Hi_School_Slide_Manager
|
||||
* @since 1.1.0
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 'Direct access forbidden.' );
|
||||
}
|
||||
|
||||
class HSSM_Slider {
|
||||
|
||||
/**
|
||||
* Render the slider
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @param array $slides Array of slide data
|
||||
* @param array $atts Shortcode attributes or configuration
|
||||
* @return string Slider HTML
|
||||
*/
|
||||
public function render( $slides, $atts = array() ) {
|
||||
if ( empty( $slides ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Merge attributes
|
||||
$atts = shortcode_atts( array(
|
||||
'id' => 'hssm-slider-' . uniqid(),
|
||||
'class' => '',
|
||||
'autoplay' => 'true',
|
||||
'interval' => '5000',
|
||||
), $atts );
|
||||
|
||||
ob_start();
|
||||
$this->enqueue_assets();
|
||||
?>
|
||||
<div id="<?php echo esc_attr( $atts['id'] ); ?>" class="hssm-slider-container <?php echo esc_attr( $atts['class'] ); ?>" data-autoplay="<?php echo esc_attr( $atts['autoplay'] ); ?>" data-interval="<?php echo esc_attr( $atts['interval'] ); ?>">
|
||||
<div class="hssm-slider">
|
||||
<div class="hssm-viewport">
|
||||
<div class="hssm-track">
|
||||
<?php foreach ( $slides as $index => $slide ) : ?>
|
||||
<?php
|
||||
$bg_image = isset( $slide['background_image']['url'] ) ? $slide['background_image']['url'] : '';
|
||||
if ( empty( $bg_image ) && isset( $slide['featured_image'] ) ) {
|
||||
$bg_image = $slide['featured_image'];
|
||||
}
|
||||
|
||||
$hide_text = isset( $slide['hide_all_text'] ) && $slide['hide_all_text'];
|
||||
?>
|
||||
<div class="hssm-slide" data-index="<?php echo esc_attr( $index ); ?>">
|
||||
<div class="hssm-slide-inner <?php echo $hide_text ? 'hssm-hide-text' : ''; ?>" style="background-image: url('<?php echo esc_url( $bg_image ); ?>');">
|
||||
<div class="hssm-slide-content-wrapper">
|
||||
<div class="hssm-slide-content">
|
||||
<?php if ( ! empty( $slide['title'] ) ) : ?>
|
||||
<h1 class="hssm-slide-title"><span><?php echo esc_html( $slide['title'] ); ?></span></h1>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( ! empty( $slide['subtitle'] ) ) : ?>
|
||||
<h4 class="hssm-slide-subtitle"><span><?php echo esc_html( $slide['subtitle'] ); ?></span></h4>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( ! empty( $slide['content'] ) ) : ?>
|
||||
<div class="hssm-slide-description">
|
||||
<span><?php echo wp_kses_post( $slide['content'] ); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( ! empty( $slide['buttons'] ) && is_array( $slide['buttons'] ) ) : ?>
|
||||
<?php
|
||||
// Filter out empty buttons
|
||||
$valid_buttons = array_filter($slide['buttons'], function($b) {
|
||||
return !empty($b['text']) && !empty($b['url']);
|
||||
});
|
||||
?>
|
||||
<?php if ( ! empty( $valid_buttons ) ) : ?>
|
||||
<div class="hssm-button-container">
|
||||
<?php foreach ( $valid_buttons as $button ) : ?>
|
||||
<a href="<?php echo esc_url( $button['url'] ); ?>" class="hssm-button">
|
||||
<?php echo esc_html( $button['text'] ); ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ( count( $slides ) > 1 ) : ?>
|
||||
<button class="hssm-arrow hssm-arrow-prev" aria-label="<?php _e( 'Previous Slide', 'hi-school-slide-manager' ); ?>">
|
||||
<span class="hssm-arrow-icon"></span>
|
||||
</button>
|
||||
<button class="hssm-arrow hssm-arrow-next" aria-label="<?php _e( 'Next Slide', 'hi-school-slide-manager' ); ?>">
|
||||
<span class="hssm-arrow-icon"></span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue slider assets
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
private function enqueue_assets() {
|
||||
wp_enqueue_style(
|
||||
'hssm-slider',
|
||||
HSSM_PLUGIN_URL . 'assets/css/slider.css',
|
||||
array(),
|
||||
HSSM_VERSION
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'hssm-slider',
|
||||
HSSM_PLUGIN_URL . 'assets/js/slider.js',
|
||||
array( 'jquery' ),
|
||||
HSSM_VERSION,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,946 @@
|
||||
<?php
|
||||
/**
|
||||
* HSSM Slides Custom Post Type
|
||||
*
|
||||
* Manages the custom post type for slides with detailed fields for multi-site management
|
||||
*
|
||||
* @package Hi_School_Slide_Manager
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 'Direct access forbidden.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* HSSM Slides CPT Class
|
||||
*
|
||||
* Handles the slides custom post type and its meta fields
|
||||
*/
|
||||
class HSSM_Slides_CPT {
|
||||
|
||||
/**
|
||||
* Post type slug
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var string
|
||||
*/
|
||||
const POST_TYPE = 'hssm_slide';
|
||||
|
||||
/**
|
||||
* Site taxonomy slug
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var string
|
||||
*/
|
||||
const SITE_TAXONOMY = 'hssm_site';
|
||||
|
||||
/**
|
||||
* Client taxonomy slug
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var string
|
||||
*/
|
||||
const CLIENT_TAXONOMY = 'hssm_client';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
|
||||
add_action( 'save_post', array( $this, 'save_meta_fields' ) );
|
||||
add_filter( 'manage_' . self::POST_TYPE . '_posts_columns', array( $this, 'add_custom_columns' ) );
|
||||
add_action( 'manage_' . self::POST_TYPE . '_posts_custom_column', array( $this, 'custom_column_content' ), 10, 2 );
|
||||
|
||||
// Add drag-and-drop ordering functionality
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
|
||||
add_action( 'wp_ajax_hssm_update_slide_order', array( $this, 'ajax_update_slide_order' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the slides custom post type
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function register() {
|
||||
$labels = array(
|
||||
'name' => _x( 'Slides', 'Post type general name', 'hi-school-slide-manager' ),
|
||||
'singular_name' => _x( 'Slide', 'Post type singular name', 'hi-school-slide-manager' ),
|
||||
'menu_name' => _x( 'Slides', 'Admin Menu text', 'hi-school-slide-manager' ),
|
||||
'name_admin_bar' => _x( 'Slide', 'Add New on Toolbar', 'hi-school-slide-manager' ),
|
||||
'add_new' => __( 'Add New', 'hi-school-slide-manager' ),
|
||||
'add_new_item' => __( 'Add New Slide', 'hi-school-slide-manager' ),
|
||||
'new_item' => __( 'New Slide', 'hi-school-slide-manager' ),
|
||||
'edit_item' => __( 'Edit Slide', 'hi-school-slide-manager' ),
|
||||
'view_item' => __( 'View Slide', 'hi-school-slide-manager' ),
|
||||
'all_items' => __( 'All Slides', 'hi-school-slide-manager' ),
|
||||
'search_items' => __( 'Search Slides', 'hi-school-slide-manager' ),
|
||||
'parent_item_colon' => __( 'Parent Slides:', 'hi-school-slide-manager' ),
|
||||
'not_found' => __( 'No slides found.', 'hi-school-slide-manager' ),
|
||||
'not_found_in_trash' => __( 'No slides found in Trash.', 'hi-school-slide-manager' ),
|
||||
'featured_image' => _x( 'Background Image', 'Overrides the "Featured Image" phrase', 'hi-school-slide-manager' ),
|
||||
'set_featured_image' => _x( 'Set background image', 'Overrides the "Set featured image" phrase', 'hi-school-slide-manager' ),
|
||||
'remove_featured_image' => _x( 'Remove background image', 'Overrides the "Remove featured image" phrase', 'hi-school-slide-manager' ),
|
||||
'use_featured_image' => _x( 'Use as background image', 'Overrides the "Use as featured image" phrase', 'hi-school-slide-manager' ),
|
||||
'archives' => _x( 'Slide archives', 'The post type archive label used in nav menus', 'hi-school-slide-manager' ),
|
||||
'insert_into_item' => _x( 'Insert into slide', 'Overrides the "Insert into post" phrase', 'hi-school-slide-manager' ),
|
||||
'uploaded_to_this_item' => _x( 'Uploaded to this slide', 'Overrides the "Uploaded to this post" phrase', 'hi-school-slide-manager' ),
|
||||
'filter_items_list' => _x( 'Filter slides list', 'Screen reader text for the filter links', 'hi-school-slide-manager' ),
|
||||
'items_list_navigation' => _x( 'Slides list navigation', 'Screen reader text for the pagination', 'hi-school-slide-manager' ),
|
||||
'items_list' => _x( 'Slides list', 'Screen reader text for the items list', 'hi-school-slide-manager' ),
|
||||
);
|
||||
|
||||
$args = array(
|
||||
'labels' => $labels,
|
||||
'public' => true,
|
||||
'publicly_queryable' => true,
|
||||
'show_ui' => true,
|
||||
'show_in_menu' => true,
|
||||
'query_var' => true,
|
||||
'rewrite' => array( 'slug' => 'slide' ),
|
||||
'capability_type' => 'post',
|
||||
'has_archive' => true,
|
||||
'hierarchical' => false,
|
||||
'menu_position' => 20,
|
||||
'menu_icon' => 'dashicons-slides',
|
||||
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
|
||||
'show_in_rest' => true,
|
||||
'rest_base' => 'slides',
|
||||
'rest_controller_class' => 'HSSM_REST_Slides_Controller',
|
||||
);
|
||||
|
||||
register_post_type( self::POST_TYPE, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register custom taxonomies
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function register_taxonomies() {
|
||||
// Site taxonomy for organizing slides by target site
|
||||
$site_labels = array(
|
||||
'name' => _x( 'Target Sites', 'taxonomy general name', 'hi-school-slide-manager' ),
|
||||
'singular_name' => _x( 'Target Site', 'taxonomy singular name', 'hi-school-slide-manager' ),
|
||||
'search_items' => __( 'Search Sites', 'hi-school-slide-manager' ),
|
||||
'all_items' => __( 'All Sites', 'hi-school-slide-manager' ),
|
||||
'parent_item' => __( 'Parent Site', 'hi-school-slide-manager' ),
|
||||
'parent_item_colon' => __( 'Parent Site:', 'hi-school-slide-manager' ),
|
||||
'edit_item' => __( 'Edit Site', 'hi-school-slide-manager' ),
|
||||
'update_item' => __( 'Update Site', 'hi-school-slide-manager' ),
|
||||
'add_new_item' => __( 'Add New Site', 'hi-school-slide-manager' ),
|
||||
'new_item_name' => __( 'New Site Name', 'hi-school-slide-manager' ),
|
||||
'menu_name' => __( 'Target Sites', 'hi-school-slide-manager' ),
|
||||
);
|
||||
|
||||
$site_args = array(
|
||||
'hierarchical' => false,
|
||||
'labels' => $site_labels,
|
||||
'show_ui' => true,
|
||||
'show_admin_column' => true,
|
||||
'query_var' => true,
|
||||
'rewrite' => array( 'slug' => 'slide-site' ),
|
||||
'show_in_rest' => true,
|
||||
);
|
||||
|
||||
register_taxonomy( self::SITE_TAXONOMY, array( self::POST_TYPE ), $site_args );
|
||||
|
||||
// Client taxonomy for organizing slides by client (optional)
|
||||
$client_labels = array(
|
||||
'name' => _x( 'Clients', 'taxonomy general name', 'hi-school-slide-manager' ),
|
||||
'singular_name' => _x( 'Client', 'taxonomy singular name', 'hi-school-slide-manager' ),
|
||||
'search_items' => __( 'Search Clients', 'hi-school-slide-manager' ),
|
||||
'all_items' => __( 'All Clients', 'hi-school-slide-manager' ),
|
||||
'parent_item' => __( 'Parent Client', 'hi-school-slide-manager' ),
|
||||
'parent_item_colon' => __( 'Parent Client:', 'hi-school-slide-manager' ),
|
||||
'edit_item' => __( 'Edit Client', 'hi-school-slide-manager' ),
|
||||
'update_item' => __( 'Update Client', 'hi-school-slide-manager' ),
|
||||
'add_new_item' => __( 'Add New Client', 'hi-school-slide-manager' ),
|
||||
'new_item_name' => __( 'New Client Name', 'hi-school-slide-manager' ),
|
||||
'menu_name' => __( 'Clients', 'hi-school-slide-manager' ),
|
||||
);
|
||||
|
||||
$client_args = array(
|
||||
'hierarchical' => false,
|
||||
'labels' => $client_labels,
|
||||
'show_ui' => true,
|
||||
'show_admin_column' => true,
|
||||
'query_var' => true,
|
||||
'rewrite' => array( 'slug' => 'slide-client' ),
|
||||
'show_in_rest' => true,
|
||||
);
|
||||
|
||||
register_taxonomy( self::CLIENT_TAXONOMY, array( self::POST_TYPE ), $client_args );
|
||||
|
||||
// Add default terms for the three companion sites
|
||||
$this->create_default_site_terms();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default site terms for the three companion sites
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function create_default_site_terms() {
|
||||
$default_sites = array();
|
||||
|
||||
// Use centralized method from HSSM_Main if available
|
||||
if ( class_exists( 'HSSM_Main' ) && method_exists( 'HSSM_Main', 'get_companion_sites' ) ) {
|
||||
$default_sites = HSSM_Main::get_companion_sites();
|
||||
} else {
|
||||
// Fallback
|
||||
$default_sites = array(
|
||||
array(
|
||||
'slug' => 'hi-school',
|
||||
'name' => 'Hi-School Pharmacy',
|
||||
'description' => 'Hi-School Pharmacy companion site'
|
||||
),
|
||||
array(
|
||||
'slug' => 'one-stop',
|
||||
'name' => 'One Stop Hardware',
|
||||
'description' => 'One Stop Hardware companion site'
|
||||
),
|
||||
array(
|
||||
'slug' => 'ace',
|
||||
'name' => 'Ace Hardware',
|
||||
'description' => 'Ace Hardware companion site'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
foreach ( $default_sites as $site ) {
|
||||
if ( ! term_exists( $site['slug'], self::SITE_TAXONOMY ) ) {
|
||||
wp_insert_term(
|
||||
$site['name'],
|
||||
self::SITE_TAXONOMY,
|
||||
array(
|
||||
'slug' => $site['slug'],
|
||||
'description' => $site['description']
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add meta boxes for slide fields
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function add_meta_boxes() {
|
||||
add_meta_box(
|
||||
'hssm_slide_details',
|
||||
__( 'Slide Details', 'hi-school-slide-manager' ),
|
||||
array( $this, 'slide_details_meta_box' ),
|
||||
self::POST_TYPE,
|
||||
'normal',
|
||||
'high'
|
||||
);
|
||||
|
||||
add_meta_box(
|
||||
'hssm_background_image_help',
|
||||
__( 'Background Image Guidelines', 'hi-school-slide-manager' ),
|
||||
array( $this, 'background_image_help_meta_box' ),
|
||||
self::POST_TYPE,
|
||||
'side',
|
||||
'high'
|
||||
);
|
||||
|
||||
add_meta_box(
|
||||
'hssm_slide_scheduling',
|
||||
__( 'Slide Scheduling', 'hi-school-slide-manager' ),
|
||||
array( $this, 'slide_scheduling_meta_box' ),
|
||||
self::POST_TYPE,
|
||||
'side',
|
||||
'default'
|
||||
);
|
||||
|
||||
add_meta_box(
|
||||
'hssm_slide_targeting',
|
||||
__( 'Site Targeting', 'hi-school-slide-manager' ),
|
||||
array( $this, 'slide_targeting_meta_box' ),
|
||||
self::POST_TYPE,
|
||||
'side',
|
||||
'default'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render slide details meta box
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param WP_Post $post Current post object
|
||||
*/
|
||||
public function slide_details_meta_box( $post ) {
|
||||
wp_nonce_field( 'hssm_save_slide_meta', 'hssm_slide_meta_nonce' );
|
||||
|
||||
$subtitle = get_post_meta( $post->ID, '_hssm_subtitle', true );
|
||||
$buttons = get_post_meta( $post->ID, '_hssm_buttons', true );
|
||||
$order = get_post_meta( $post->ID, '_hssm_order', true );
|
||||
$hide_all_text = get_post_meta( $post->ID, '_hssm_hide_all_text', true );
|
||||
|
||||
// Initialize buttons array if empty
|
||||
if ( ! is_array( $buttons ) || empty( $buttons ) ) {
|
||||
$buttons = array(
|
||||
array( 'text' => 'Learn More', 'url' => '' )
|
||||
);
|
||||
}
|
||||
|
||||
?>
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="hssm_subtitle"><?php _e( 'Subtitle', 'hi-school-slide-manager' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="text" id="hssm_subtitle" name="hssm_subtitle" value="<?php echo esc_attr( $subtitle ); ?>" class="regular-text" />
|
||||
<p class="description"><?php _e( 'Optional subtitle for the slide', 'hi-school-slide-manager' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label><?php _e( 'Call-to-Action Buttons', 'hi-school-slide-manager' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<div id="hssm-buttons-container">
|
||||
<?php foreach ( $buttons as $index => $button ) : ?>
|
||||
<div class="hssm-button-row" data-index="<?php echo esc_attr( $index ); ?>">
|
||||
<div class="hssm-button-fields">
|
||||
<label><?php _e( 'Button Text:', 'hi-school-slide-manager' ); ?></label>
|
||||
<input type="text" name="hssm_buttons[<?php echo esc_attr( $index ); ?>][text]" value="<?php echo esc_attr( $button['text'] ?: 'Learn More' ); ?>" class="regular-text" placeholder="Learn More" />
|
||||
|
||||
<label><?php _e( 'Button URL:', 'hi-school-slide-manager' ); ?></label>
|
||||
<input type="url" name="hssm_buttons[<?php echo esc_attr( $index ); ?>][url]" value="<?php echo esc_url( $button['url'] ?: '' ); ?>" class="regular-text" placeholder="https://example.com" />
|
||||
|
||||
<button type="button" class="button hssm-remove-button" <?php echo count( $buttons ) <= 1 ? 'style="display:none;"' : ''; ?>><?php _e( 'Remove', 'hi-school-slide-manager' ); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<button type="button" id="hssm-add-button" class="button button-secondary"><?php _e( 'Add Another Button', 'hi-school-slide-manager' ); ?></button>
|
||||
<span class="description"><?php _e( '(Maximum 5 buttons)', 'hi-school-slide-manager' ); ?></span>
|
||||
</p>
|
||||
|
||||
<p class="description"><?php _e( 'Add call-to-action buttons for the slide. The first button is typically the primary action.', 'hi-school-slide-manager' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="hssm_order"><?php _e( 'Display Order', 'hi-school-slide-manager' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="number" id="hssm_order" name="hssm_order" value="<?php echo esc_attr( $order ?: 0 ); ?>" min="0" />
|
||||
<p class="description"><?php _e( 'Display order for the slide (lower numbers show first). You can also drag and drop to reorder slides in the list view.', 'hi-school-slide-manager' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="hssm_hide_all_text"><?php _e( 'Text Visibility', 'hi-school-slide-manager' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<label>
|
||||
<input type="checkbox" id="hssm_hide_all_text" name="hssm_hide_all_text" value="1" <?php checked( $hide_all_text, '1' ); ?> />
|
||||
<?php _e( 'Hide All Text (Image Only)', 'hi-school-slide-manager' ); ?>
|
||||
</label>
|
||||
<p class="description"><?php _e( 'If checked, the title, subtitle, and buttons will not be displayed on the slide.', 'hi-school-slide-manager' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<style>
|
||||
.hssm-button-row {
|
||||
margin-bottom: 15px;
|
||||
padding: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.hssm-button-fields label {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
font-weight: 600;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.hssm-button-fields input {
|
||||
margin-bottom: 8px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.hssm-remove-button {
|
||||
margin-top: 5px;
|
||||
color: #a00;
|
||||
border-color: #a00;
|
||||
}
|
||||
|
||||
.hssm-remove-button:hover {
|
||||
background: #a00;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
jQuery(document).ready(function($) {
|
||||
let buttonIndex = <?php echo count( $buttons ); ?>;
|
||||
const maxButtons = 5;
|
||||
|
||||
// Add button functionality
|
||||
$('#hssm-add-button').on('click', function() {
|
||||
if ($('.hssm-button-row').length >= maxButtons) {
|
||||
alert('<?php _e( 'Maximum 5 buttons allowed', 'hi-school-slide-manager' ); ?>');
|
||||
return;
|
||||
}
|
||||
|
||||
const newRow = `
|
||||
<div class="hssm-button-row" data-index="${buttonIndex}">
|
||||
<div class="hssm-button-fields">
|
||||
<label><?php _e( 'Button Text:', 'hi-school-slide-manager' ); ?></label>
|
||||
<input type="text" name="hssm_buttons[${buttonIndex}][text]" value="Learn More" class="regular-text" placeholder="Learn More" />
|
||||
|
||||
<label><?php _e( 'Button URL:', 'hi-school-slide-manager' ); ?></label>
|
||||
<input type="url" name="hssm_buttons[${buttonIndex}][url]" value="" class="regular-text" placeholder="https://example.com" />
|
||||
|
||||
<button type="button" class="button hssm-remove-button"><?php _e( 'Remove', 'hi-school-slide-manager' ); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$('#hssm-buttons-container').append(newRow);
|
||||
buttonIndex++;
|
||||
|
||||
// Show remove buttons if we have more than 1
|
||||
if ($('.hssm-button-row').length > 1) {
|
||||
$('.hssm-remove-button').show();
|
||||
}
|
||||
|
||||
// Hide add button if we've reached the maximum
|
||||
if ($('.hssm-button-row').length >= maxButtons) {
|
||||
$('#hssm-add-button').hide();
|
||||
}
|
||||
});
|
||||
|
||||
// Remove button functionality
|
||||
$(document).on('click', '.hssm-remove-button', function() {
|
||||
$(this).closest('.hssm-button-row').remove();
|
||||
|
||||
// Hide remove buttons if we only have 1 left
|
||||
if ($('.hssm-button-row').length <= 1) {
|
||||
$('.hssm-remove-button').hide();
|
||||
}
|
||||
|
||||
// Show add button if we're under the maximum
|
||||
if ($('.hssm-button-row').length < maxButtons) {
|
||||
$('#hssm-add-button').show();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render background image help meta box
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param WP_Post $post Current post object
|
||||
*/
|
||||
public function background_image_help_meta_box( $post ) {
|
||||
?>
|
||||
<div class="hssm-background-help">
|
||||
<h4><?php _e( 'Background Image Requirements:', 'hi-school-slide-manager' ); ?></h4>
|
||||
<ul>
|
||||
<li><?php _e( 'Recommended size: 1920x800px or larger', 'hi-school-slide-manager' ); ?></li>
|
||||
<li><?php _e( 'Aspect ratio: 16:9 or similar wide format', 'hi-school-slide-manager' ); ?></li>
|
||||
<li><?php _e( 'Format: JPG, PNG, or WebP', 'hi-school-slide-manager' ); ?></li>
|
||||
<li><?php _e( 'File size: Under 1MB for best performance', 'hi-school-slide-manager' ); ?></li>
|
||||
</ul>
|
||||
|
||||
<h4><?php _e( 'Design Tips:', 'hi-school-slide-manager' ); ?></h4>
|
||||
<ul>
|
||||
<li><?php _e( 'Keep important content away from edges', 'hi-school-slide-manager' ); ?></li>
|
||||
<li><?php _e( 'Ensure good contrast for text overlay', 'hi-school-slide-manager' ); ?></li>
|
||||
<li><?php _e( 'Consider mobile viewing (image will be cropped)', 'hi-school-slide-manager' ); ?></li>
|
||||
</ul>
|
||||
|
||||
<p><strong><?php _e( 'Note:', 'hi-school-slide-manager' ); ?></strong> <?php _e( 'This image will be used as a full-width background for the header slide on the selected companion sites.', 'hi-school-slide-manager' ); ?></p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.hssm-background-help ul {
|
||||
margin-left: 20px;
|
||||
}
|
||||
.hssm-background-help li {
|
||||
margin-bottom: 5px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.hssm-background-help h4 {
|
||||
margin-bottom: 8px;
|
||||
margin-top: 15px;
|
||||
color: #23282d;
|
||||
}
|
||||
.hssm-background-help h4:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render slide scheduling meta box
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param WP_Post $post Current post object
|
||||
*/
|
||||
public function slide_scheduling_meta_box( $post ) {
|
||||
$start_date = get_post_meta( $post->ID, '_hssm_start_date', true );
|
||||
$end_date = get_post_meta( $post->ID, '_hssm_end_date', true );
|
||||
$auto_publish = get_post_meta( $post->ID, '_hssm_auto_publish', true );
|
||||
$auto_unpublish = get_post_meta( $post->ID, '_hssm_auto_unpublish', true );
|
||||
|
||||
?>
|
||||
<p>
|
||||
<label for="hssm_start_date"><strong><?php _e( 'Start Date', 'hi-school-slide-manager' ); ?></strong></label><br>
|
||||
<input type="datetime-local" id="hssm_start_date" name="hssm_start_date" value="<?php echo esc_attr( $start_date ); ?>" style="width: 100%;" />
|
||||
</p>
|
||||
<p>
|
||||
<label for="hssm_end_date"><strong><?php _e( 'End Date', 'hi-school-slide-manager' ); ?></strong></label><br>
|
||||
<input type="datetime-local" id="hssm_end_date" name="hssm_end_date" value="<?php echo esc_attr( $end_date ); ?>" style="width: 100%;" />
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" name="hssm_auto_publish" value="1" <?php checked( $auto_publish, '1' ); ?> />
|
||||
<?php _e( 'Auto-publish on start date', 'hi-school-slide-manager' ); ?>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" name="hssm_auto_unpublish" value="1" <?php checked( $auto_unpublish, '1' ); ?> />
|
||||
<?php _e( 'Auto-unpublish on end date', 'hi-school-slide-manager' ); ?>
|
||||
</label>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render slide targeting meta box
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param WP_Post $post Current post object
|
||||
*/
|
||||
public function slide_targeting_meta_box( $post ) {
|
||||
// Get current terms
|
||||
$current_sites = wp_get_post_terms( $post->ID, self::SITE_TAXONOMY, array( 'fields' => 'ids' ) );
|
||||
$current_clients = wp_get_post_terms( $post->ID, self::CLIENT_TAXONOMY, array( 'fields' => 'ids' ) );
|
||||
|
||||
// Get all available site terms
|
||||
$site_terms = get_terms( array(
|
||||
'taxonomy' => self::SITE_TAXONOMY,
|
||||
'hide_empty' => false,
|
||||
) );
|
||||
|
||||
// Get all available client terms
|
||||
$client_terms = get_terms( array(
|
||||
'taxonomy' => self::CLIENT_TAXONOMY,
|
||||
'hide_empty' => false,
|
||||
) );
|
||||
|
||||
?>
|
||||
<div class="hssm-targeting-section">
|
||||
<p><strong><?php _e( 'Target Sites', 'hi-school-slide-manager' ); ?></strong></p>
|
||||
<?php if ( ! empty( $site_terms ) && ! is_wp_error( $site_terms ) ) : ?>
|
||||
<?php foreach ( $site_terms as $term ) : ?>
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" name="hssm_target_sites[]" value="<?php echo esc_attr( $term->term_id ); ?>" <?php checked( in_array( $term->term_id, $current_sites ) ); ?> />
|
||||
<?php echo esc_html( $term->name ); ?>
|
||||
</label>
|
||||
<?php if ( $term->description ) : ?>
|
||||
<br><small class="description"><?php echo esc_html( $term->description ); ?></small>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
<?php endforeach; ?>
|
||||
<?php else : ?>
|
||||
<p class="description"><?php _e( 'No sites available. Sites will be created automatically.', 'hi-school-slide-manager' ); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="hssm-targeting-section" style="margin-top: 20px; padding-top: 15px; border-top: 1px solid #ddd;">
|
||||
<p><strong><?php _e( 'Client (Optional)', 'hi-school-slide-manager' ); ?></strong></p>
|
||||
<?php if ( ! empty( $client_terms ) && ! is_wp_error( $client_terms ) ) : ?>
|
||||
<select name="hssm_client" style="width: 100%;">
|
||||
<option value=""><?php _e( 'Select a client (optional)', 'hi-school-slide-manager' ); ?></option>
|
||||
<?php foreach ( $client_terms as $term ) : ?>
|
||||
<option value="<?php echo esc_attr( $term->term_id ); ?>" <?php selected( in_array( $term->term_id, $current_clients ) ); ?>>
|
||||
<?php echo esc_html( $term->name ); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php else : ?>
|
||||
<p class="description">
|
||||
<?php _e( 'No clients available.', 'hi-school-slide-manager' ); ?>
|
||||
<a href="<?php echo admin_url( 'edit-tags.php?taxonomy=' . self::CLIENT_TAXONOMY . '&post_type=' . self::POST_TYPE ); ?>">
|
||||
<?php _e( 'Add clients here', 'hi-school-slide-manager' ); ?>
|
||||
</a>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.hssm-targeting-section label {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
.hssm-targeting-section input[type="checkbox"] {
|
||||
margin: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Save meta field data
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param int $post_id Post ID
|
||||
*/
|
||||
public function save_meta_fields( $post_id ) {
|
||||
// Verify nonce
|
||||
if ( ! isset( $_POST['hssm_slide_meta_nonce'] ) || ! wp_verify_nonce( $_POST['hssm_slide_meta_nonce'], 'hssm_save_slide_meta' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is an autosave
|
||||
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check post type
|
||||
if ( get_post_type( $post_id ) !== self::POST_TYPE ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check permissions
|
||||
if ( ! current_user_can( 'edit_post', $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save meta fields
|
||||
$meta_fields = array(
|
||||
'hssm_subtitle' => 'sanitize_text_field',
|
||||
'hssm_order' => 'intval',
|
||||
'hssm_start_date' => 'sanitize_text_field',
|
||||
'hssm_end_date' => 'sanitize_text_field',
|
||||
);
|
||||
|
||||
foreach ( $meta_fields as $field => $sanitize_function ) {
|
||||
if ( isset( $_POST[ $field ] ) ) {
|
||||
$value = call_user_func( $sanitize_function, $_POST[ $field ] );
|
||||
update_post_meta( $post_id, '_' . $field, $value );
|
||||
}
|
||||
}
|
||||
|
||||
// Save buttons array
|
||||
if ( isset( $_POST['hssm_buttons'] ) && is_array( $_POST['hssm_buttons'] ) ) {
|
||||
$buttons = array();
|
||||
foreach ( $_POST['hssm_buttons'] as $button_data ) {
|
||||
if ( ! empty( $button_data['text'] ) || ! empty( $button_data['url'] ) ) {
|
||||
$buttons[] = array(
|
||||
'text' => sanitize_text_field( $button_data['text'] ),
|
||||
'url' => esc_url_raw( $button_data['url'] )
|
||||
);
|
||||
}
|
||||
}
|
||||
// Limit to 5 buttons maximum
|
||||
$buttons = array_slice( $buttons, 0, 5 );
|
||||
update_post_meta( $post_id, '_hssm_buttons', $buttons );
|
||||
} else {
|
||||
// If no buttons, set default
|
||||
update_post_meta( $post_id, '_hssm_buttons', array(
|
||||
array( 'text' => 'Learn More', 'url' => '' )
|
||||
) );
|
||||
}
|
||||
|
||||
// Save checkbox fields
|
||||
$checkbox_fields = array( 'hssm_auto_publish', 'hssm_auto_unpublish', 'hssm_hide_all_text' );
|
||||
foreach ( $checkbox_fields as $field ) {
|
||||
$value = isset( $_POST[ $field ] ) ? '1' : '0';
|
||||
update_post_meta( $post_id, '_' . $field, $value );
|
||||
}
|
||||
|
||||
// Save target sites taxonomy
|
||||
if ( isset( $_POST['hssm_target_sites'] ) && is_array( $_POST['hssm_target_sites'] ) ) {
|
||||
$target_sites = array_map( 'intval', $_POST['hssm_target_sites'] );
|
||||
wp_set_post_terms( $post_id, $target_sites, self::SITE_TAXONOMY );
|
||||
} else {
|
||||
wp_set_post_terms( $post_id, array(), self::SITE_TAXONOMY );
|
||||
}
|
||||
|
||||
// Save client taxonomy
|
||||
if ( isset( $_POST['hssm_client'] ) && ! empty( $_POST['hssm_client'] ) ) {
|
||||
$client_id = intval( $_POST['hssm_client'] );
|
||||
wp_set_post_terms( $post_id, array( $client_id ), self::CLIENT_TAXONOMY );
|
||||
} else {
|
||||
wp_set_post_terms( $post_id, array(), self::CLIENT_TAXONOMY );
|
||||
}
|
||||
|
||||
// Send email notification for new slides
|
||||
if ( get_post_status( $post_id ) === 'publish' && ! get_post_meta( $post_id, '_hssm_notification_sent', true ) ) {
|
||||
$this->send_new_slide_notification( $post_id );
|
||||
update_post_meta( $post_id, '_hssm_notification_sent', '1' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom columns to the slides list table
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param array $columns Current columns
|
||||
* @return array Modified columns
|
||||
*/
|
||||
public function add_custom_columns( $columns ) {
|
||||
$new_columns = array();
|
||||
$new_columns['cb'] = $columns['cb'];
|
||||
$new_columns['title'] = $columns['title'];
|
||||
$new_columns['target_sites'] = __( 'Target Sites', 'hi-school-slide-manager' );
|
||||
$new_columns['client'] = __( 'Client', 'hi-school-slide-manager' );
|
||||
$new_columns['schedule'] = __( 'Schedule', 'hi-school-slide-manager' );
|
||||
$new_columns['order'] = __( 'Order', 'hi-school-slide-manager' );
|
||||
$new_columns['date'] = $columns['date'];
|
||||
|
||||
return $new_columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display custom column content
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param string $column Column name
|
||||
* @param int $post_id Post ID
|
||||
*/
|
||||
public function custom_column_content( $column, $post_id ) {
|
||||
switch ( $column ) {
|
||||
case 'title':
|
||||
// Add drag handle to title column
|
||||
echo '<span class="hssm-drag-handle dashicons dashicons-menu" title="' . esc_attr__( 'Drag to reorder', 'hi-school-slide-manager' ) . '"></span>';
|
||||
break;
|
||||
|
||||
case 'target_sites':
|
||||
$site_terms = wp_get_post_terms( $post_id, self::SITE_TAXONOMY );
|
||||
if ( ! empty( $site_terms ) && ! is_wp_error( $site_terms ) ) {
|
||||
$site_names = wp_list_pluck( $site_terms, 'name' );
|
||||
echo esc_html( implode( ', ', $site_names ) );
|
||||
} else {
|
||||
echo '<em>' . __( 'No sites selected', 'hi-school-slide-manager' ) . '</em>';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'client':
|
||||
$client_terms = wp_get_post_terms( $post_id, self::CLIENT_TAXONOMY );
|
||||
if ( ! empty( $client_terms ) && ! is_wp_error( $client_terms ) ) {
|
||||
echo esc_html( $client_terms[0]->name );
|
||||
} else {
|
||||
echo '<em>' . __( 'No client', 'hi-school-slide-manager' ) . '</em>';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'schedule':
|
||||
$start_date = get_post_meta( $post_id, '_hssm_start_date', true );
|
||||
$end_date = get_post_meta( $post_id, '_hssm_end_date', true );
|
||||
|
||||
if ( $start_date || $end_date ) {
|
||||
echo '<div>';
|
||||
if ( $start_date ) {
|
||||
echo '<strong>' . __( 'Start:', 'hi-school-slide-manager' ) . '</strong> ' . esc_html( date( 'M j, Y g:i A', strtotime( $start_date ) ) ) . '<br>';
|
||||
}
|
||||
if ( $end_date ) {
|
||||
echo '<strong>' . __( 'End:', 'hi-school-slide-manager' ) . '</strong> ' . esc_html( date( 'M j, Y g:i A', strtotime( $end_date ) ) );
|
||||
}
|
||||
echo '</div>';
|
||||
} else {
|
||||
echo '<em>' . __( 'No schedule set', 'hi-school-slide-manager' ) . '</em>';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'order':
|
||||
$order = get_post_meta( $post_id, '_hssm_order', true );
|
||||
echo esc_html( $order ?: '0' );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send email notification for new slide
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param int $post_id Post ID
|
||||
*/
|
||||
private function send_new_slide_notification( $post_id ) {
|
||||
$notification_emails = get_option( 'hssm_notification_emails', array() );
|
||||
|
||||
if ( empty( $notification_emails ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post = get_post( $post_id );
|
||||
$subject = sprintf( __( 'New Slide Created: %s', 'hi-school-slide-manager' ), $post->post_title );
|
||||
|
||||
$message = sprintf(
|
||||
__( 'A new slide has been created and published on %s.' . "\n\n" .
|
||||
'Title: %s' . "\n" .
|
||||
'Content: %s' . "\n" .
|
||||
'Edit URL: %s', 'hi-school-slide-manager' ),
|
||||
get_bloginfo( 'name' ),
|
||||
$post->post_title,
|
||||
wp_strip_all_tags( $post->post_content ),
|
||||
admin_url( 'post.php?post=' . $post_id . '&action=edit' )
|
||||
);
|
||||
|
||||
$headers = array( 'Content-Type: text/plain; charset=UTF-8' );
|
||||
|
||||
foreach ( $notification_emails as $email ) {
|
||||
wp_mail( $email, $subject, $message, $headers );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for scheduled slides that need status updates
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function check_scheduled_slides() {
|
||||
$current_time = current_time( 'mysql' );
|
||||
|
||||
// Auto-publish slides that should start now
|
||||
$slides_to_publish = get_posts( array(
|
||||
'post_type' => self::POST_TYPE,
|
||||
'post_status' => 'draft',
|
||||
'meta_query' => array(
|
||||
'relation' => 'AND',
|
||||
array(
|
||||
'key' => '_hssm_auto_publish',
|
||||
'value' => '1',
|
||||
'compare' => '='
|
||||
),
|
||||
array(
|
||||
'key' => '_hssm_start_date',
|
||||
'value' => $current_time,
|
||||
'compare' => '<='
|
||||
)
|
||||
),
|
||||
'numberposts' => -1
|
||||
) );
|
||||
|
||||
foreach ( $slides_to_publish as $slide ) {
|
||||
wp_update_post( array(
|
||||
'ID' => $slide->ID,
|
||||
'post_status' => 'publish'
|
||||
) );
|
||||
}
|
||||
|
||||
// Auto-unpublish slides that should end now
|
||||
$slides_to_unpublish = get_posts( array(
|
||||
'post_type' => self::POST_TYPE,
|
||||
'post_status' => 'publish',
|
||||
'meta_query' => array(
|
||||
'relation' => 'AND',
|
||||
array(
|
||||
'key' => '_hssm_auto_unpublish',
|
||||
'value' => '1',
|
||||
'compare' => '='
|
||||
),
|
||||
array(
|
||||
'key' => '_hssm_end_date',
|
||||
'value' => $current_time,
|
||||
'compare' => '<='
|
||||
)
|
||||
),
|
||||
'numberposts' => -1
|
||||
) );
|
||||
|
||||
foreach ( $slides_to_unpublish as $slide ) {
|
||||
wp_update_post( array(
|
||||
'ID' => $slide->ID,
|
||||
'post_status' => 'draft'
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue admin scripts for drag-and-drop functionality
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param string $hook Current admin page hook
|
||||
*/
|
||||
public function enqueue_admin_scripts( $hook ) {
|
||||
global $post_type;
|
||||
|
||||
if ( $hook === 'edit.php' && $post_type === self::POST_TYPE ) {
|
||||
wp_enqueue_script( 'jquery-ui-sortable' );
|
||||
wp_enqueue_script(
|
||||
'hssm-admin-sortable',
|
||||
HSSM_PLUGIN_URL . 'assets/js/admin-sortable.js',
|
||||
array( 'jquery', 'jquery-ui-sortable' ),
|
||||
HSSM_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script( 'hssm-admin-sortable', 'hssmAjax', array(
|
||||
'ajaxurl' => admin_url( 'admin-ajax.php' ),
|
||||
'nonce' => wp_create_nonce( 'hssm_update_slide_order' ),
|
||||
'postType' => self::POST_TYPE
|
||||
) );
|
||||
|
||||
// Add drag handle styles
|
||||
wp_add_inline_style( 'wp-admin', '
|
||||
.wp-list-table .hssm-drag-handle {
|
||||
cursor: move;
|
||||
color: #666;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.wp-list-table .hssm-drag-handle:hover {
|
||||
color: #000;
|
||||
}
|
||||
.wp-list-table tbody tr.ui-sortable-helper {
|
||||
background: #f0f0f0;
|
||||
border: 2px dashed #0073aa;
|
||||
}
|
||||
.wp-list-table tbody tr.ui-sortable-placeholder {
|
||||
background: #e0e0e0;
|
||||
height: 40px;
|
||||
}
|
||||
' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX handler for updating slide order
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function ajax_update_slide_order() {
|
||||
// Verify nonce and permissions
|
||||
if ( ! wp_verify_nonce( $_POST['nonce'], 'hssm_update_slide_order' ) || ! current_user_can( 'edit_posts' ) ) {
|
||||
wp_die( 'Unauthorized' );
|
||||
}
|
||||
|
||||
if ( isset( $_POST['slide_ids'] ) && is_array( $_POST['slide_ids'] ) ) {
|
||||
$slide_ids = array_map( 'intval', $_POST['slide_ids'] );
|
||||
|
||||
foreach ( $slide_ids as $index => $slide_id ) {
|
||||
update_post_meta( $slide_id, '_hssm_order', $index );
|
||||
}
|
||||
|
||||
wp_send_json_success( array( 'message' => __( 'Slide order updated successfully', 'hi-school-slide-manager' ) ) );
|
||||
}
|
||||
|
||||
wp_send_json_error( array( 'message' => __( 'Failed to update slide order', 'hi-school-slide-manager' ) ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Carousel Mimic Test</title>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'ETmodules';
|
||||
src: url('http://hi-school-myhspstores.local/wp-content/themes/Divi/core/admin/fonts/modules/all/modules.ttf') format('truetype');
|
||||
}
|
||||
.et-pb-icon {
|
||||
font-family: 'ETmodules' !important;
|
||||
speak: none;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-shadow: 0 0;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
direction: ltr;
|
||||
}
|
||||
body { font-family: sans-serif; background: #f4f4f4; padding: 20px; }
|
||||
.carousel-wrapper { max-width: 1200px; margin: 0 auto; position: relative; overflow: hidden; }
|
||||
|
||||
/* Mimic Divi Carousel Classes */
|
||||
.et_pb_group_carousel { position: relative; display: flex; flex-direction: column; }
|
||||
.et_pb_group_carousel_container { position: relative; overflow: clip visible; width: 100%; }
|
||||
.et_pb_group_carousel_track { display: flex; transition: transform 0.5s ease-in-out; }
|
||||
.et_pb_group_carousel_slide { flex: 0 0 100%; min-width: 0; box-sizing: border-box; }
|
||||
|
||||
.et_pb_group {
|
||||
min-height: 600px;
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
box-shadow: 0px 2px 18px 0px rgba(0,0,0,0.6);
|
||||
color: white;
|
||||
text-align: center;
|
||||
overflow: hidden; /* Critical for rounded corners on backgrounds */
|
||||
margin: 10px 20px;
|
||||
}
|
||||
|
||||
.et_pb_module_header {
|
||||
background: rgba(0,0,0,0.5);
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.et_pb_group_carousel_arrow {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: red;
|
||||
color: white;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
user-select: none;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.et_pb_group_carousel_arrow_prev { left: 10px; }
|
||||
.et_pb_group_carousel_arrow_next { right: 10px; }
|
||||
|
||||
.et_pb_button {
|
||||
display: inline-block;
|
||||
margin-top: 20px;
|
||||
padding: 10px 25px;
|
||||
background: red;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Mocking the fixed background class from the plugin */
|
||||
.hssm-bg-fixed {
|
||||
background-size: cover !important;
|
||||
background-position: center !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Carousel Mimic for Testing</h1>
|
||||
|
||||
<div class="carousel-wrapper">
|
||||
<div class="et_pb_group_carousel et_pb_group_carousel_0">
|
||||
<div class="et_pb_group_carousel_container">
|
||||
<div class="et_pb_group_carousel_track">
|
||||
|
||||
<!-- Slide 1 -->
|
||||
<div class="et_pb_group_carousel_slide">
|
||||
<div class="et_pb_group et_pb_group_0">
|
||||
<h1 class="et_pb_module_header">Get A $25, $10 or $5 Reward</h1>
|
||||
<a href="#" class="et_pb_button">Click Here</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Slide 2 -->
|
||||
<div class="et_pb_group_carousel_slide">
|
||||
<div class="et_pb_group et_pb_group_1">
|
||||
<h1 class="et_pb_module_header">Tips For Caring For Your Houseplants During The Winter</h1>
|
||||
<a href="#" class="et_pb_button">Click Here</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Slide 3 -->
|
||||
<div class="et_pb_group_carousel_slide">
|
||||
<div class="et_pb_group et_pb_group_2">
|
||||
<h1 class="et_pb_module_header">Reach For Mucinex & Delsym For Cough and Congestion Relief</h1>
|
||||
<a href="#" class="et_pb_button">Click Here</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Slide 4 (Ace Hardware Mock) -->
|
||||
<div class="et_pb_group_carousel_slide">
|
||||
<div class="et_pb_group et_pb_group_3">
|
||||
<h1 class="et_pb_module_header">Ace Hardware - The Helpful Place</h1>
|
||||
<a href="#" class="et_pb_button">Shop Now</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="et_pb_group_carousel_arrow et_pb_group_carousel_arrow_prev" role="button"><span class="et-pb-icon">4</span></div>
|
||||
<div class="et_pb_group_carousel_arrow et_pb_group_carousel_arrow_next" role="button"><span class="et-pb-icon">5</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
var $track = $('.et_pb_group_carousel_track');
|
||||
var $slides = $('.et_pb_group_carousel_slide');
|
||||
var currentIndex = 0;
|
||||
var slideCount = $slides.length;
|
||||
|
||||
function updateCarousel() {
|
||||
var offset = -currentIndex * 100;
|
||||
$track.css('transform', 'translateX(' + offset + '%)');
|
||||
}
|
||||
|
||||
$('.et_pb_group_carousel_arrow_next').click(function() {
|
||||
currentIndex = (currentIndex + 1) % slideCount;
|
||||
updateCarousel();
|
||||
});
|
||||
|
||||
$('.et_pb_group_carousel_arrow_prev').click(function() {
|
||||
currentIndex = (currentIndex - 1 + slideCount) % slideCount;
|
||||
updateCarousel();
|
||||
});
|
||||
|
||||
// Simulate the dynamic background fixing logic that the user is working on
|
||||
// This is what they want to test.
|
||||
var bgData = [
|
||||
{
|
||||
title: "Get A $25, $10 or $5 Reward",
|
||||
bg: "http://hi-school-myhspstores.local/wp-content/uploads/2026/01/JOHNSON-JAN-BANNER-AUC-25YJGLA3676-KVU_SCDR_HiSchool_Q12026_printdig.jpg"
|
||||
},
|
||||
{
|
||||
title: "Tips For Caring For Your Houseplants During The Winter",
|
||||
bg: "http://hi-school-myhspstores.local/wp-content/uploads/2026/01/houseplant-winter-scaled.jpg"
|
||||
},
|
||||
{
|
||||
title: "Reach For Mucinex & Delsym For Cough and Congestion Relief",
|
||||
bg: "http://hi-school-myhspstores.local/wp-content/uploads/2026/01/rb-health-mucinex-delsym-january-banner.jpg"
|
||||
},
|
||||
{
|
||||
title: "Ace Hardware - The Helpful Place",
|
||||
bg: "http://hi-school-myhspstores.local/wp-content/uploads/2026/01/sale-weekly-coups.jpg"
|
||||
}
|
||||
];
|
||||
|
||||
function fixCarouselBgs() {
|
||||
$('.et_pb_group_carousel_slide').each(function() {
|
||||
var $slide = $(this);
|
||||
var slideText = $slide.find('.et_pb_module_header').first().text().trim();
|
||||
|
||||
if (!slideText) return;
|
||||
|
||||
$.each(bgData, function(i, item) {
|
||||
if (slideText.indexOf(item.title) !== -1 || item.title.indexOf(slideText) !== -1) {
|
||||
$slide.find('.et_pb_group').first().css({
|
||||
'background-image': 'url(' + item.bg + ')',
|
||||
'background-size': 'cover',
|
||||
'background-position': 'center',
|
||||
'background-repeat': 'no-repeat'
|
||||
}).addClass('hssm-bg-fixed');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Run the fix
|
||||
fixCarouselBgs();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,7 @@
|
||||
# Emergent Tasks
|
||||
|
||||
- Nest pages into slide-dashboard as parent
|
||||
- Preview banners to preview slides
|
||||
- check all "Coming Soon!" functionality
|
||||
- conditional show for empty header fields
|
||||
-
|
||||
@@ -0,0 +1,19 @@
|
||||
# Dynamic Query for Our Slider on the Preview Page
|
||||
|
||||
I need to dynamically filter and override the Divi 5 Group/Carousel module on the "Slide Preview" page based on our URL parameters, or the filters used from our preview dashboard.
|
||||
|
||||
We need to intercept divi's loop query and create our own. The header Currently shows all published slides as intended, so it should be just a matter of finding and replacing the query.
|
||||
|
||||
# Task: Please write a PHP filter in class-hssm-frontend.php to intercept the query args.
|
||||
|
||||
1. **Identify the Hook:** Look for a filter like divi_query_args, et_pb_blog_query_args, or use pre_get_posts as a fallback. Since this is Divi 5, we need to be careful not to break the visual builder, so ensure !is_admin() or !et_fb_is_enabled() checks are used if we go the pre_get_posts route.
|
||||
|
||||
2. **Targeting:** The filter must ONLY apply if:
|
||||
- We are on the Preview Dashboard page.
|
||||
- The $_GET parameters (like hssm_filter) are present in the URL.
|
||||
3. **The Logic:**
|
||||
- Sanitize the GET parameter.
|
||||
- Modify the query's tax_query (if filtering by Slide Group) or meta_query (if filtering by Store ID) to match the parameter.
|
||||
4. **Implementation:**
|
||||
- Add the code to class-hssm-frontend.php.
|
||||
- If you are unsure of the exact Divi 5 hook, start by creating a standard pre_get_posts function that targets the main query of that specific page ID.
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
/**
|
||||
* Template Name: Slide Live Confirmation
|
||||
*
|
||||
* Page for ITC team to confirm a slide is live
|
||||
*
|
||||
* @package Hi_School_Slide_Manager
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 'Direct access forbidden.' );
|
||||
}
|
||||
|
||||
get_header();
|
||||
|
||||
$slide_id = isset( $_GET['slide_id'] ) ? intval( $_GET['slide_id'] ) : 0;
|
||||
// Note: We used a simplified token/hash check in the email link generation.
|
||||
// Ideally, we would verify that here.
|
||||
// For now, let's assume if they have the link, they can confirm (or we add basic checks).
|
||||
|
||||
$slide = get_post( $slide_id );
|
||||
$is_valid_slide = ( $slide && $slide->post_type === 'hssm_slide' );
|
||||
$confirmed = false;
|
||||
$error_message = '';
|
||||
|
||||
// Handle confirmation action
|
||||
if ( $is_valid_slide && isset( $_POST['confirm_live_action'] ) && $_POST['confirm_live_action'] === 'confirm' ) {
|
||||
if ( wp_verify_nonce( $_POST['hssm_live_nonce'], 'hssm_confirm_live_action' ) ) {
|
||||
// Trigger the final notification
|
||||
if ( class_exists( 'HSSM_Notifications' ) ) {
|
||||
$notifications = new HSSM_Notifications();
|
||||
$notifications->notify_all_confirmed_live( $slide_id );
|
||||
|
||||
// Mark as confirmed in metadata
|
||||
update_post_meta( $slide_id, '_hssm_live_confirmed', current_time( 'timestamp' ) );
|
||||
$confirmed = true;
|
||||
} else {
|
||||
$error_message = 'Notification system not available.';
|
||||
}
|
||||
} else {
|
||||
$error_message = 'Security check failed. Please refresh and try again.';
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<div id="main-content">
|
||||
<div class="container-fluid">
|
||||
<div id="content-area" class="clearfix full-width">
|
||||
|
||||
<div class="hssm-confirmation-container" style="max-width: 600px; margin: 50px auto; padding: 30px; background: #fff; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); text-align: center;">
|
||||
|
||||
<?php if ( ! $is_valid_slide ) : ?>
|
||||
<div class="hssm-error">
|
||||
<h2>Invalid Slide</h2>
|
||||
<p>The slide you are trying to confirm could not be found.</p>
|
||||
<a href="<?php echo home_url(); ?>" class="button">Return Home</a>
|
||||
</div>
|
||||
|
||||
<?php elseif ( $confirmed ) : ?>
|
||||
<div class="hssm-success">
|
||||
<h2 style="color: #46b450;">Confirmation Sent!</h2>
|
||||
<p class="dashicons dashicons-yes-alt" style="font-size: 64px; width: 64px; height: 64px; color: #46b450;"></p>
|
||||
<p><strong>Thank you!</strong></p>
|
||||
<p>Notifications have been sent to the Editor and Creator confirming that "<?php echo esc_html( $slide->post_title ); ?>" is Live.</p>
|
||||
<a href="<?php echo home_url(); ?>" class="button">Return Home</a>
|
||||
</div>
|
||||
|
||||
<?php else : ?>
|
||||
<div class="hssm-confirm-form">
|
||||
<h2>Confirm Slide is Live</h2>
|
||||
|
||||
<?php if ( $error_message ) : ?>
|
||||
<div style="background: #f8d7da; color: #721c24; padding: 10px; margin-bottom: 20px; border-radius: 4px;">
|
||||
<?php echo esc_html( $error_message ); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="margin: 30px 0; padding: 20px; background: #f9f9f9; border: 1px solid #eee; border-radius: 4px;">
|
||||
<h3 style="margin-top: 0;"><?php echo esc_html( $slide->post_title ); ?></h3>
|
||||
<?php
|
||||
$target_sites = wp_get_post_terms( $slide_id, 'hssm_site', array( 'fields' => 'names' ) );
|
||||
if ( ! empty( $target_sites ) ) {
|
||||
echo '<p><strong>Target Sites:</strong> ' . esc_html( implode( ', ', $target_sites ) ) . '</p>';
|
||||
}
|
||||
?>
|
||||
<div style="margin-top: 15px;">
|
||||
<img src="<?php echo get_the_post_thumbnail_url( $slide_id, 'medium' ); ?>" style="max-width: 100%; height: auto; border-radius: 4px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>I confirm that the slide <strong>"<?php echo esc_html( $slide->post_title ); ?>"</strong> is Live and displaying correctly on the target sites.</p>
|
||||
|
||||
<form method="post">
|
||||
<input type="hidden" name="confirm_live_action" value="confirm">
|
||||
<?php wp_nonce_field( 'hssm_confirm_live_action', 'hssm_live_nonce' ); ?>
|
||||
|
||||
<button type="submit" class="button" style="background-color: #46b450; color: #fff; border: none; padding: 15px 30px; font-size: 18px; cursor: pointer; border-radius: 4px;">
|
||||
<span class="dashicons dashicons-yes"></span> Confirm Slide is Live
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php get_footer(); ?>
|
||||
@@ -0,0 +1,369 @@
|
||||
<?php
|
||||
/**
|
||||
* Template Name: Slide Dashboard
|
||||
*
|
||||
* Divi full-width template for the slide management dashboard
|
||||
*
|
||||
* @package Hi_School_Slide_Manager
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 'Direct access forbidden.' );
|
||||
}
|
||||
|
||||
get_header(); ?>
|
||||
|
||||
<div id="main-content">
|
||||
<div class="container-fluid">
|
||||
<div id="content-area" class="clearfix full-width">
|
||||
|
||||
<!-- Custom Dashboard Header with Navigation -->
|
||||
<div class="hssm-header-bar">
|
||||
<div class="hssm-header-content">
|
||||
<div class="hssm-logo-section">
|
||||
<h1 class="hssm-site-title">
|
||||
Slide Management Dashboard
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="hssm-header-nav">
|
||||
<a href="<?php echo home_url(); ?>" class="hssm-nav-btn hssm-nav-home">
|
||||
<span class="dashicons dashicons-admin-home"></span>
|
||||
Home
|
||||
</a>
|
||||
<a href="<?php echo admin_url(); ?>" class="hssm-nav-btn hssm-nav-admin">
|
||||
<span class="dashicons dashicons-admin-generic"></span>
|
||||
Admin
|
||||
</a>
|
||||
<a href="<?php echo home_url( '/slide-preview/' ); ?>" class="hssm-nav-btn hssm-nav-preview" target="_blank">
|
||||
<span class="dashicons dashicons-visibility"></span>
|
||||
Slide Preview
|
||||
</a>
|
||||
<div class="hssm-user-menu">
|
||||
<span class="hssm-user-name">
|
||||
<?php echo esc_html( wp_get_current_user()->display_name ); ?>
|
||||
</span>
|
||||
<a href="<?php echo wp_logout_url( home_url() ); ?>" class="hssm-nav-btn hssm-nav-logout">
|
||||
<span class="dashicons dashicons-exit"></span>
|
||||
Logout
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Dashboard Content -->
|
||||
<div class="hssm-dashboard-main">
|
||||
|
||||
<!-- Messages Container -->
|
||||
<div id="hssm-messages" class="hssm-messages"></div>
|
||||
|
||||
<!-- Dashboard Navigation -->
|
||||
<div class="hssm-dashboard-nav">
|
||||
<ul class="hssm-nav-tabs">
|
||||
<li class="active">
|
||||
<a href="#create-slides" data-tab="create-slides">
|
||||
<span class="dashicons dashicons-plus-alt"></span>
|
||||
Create Slides
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#manage-slides" data-tab="manage-slides">
|
||||
<span class="dashicons dashicons-admin-page"></span>
|
||||
Manage Slides
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#analytics" data-tab="analytics">
|
||||
<span class="dashicons dashicons-chart-area"></span>
|
||||
Analytics
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#notifications" data-tab="notifications">
|
||||
<span class="dashicons dashicons-email"></span>
|
||||
Notifications
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Create Slides Tab -->
|
||||
<div id="create-slides" class="hssm-tab-content active">
|
||||
<div class="hssm-tab-header">
|
||||
<h2>Create New Slides</h2>
|
||||
<div class="hssm-tab-actions">
|
||||
<button id="add-slide-form" class="hssm-btn hssm-btn-primary" data-max-forms="5">
|
||||
<span class="dashicons dashicons-plus"></span> Add New Slide Form
|
||||
</button>
|
||||
<button id="import-slides" class="hssm-btn hssm-btn-secondary">
|
||||
<span class="dashicons dashicons-upload"></span> Import Slides
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="slide-forms-container">
|
||||
<!-- Initial slide form will be inserted here -->
|
||||
</div>
|
||||
|
||||
<div class="hssm-bulk-actions">
|
||||
<button id="save-all-slides" class="hssm-btn hssm-btn-success hssm-btn-large">
|
||||
<span class="dashicons dashicons-yes"></span> Save All Slides
|
||||
</button>
|
||||
<button id="save-draft-all" class="hssm-btn hssm-btn-secondary">
|
||||
<span class="dashicons dashicons-edit"></span> Save as Drafts
|
||||
</button>
|
||||
<button id="clear-all-forms" class="hssm-btn hssm-btn-outline">
|
||||
<span class="dashicons dashicons-trash"></span> Clear All Forms
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Manage Slides Tab -->
|
||||
<div id="manage-slides" class="hssm-tab-content">
|
||||
<div class="hssm-tab-header">
|
||||
<h2>Manage Existing Slides</h2>
|
||||
<div class="hssm-filters">
|
||||
<select id="filter-site" class="hssm-select">
|
||||
<option value="">All Sites</option>
|
||||
<option value="hi-school">Hi-School Pharmacy</option>
|
||||
<option value="one-stop">One Stop Hardware</option>
|
||||
<option value="ace">Ace Hardware</option>
|
||||
</select>
|
||||
<select id="filter-client" class="hssm-select">
|
||||
<option value="">All Clients</option>
|
||||
</select>
|
||||
<select id="filter-status" class="hssm-select">
|
||||
<option value="">All Status</option>
|
||||
<option value="publish">Published</option>
|
||||
<option value="draft">Draft</option>
|
||||
<option value="scheduled">Scheduled</option>
|
||||
</select>
|
||||
<button id="apply-filters" class="hssm-btn hssm-btn-primary">
|
||||
<span class="dashicons dashicons-filter"></span> Apply Filters
|
||||
</button>
|
||||
<button id="export-slides" class="hssm-btn hssm-btn-secondary">
|
||||
<span class="dashicons dashicons-download"></span> Export
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="slides-list" class="hssm-slides-list">
|
||||
<!-- Existing slides will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Analytics Tab -->
|
||||
<div id="analytics" class="hssm-tab-content">
|
||||
<div class="hssm-tab-header">
|
||||
<h2>Slide Analytics</h2>
|
||||
<div class="hssm-analytics-controls">
|
||||
<select id="analytics-timeframe" class="hssm-select">
|
||||
<option value="7">Last 7 Days</option>
|
||||
<option value="30">Last 30 Days</option>
|
||||
<option value="90">Last 90 Days</option>
|
||||
</select>
|
||||
<button id="generate-report" class="hssm-btn hssm-btn-primary">
|
||||
<span class="dashicons dashicons-chart-area"></span> Generate Report
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="analytics-content" class="hssm-analytics-content">
|
||||
<div class="hssm-analytics-cards">
|
||||
<div class="hssm-analytics-card">
|
||||
<h3>Total Slides Created</h3>
|
||||
<div class="hssm-analytics-number">--</div>
|
||||
</div>
|
||||
<div class="hssm-analytics-card">
|
||||
<h3>Active Campaigns</h3>
|
||||
<div class="hssm-analytics-number">--</div>
|
||||
</div>
|
||||
<div class="hssm-analytics-card">
|
||||
<h3>Scheduled Slides</h3>
|
||||
<div class="hssm-analytics-number">--</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notifications Tab -->
|
||||
<div id="notifications" class="hssm-tab-content">
|
||||
<div class="hssm-tab-header">
|
||||
<h2>Notification Settings</h2>
|
||||
</div>
|
||||
|
||||
<div class="hssm-notifications-settings">
|
||||
<form id="notification-settings-form">
|
||||
<div class="hssm-form-section">
|
||||
<h3>Editor Notifications</h3>
|
||||
<p class="description">Recipients for slide scheduling and approval requests.</p>
|
||||
<div class="hssm-field">
|
||||
<label for="editor_emails">Editor Email(s)</label>
|
||||
<textarea id="editor_emails" name="editor_emails" class="hssm-textarea" rows="3" placeholder="Enter email addresses, separated by commas or new lines"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hssm-form-section">
|
||||
<h3>I.T.C.Guys Notifications</h3>
|
||||
<p class="description">Recipients for LIVE confirmation checks.</p>
|
||||
<div class="hssm-field">
|
||||
<label for="itc_emails">I.T.C.Guys Email(s)</label>
|
||||
<textarea id="itc_emails" name="itc_emails" class="hssm-textarea" rows="3" placeholder="Enter email addresses, separated by commas or new lines"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hssm-form-actions">
|
||||
<button type="submit" id="save-notifications" class="hssm-btn hssm-btn-primary">
|
||||
<span class="dashicons dashicons-saved"></span> Save Settings
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- .hssm-dashboard-main -->
|
||||
</div> <!-- #content-area -->
|
||||
</div> <!-- .container-fluid -->
|
||||
</div> <!-- #main-content -->
|
||||
|
||||
<!-- Slide Form Template (Hidden) -->
|
||||
<script type="text/html" id="slide-form-template">
|
||||
<div class="hssm-slide-form" data-form-id="{{form_id}}">
|
||||
<div class="hssm-form-header">
|
||||
<h3><strong>New Slide #{{form_number}}</strong></h3>
|
||||
<button type="button" class="hssm-remove-form" aria-label="Remove this form">
|
||||
<span class="dashicons dashicons-no"> test</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="hssm-form-grid">
|
||||
<!-- Left Column: Basic Information -->
|
||||
<div class="hssm-form-section hssm-form-basic">
|
||||
<h4>Basic Information</h4>
|
||||
|
||||
<div class="hssm-field-row">
|
||||
<div class="hssm-field">
|
||||
<label for="title_{{form_id}}">Title *</label>
|
||||
<input type="text" id="title_{{form_id}}" name="title" class="hssm-input" required>
|
||||
</div>
|
||||
<div class="hssm-field">
|
||||
<label for="client_{{form_id}}">Client</label>
|
||||
<input type="text" id="client_{{form_id}}" name="client" class="hssm-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hssm-field">
|
||||
<label for="subtitle_{{form_id}}">Subtitle</label>
|
||||
<input type="text" id="subtitle_{{form_id}}" name="subtitle" class="hssm-input">
|
||||
</div>
|
||||
|
||||
<div class="hssm-field">
|
||||
<label for="content_{{form_id}}">Content</label>
|
||||
<textarea id="content_{{form_id}}" name="content" class="hssm-textarea" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Center Column: Image & Buttons -->
|
||||
<div class="hssm-form-section hssm-form-media">
|
||||
<h4>Background Image</h4>
|
||||
|
||||
<div class="hssm-image-upload-compact">
|
||||
<div class="hssm-image-preview-small" id="image_preview_{{form_id}}">
|
||||
<span class="hssm-placeholder">Click to upload</span>
|
||||
</div>
|
||||
<div class="hssm-image-controls">
|
||||
<input type="file" id="image_upload_{{form_id}}" name="background_image" accept="image/*" style="display: none;">
|
||||
<input type="hidden" id="image_id_{{form_id}}" name="featured_image_id" value="">
|
||||
<button type="button" class="hssm-btn hssm-btn-small hssm-upload-btn" data-target="{{form_id}}">
|
||||
Choose Image
|
||||
</button>
|
||||
<button type="button" class="hssm-btn hssm-btn-small hssm-btn-text hssm-remove-image" data-target="{{form_id}}" style="display: none;">
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4>Call-to-Action Buttons</h4>
|
||||
<div class="hssm-buttons-container-compact" id="buttons_{{form_id}}">
|
||||
<div class="hssm-button-row-compact">
|
||||
<input type="text" name="buttons[0][text]" placeholder="Button Text" class="hssm-input-small">
|
||||
<input type="url" name="buttons[0][url]" placeholder="Button URL" class="hssm-input-small">
|
||||
<button type="button" class="hssm-btn hssm-btn-tiny hssm-remove-button">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="hssm-btn hssm-btn-small hssm-add-button" data-form="{{form_id}}">
|
||||
<span class="dashicons dashicons-plus"></span> Add Button
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Targeting & Scheduling -->
|
||||
<div class="hssm-form-section hssm-form-targeting">
|
||||
<h4>Targeting & Scheduling</h4>
|
||||
|
||||
<div class="hssm-field">
|
||||
<label>Target Sites *</label>
|
||||
<div class="hssm-checkboxes-compact">
|
||||
<label class="hssm-checkbox-compact">
|
||||
<input type="checkbox" name="target_sites[]" value="hi-school">
|
||||
Hi-School Pharmacy
|
||||
</label>
|
||||
<label class="hssm-checkbox-compact">
|
||||
<input type="checkbox" name="target_sites[]" value="one-stop">
|
||||
One Stop Hardware
|
||||
</label>
|
||||
<label class="hssm-checkbox-compact">
|
||||
<input type="checkbox" name="target_sites[]" value="ace">
|
||||
Ace Hardware
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hssm-field" style="margin-top: 15px;">
|
||||
<label class="hssm-checkbox-compact hssm-hide-text-option">
|
||||
<input type="checkbox" name="hide_all_text" value="1">
|
||||
Hide All Text (Image Only)
|
||||
<small style="display: block; color: #666; margin-top: 5px;">Check this to display only the image without any overlay text</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="hssm-field-row">
|
||||
<div class="hssm-field">
|
||||
<label for="start_date_{{form_id}}">Start Date</label>
|
||||
<input type="date" id="start_date_{{form_id}}" name="start_date" class="hssm-input">
|
||||
</div>
|
||||
<div class="hssm-field">
|
||||
<label for="end_date_{{form_id}}">End Date</label>
|
||||
<input type="date" id="end_date_{{form_id}}" name="end_date" class="hssm-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hssm-field-row">
|
||||
<div class="hssm-field">
|
||||
<label for="order_{{form_id}}">Display Order</label>
|
||||
<input type="number" id="order_{{form_id}}" name="order" class="hssm-input" value="0" min="0">
|
||||
</div>
|
||||
<div class="hssm-field">
|
||||
<!-- Spacer for alignment -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hssm-form-actions">
|
||||
<button type="button" class="hssm-btn hssm-btn-secondary hssm-schedule-slide" data-form="{{form_id}}">
|
||||
<span class="dashicons dashicons-calendar-alt"></span> Schedule for Approval
|
||||
</button>
|
||||
<button type="button" class="hssm-btn hssm-btn-primary hssm-publish-slide" data-form="{{form_id}}">
|
||||
<span class="dashicons dashicons-yes"></span> Publish Immediately
|
||||
</button>
|
||||
<button type="button" class="hssm-btn hssm-btn-outline hssm-preview-slide" data-form="{{form_id}}">
|
||||
<span class="dashicons dashicons-visibility"></span> Preview
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<?php get_footer(); ?>
|
||||
@@ -0,0 +1,498 @@
|
||||
<?php
|
||||
/**
|
||||
* Template Name: Slide Preview
|
||||
* Front-end preview page for scheduled slides.
|
||||
* Can be used as a Divi page with a header/loop module,
|
||||
* while this template renders a canonical preview dashboard underneath.
|
||||
*
|
||||
* URL parameters:
|
||||
* ?hssm_date=YYYY-MM-DD
|
||||
* ?hssm_site=site-slug (matches hssm_site taxonomy slug)
|
||||
*
|
||||
* @package Hi_School_Slide_Manager
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit( 'Direct access forbidden.' );
|
||||
}
|
||||
|
||||
get_header();
|
||||
|
||||
/**
|
||||
* Inject dynamic CSS for carousel slide backgrounds.
|
||||
* Divi 5 group carousels use dynamic loop items.
|
||||
* We force the backgrounds based on our preview query.
|
||||
*/
|
||||
/**
|
||||
* Pass slide background data to the frontend.
|
||||
* We use JavaScript to apply backgrounds because Divi 5's loop indices
|
||||
* and cloning can make CSS targeting unreliable on the preview page.
|
||||
*/
|
||||
add_action( 'wp_footer', function() {
|
||||
$request = hssm_get_preview_request();
|
||||
// Get all possible slides for this day
|
||||
$slides = hssm_get_preview_slides_for_date( $request['date'], $request['site_slug'] );
|
||||
|
||||
if ( empty( $slides ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$bg_data = array();
|
||||
foreach ( $slides as $slide ) {
|
||||
$img_url = get_the_post_thumbnail_url( $slide->ID, 'full' );
|
||||
if ( $img_url ) {
|
||||
// We use the title to match the slide on the frontend
|
||||
$bg_data[] = array(
|
||||
'title' => html_entity_decode(get_the_title($slide)),
|
||||
'bg' => $img_url
|
||||
);
|
||||
}
|
||||
}
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
(function($) {
|
||||
$(document).ready(function() {
|
||||
var bgData = <?php echo json_encode($bg_data); ?>;
|
||||
|
||||
function fixCarouselBgs() {
|
||||
$('.et_pb_group_carousel_slide').each(function() {
|
||||
var $slide = $(this);
|
||||
var slideText = $slide.find('.et_pb_module_header').first().text().trim();
|
||||
|
||||
if (!slideText) return;
|
||||
|
||||
$.each(bgData, function(i, item) {
|
||||
// Check if the slide heading contains the slide title (or vice versa)
|
||||
if (slideText.indexOf(item.title) !== -1 || item.title.indexOf(slideText) !== -1) {
|
||||
// Apply background to the inner group module
|
||||
$slide.find('.et_pb_group').first().css({
|
||||
'background-image': 'url(' + item.bg + ')',
|
||||
'background-size': 'cover',
|
||||
'background-position': 'center',
|
||||
'background-repeat': 'no-repeat'
|
||||
}).addClass('hssm-bg-fixed');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Run immediately and also on a interval to catch Divi 5's dynamic rendering/cloning
|
||||
fixCarouselBgs();
|
||||
setInterval(fixCarouselBgs, 1000);
|
||||
|
||||
// Also run when carousel might have re-initialized
|
||||
$(window).on('resize', fixCarouselBgs);
|
||||
});
|
||||
})(jQuery);
|
||||
</script>
|
||||
<style>
|
||||
/* Ensure the fixed backgrounds are visible */
|
||||
.hssm-bg-fixed {
|
||||
background-size: cover !important;
|
||||
background-position: center !important;
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}, 100 );
|
||||
|
||||
/**
|
||||
* Get normalized preview inputs from the URL with sane defaults.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function hssm_get_preview_request() {
|
||||
$today = new DateTime( current_time( 'Y-m-d' ) );
|
||||
|
||||
$date_param = isset( $_GET['hssm_date'] ) ? sanitize_text_field( wp_unslash( $_GET['hssm_date'] ) ) : $today->format( 'Y-m-d' );
|
||||
$site_param = isset( $_GET['hssm_site'] ) ? sanitize_text_field( wp_unslash( $_GET['hssm_site'] ) ) : '';
|
||||
$show_past = isset( $_GET['hssm_past'] ) && $_GET['hssm_past'] === '1';
|
||||
|
||||
// Validate date (YYYY-MM-DD)
|
||||
$date_obj = DateTime::createFromFormat( 'Y-m-d', $date_param );
|
||||
if ( ! $date_obj ) {
|
||||
$date_obj = $today;
|
||||
}
|
||||
|
||||
return array(
|
||||
'date' => $date_obj->format( 'Y-m-d' ),
|
||||
'site_slug' => $site_param,
|
||||
'show_past' => $show_past,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available target sites from the hssm_site taxonomy.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function hssm_get_preview_sites() {
|
||||
// We specifically want only these three sites to appear, to match the main dashboard.
|
||||
// Also helps avoid duplicates if the taxonomy has similar terms.
|
||||
$allowed_slugs = array( 'hi-school', 'ace', 'one-stop' );
|
||||
|
||||
$terms = get_terms(
|
||||
array(
|
||||
'taxonomy' => HSSM_Slides_CPT::SITE_TAXONOMY,
|
||||
'hide_empty' => false,
|
||||
'slug' => $allowed_slugs,
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $terms ) || empty( $terms ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$sites = array();
|
||||
$added_slugs = array();
|
||||
|
||||
foreach ( $terms as $term ) {
|
||||
// Double check against duplicates just in case
|
||||
if ( in_array( $term->slug, $added_slugs, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sites[] = array(
|
||||
'slug' => $term->slug,
|
||||
'name' => $term->name,
|
||||
);
|
||||
$added_slugs[] = $term->slug;
|
||||
}
|
||||
|
||||
return $sites;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get calendar events (start/end dates) for highlighting.
|
||||
*
|
||||
* @param string $site_slug Optional site slug to filter by.
|
||||
* @return array Map of date => ['start', 'end']
|
||||
*/
|
||||
function hssm_get_calendar_events( $site_slug = '' ) {
|
||||
$meta_query = array(
|
||||
'relation' => 'OR',
|
||||
array(
|
||||
'key' => '_hssm_start_date',
|
||||
'compare' => 'EXISTS',
|
||||
),
|
||||
array(
|
||||
'key' => '_hssm_end_date',
|
||||
'compare' => 'EXISTS',
|
||||
),
|
||||
);
|
||||
|
||||
$tax_query = array();
|
||||
|
||||
if ( ! empty( $site_slug ) ) {
|
||||
$site_term = get_term_by( 'slug', $site_slug, HSSM_Slides_CPT::SITE_TAXONOMY );
|
||||
if ( $site_term && ! is_wp_error( $site_term ) ) {
|
||||
$tax_query[] = array(
|
||||
'taxonomy' => HSSM_Slides_CPT::SITE_TAXONOMY,
|
||||
'field' => 'term_id',
|
||||
'terms' => $site_term->term_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( count( $tax_query ) > 1 ) {
|
||||
$tax_query['relation'] = 'AND';
|
||||
}
|
||||
|
||||
$args = array(
|
||||
'post_type' => HSSM_Slides_CPT::POST_TYPE,
|
||||
'post_status' => array( 'publish', 'future', 'pending' ),
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids',
|
||||
'meta_query' => $meta_query,
|
||||
);
|
||||
|
||||
if ( ! empty( $tax_query ) ) {
|
||||
$args['tax_query'] = $tax_query;
|
||||
}
|
||||
|
||||
$slide_ids = get_posts( $args );
|
||||
$events = array();
|
||||
|
||||
foreach ( $slide_ids as $slide_id ) {
|
||||
$start_raw = get_post_meta( $slide_id, '_hssm_start_date', true );
|
||||
$end_raw = get_post_meta( $slide_id, '_hssm_end_date', true );
|
||||
|
||||
if ( ! empty( $start_raw ) ) {
|
||||
$d = substr( $start_raw, 0, 10 );
|
||||
if ( ! isset( $events[ $d ] ) ) $events[ $d ] = array();
|
||||
if ( ! in_array( 'start', $events[ $d ], true ) ) $events[ $d ][] = 'start';
|
||||
}
|
||||
|
||||
if ( ! empty( $end_raw ) ) {
|
||||
$d = substr( $end_raw, 0, 10 );
|
||||
if ( ! isset( $events[ $d ] ) ) $events[ $d ] = array();
|
||||
if ( ! in_array( 'end', $events[ $d ], true ) ) $events[ $d ][] = 'end';
|
||||
}
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query slides for a given date and optional site.
|
||||
*
|
||||
* @param string $date Date in YYYY-MM-DD format.
|
||||
* @param string $site_slug Optional site slug.
|
||||
* @return WP_Post[]
|
||||
*/
|
||||
function hssm_get_preview_slides_for_date( $date, $site_slug = '' ) {
|
||||
$meta_query = array(
|
||||
'relation' => 'AND',
|
||||
array(
|
||||
'key' => '_hssm_start_date',
|
||||
'value' => $date,
|
||||
'compare' => '<=',
|
||||
),
|
||||
array(
|
||||
'relation' => 'OR',
|
||||
array(
|
||||
'key' => '_hssm_end_date',
|
||||
'value' => $date,
|
||||
'compare' => '>=',
|
||||
),
|
||||
array(
|
||||
'key' => '_hssm_end_date',
|
||||
'compare' => 'NOT EXISTS',
|
||||
),
|
||||
array(
|
||||
'key' => '_hssm_end_date',
|
||||
'value' => '',
|
||||
'compare' => '=',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$tax_query = array();
|
||||
|
||||
if ( ! empty( $site_slug ) ) {
|
||||
$site_term = get_term_by( 'slug', $site_slug, HSSM_Slides_CPT::SITE_TAXONOMY );
|
||||
if ( $site_term && ! is_wp_error( $site_term ) ) {
|
||||
$tax_query[] = array(
|
||||
'taxonomy' => HSSM_Slides_CPT::SITE_TAXONOMY,
|
||||
'field' => 'term_id',
|
||||
'terms' => $site_term->term_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( count( $tax_query ) > 1 ) {
|
||||
$tax_query['relation'] = 'AND';
|
||||
}
|
||||
|
||||
$args = array(
|
||||
'post_type' => HSSM_Slides_CPT::POST_TYPE,
|
||||
'post_status' => array( 'publish', 'future', 'pending' ),
|
||||
'posts_per_page' => -1,
|
||||
'orderby' => 'meta_value_num',
|
||||
'meta_key' => '_hssm_order',
|
||||
'order' => 'ASC',
|
||||
'meta_query' => $meta_query,
|
||||
);
|
||||
|
||||
if ( ! empty( $tax_query ) ) {
|
||||
$args['tax_query'] = $tax_query;
|
||||
}
|
||||
|
||||
return get_posts( $args );
|
||||
}
|
||||
|
||||
$request = hssm_get_preview_request();
|
||||
$sites = hssm_get_preview_sites();
|
||||
$events = hssm_get_calendar_events( $request['site_slug'] );
|
||||
|
||||
$current_site_name = '';
|
||||
if ( ! empty( $request['site_slug'] ) ) {
|
||||
foreach ( $sites as $site ) {
|
||||
if ( $site['slug'] === $request['site_slug'] ) {
|
||||
$current_site_name = $site['name'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $current_site_name ) && ! empty( $sites ) ) {
|
||||
$current_site_name = __( 'All Sites', 'hi-school-slide-manager' );
|
||||
}
|
||||
?>
|
||||
|
||||
<script type="text/javascript">
|
||||
var hssmEvents = <?php echo json_encode( $events ); ?>;
|
||||
</script>
|
||||
|
||||
<div id="main-content">
|
||||
<div class="container-fluid">
|
||||
<div id="content-area" class="clearfix full-width">
|
||||
|
||||
<?php
|
||||
// Render the live slider preview using the modular shortcode
|
||||
echo do_shortcode( sprintf(
|
||||
'[hssm_slider site="%s" date="%s"]',
|
||||
esc_attr( $request['site_slug'] ),
|
||||
esc_attr( $request['date'] )
|
||||
) );
|
||||
|
||||
// Normal page content (Divi builder, header, etc.).
|
||||
if ( have_posts() ) :
|
||||
while ( have_posts() ) :
|
||||
the_post();
|
||||
// the_content(); // We might not want the_content if we're replacing it with the shortcode,
|
||||
// but let's keep it in case there's other content.
|
||||
endwhile;
|
||||
endif;
|
||||
?>
|
||||
|
||||
<div class="hssm-dashboard-main hssm-mt-20">
|
||||
<div class="hssm-tab-header">
|
||||
<h2>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: 1: date, 2: site name */
|
||||
esc_html__( 'Slide Preview for %1$s (%2$s)', 'hi-school-slide-manager' ),
|
||||
esc_html( date_i18n( get_option( 'date_format' ), strtotime( $request['date'] ) ) ),
|
||||
esc_html( $current_site_name )
|
||||
);
|
||||
?>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<form method="get" class="hssm-filters hssm-mt-20">
|
||||
<div>
|
||||
<label for="hssm_date"><strong><?php esc_html_e( 'Preview Date', 'hi-school-slide-manager' ); ?></strong></label><br>
|
||||
<input type="text" id="hssm_date" name="hssm_date" class="hssm-input" value="<?php echo esc_attr( $request['date'] ); ?>" autocomplete="off" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="hssm_site"><strong><?php esc_html_e( 'Site', 'hi-school-slide-manager' ); ?></strong></label><br>
|
||||
<select id="hssm_site" name="hssm_site" class="hssm-select">
|
||||
<option value=""><?php esc_html_e( 'All Sites', 'hi-school-slide-manager' ); ?></option>
|
||||
<?php foreach ( $sites as $site ) : ?>
|
||||
<option value="<?php echo esc_attr( $site['slug'] ); ?>" <?php selected( $site['slug'], $request['site_slug'] ); ?>>
|
||||
<?php echo esc_html( $site['name'] ); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label> </label><br>
|
||||
<label class="hssm-checkbox-label" style="display: inline-block; margin-top: 5px;">
|
||||
<input type="checkbox" name="hssm_past" value="1" <?php checked( $request['show_past'] ); ?>>
|
||||
<?php esc_html_e( 'Show Previously Published', 'hi-school-slide-manager' ); ?>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label> </label><br>
|
||||
<button type="submit" class="hssm-btn hssm-btn-primary">
|
||||
<span class="dashicons dashicons-filter"></span>
|
||||
<?php esc_html_e( 'Apply Filters', 'hi-school-slide-manager' ); ?>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="hssm-slides-list hssm-mt-20">
|
||||
<?php
|
||||
$slides = hssm_get_preview_slides_for_date( $request['date'], $request['site_slug'] );
|
||||
|
||||
if ( empty( $slides ) ) :
|
||||
?>
|
||||
<div class="hssm-empty-state">
|
||||
<div class="hssm-empty-icon dashicons dashicons-visibility"></div>
|
||||
<h3><?php esc_html_e( 'No slides scheduled for this date/site.', 'hi-school-slide-manager' ); ?></h3>
|
||||
<p><?php esc_html_e( 'Choose another date or site above to see upcoming slides.', 'hi-school-slide-manager' ); ?></p>
|
||||
</div>
|
||||
<?php
|
||||
else :
|
||||
foreach ( $slides as $slide ) :
|
||||
$subtitle = get_post_meta( $slide->ID, '_hssm_subtitle', true );
|
||||
$start_date = get_post_meta( $slide->ID, '_hssm_start_date', true );
|
||||
$end_date = get_post_meta( $slide->ID, '_hssm_end_date', true );
|
||||
$sites_terms = wp_get_post_terms( $slide->ID, HSSM_Slides_CPT::SITE_TAXONOMY );
|
||||
$site_names = array();
|
||||
if ( ! is_wp_error( $sites_terms ) && ! empty( $sites_terms ) ) {
|
||||
foreach ( $sites_terms as $term ) {
|
||||
$site_names[] = $term->name;
|
||||
}
|
||||
}
|
||||
$thumb_url = get_the_post_thumbnail_url( $slide->ID, 'thumbnail' );
|
||||
?>
|
||||
<div class="hssm-slide-item">
|
||||
<div class="hssm-slide-info">
|
||||
<div class="hssm-list-slide-title">
|
||||
<?php echo esc_html( get_the_title( $slide ) ); ?>
|
||||
</div>
|
||||
<?php if ( ! empty( $subtitle ) ) : ?>
|
||||
<div class="hssm-slide-subtitle">
|
||||
<?php echo esc_html( $subtitle ); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="hssm-slide-meta">
|
||||
<?php if ( $slide->post_status === 'pending' ) : ?>
|
||||
<span class="hssm-status-badge hssm-status-pending"><?php esc_html_e( 'Pending Approval', 'hi-school-slide-manager' ); ?></span><br>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( ! empty( $site_names ) ) : ?>
|
||||
<span><?php esc_html_e( 'Sites:', 'hi-school-slide-manager' ); ?> <?php echo esc_html( implode( ', ', $site_names ) ); ?></span><br>
|
||||
<?php endif; ?>
|
||||
<?php if ( ! empty( $start_date ) ) : ?>
|
||||
<span>
|
||||
<?php esc_html_e( 'Start:', 'hi-school-slide-manager' ); ?>
|
||||
<?php echo esc_html( date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), strtotime( $start_date ) ) ); ?>
|
||||
</span><br>
|
||||
<?php endif; ?>
|
||||
<?php if ( ! empty( $end_date ) ) : ?>
|
||||
<span>
|
||||
<?php esc_html_e( 'End:', 'hi-school-slide-manager' ); ?>
|
||||
<?php echo esc_html( date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), strtotime( $end_date ) ) ); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ( $slide->post_status === 'pending' && current_user_can( 'edit_others_posts' ) ) : ?>
|
||||
<div class="hssm-slide-actions hssm-mt-10">
|
||||
<button type="button" class="hssm-btn hssm-btn-success hssm-approve-slide" data-id="<?php echo esc_attr( $slide->ID ); ?>">
|
||||
<span class="dashicons dashicons-yes"></span> <?php esc_html_e( 'Approve Slide', 'hi-school-slide-manager' ); ?>
|
||||
</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ( $thumb_url ) : ?>
|
||||
<div class="hssm-slide-thumbnail" style="background-image:url('<?php echo esc_url( $thumb_url ); ?>');">
|
||||
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
endforeach;
|
||||
endif;
|
||||
?>
|
||||
</div>
|
||||
|
||||
<div class="hssm-preview-actions hssm-mt-20" style="text-align: center; margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee;">
|
||||
<button id="email-preview" class="hssm-btn hssm-btn-secondary">
|
||||
<span class="dashicons dashicons-email"></span> <?php esc_html_e( 'Email Preview', 'hi-school-slide-manager' ); ?>
|
||||
</button>
|
||||
<button id="download-screenshot" class="hssm-btn hssm-btn-secondary">
|
||||
<span class="dashicons dashicons-download"></span> <?php esc_html_e( 'Download Screenshot', 'hi-school-slide-manager' ); ?>
|
||||
</button>
|
||||
<button id="copy-preview-link" class="hssm-btn hssm-btn-secondary">
|
||||
<span class="dashicons dashicons-admin-links"></span> <?php esc_html_e( 'Copy Link', 'hi-school-slide-manager' ); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- #content-area -->
|
||||
</div><!-- .container-fluid -->
|
||||
</div><!-- #main-content -->
|
||||
|
||||
<?php
|
||||
get_footer();
|
||||
|
||||
Reference in New Issue
Block a user