388 lines
14 KiB
PHP
388 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* ==========================
|
|
* 🔐 IGNY8 FILE RULE HEADER
|
|
* ==========================
|
|
* @file : meta-boxes.php
|
|
* @location : /core/admin/meta-boxes.php
|
|
* @type : Function Library
|
|
* @scope : Global
|
|
* @allowed : Meta box registration, SEO field management
|
|
* @reusability : Globally Reusable
|
|
* @notes : SEO meta boxes for post editor
|
|
*/
|
|
|
|
// Prevent direct access
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
// === SEO Meta Box ===
|
|
add_action('add_meta_boxes', function() {
|
|
// SEO fields
|
|
add_meta_box('igny8_seo_meta', 'Igny8 SEO Fields', function($post) {
|
|
$meta_title = get_post_meta($post->ID, '_igny8_meta_title', true);
|
|
$meta_desc = get_post_meta($post->ID, '_igny8_meta_description', true);
|
|
$primary_kw = get_post_meta($post->ID, '_igny8_primary_keywords', true);
|
|
$secondary_kw = get_post_meta($post->ID, '_igny8_secondary_keywords', true);
|
|
?>
|
|
<div style="padding:8px 4px;">
|
|
<label><strong>Meta Title:</strong></label><br>
|
|
<input type="text" name="_igny8_meta_title" value="<?php echo esc_attr($meta_title); ?>" style="width:100%;"><br><br>
|
|
|
|
<label><strong>Meta Description:</strong></label><br>
|
|
<textarea name="_igny8_meta_description" rows="3" style="width:100%;"><?php echo esc_textarea($meta_desc); ?></textarea><br><br>
|
|
|
|
<label><strong>Primary Keyword:</strong></label><br>
|
|
<input type="text" name="_igny8_primary_keywords" value="<?php echo esc_attr($primary_kw); ?>" style="width:100%;"><br><br>
|
|
|
|
<label><strong>Secondary Keywords (comma-separated):</strong></label><br>
|
|
<input type="text" name="_igny8_secondary_keywords" value="<?php echo esc_attr($secondary_kw); ?>" style="width:100%;">
|
|
</div>
|
|
<?php
|
|
}, ['post','page','product'], 'normal', 'high');
|
|
|
|
|
|
});
|
|
|
|
|
|
// === Save Meta Box Data ===
|
|
add_action('save_post', function($post_id) {
|
|
// Security checks
|
|
if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) {
|
|
return;
|
|
}
|
|
|
|
// Save SEO fields
|
|
$fields = [
|
|
'_igny8_meta_title',
|
|
'_igny8_meta_description',
|
|
'_igny8_primary_keywords',
|
|
'_igny8_secondary_keywords',
|
|
];
|
|
foreach ($fields as $field) {
|
|
if (isset($_POST[$field])) {
|
|
update_post_meta($post_id, $field, sanitize_text_field($_POST[$field]));
|
|
}
|
|
}
|
|
|
|
|
|
if (defined('WP_DEBUG') && WP_DEBUG) {
|
|
error_log("Igny8 Metabox: SEO fields saved for post $post_id");
|
|
}
|
|
});
|
|
|
|
// === In-Article Image Gallery Meta Box ===
|
|
add_action('add_meta_boxes', function() {
|
|
$enabled_post_types = get_option('igny8_enable_image_metabox', []);
|
|
foreach ((array) $enabled_post_types as $pt) {
|
|
add_meta_box(
|
|
'igny8_image_gallery',
|
|
'Igny8 In-Article Images',
|
|
'igny8_render_image_gallery_metabox',
|
|
$pt,
|
|
'side',
|
|
'high'
|
|
);
|
|
}
|
|
});
|
|
|
|
function igny8_render_image_gallery_metabox($post) {
|
|
wp_nonce_field('igny8_save_image_gallery', 'igny8_image_gallery_nonce');
|
|
$images = get_post_meta($post->ID, '_igny8_inarticle_images', true);
|
|
if (!is_array($images)) $images = [];
|
|
|
|
// Add CSS for grid layout and remove button
|
|
?>
|
|
<style>
|
|
.igny8-image-list {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
gap: 5px;
|
|
}
|
|
.igny8-image-list li {
|
|
width: 200px;
|
|
margin: 0 5px 5px 0;
|
|
box-sizing: border-box;
|
|
text-align: center;
|
|
position: relative;
|
|
border: 1px solid #eee;
|
|
padding: 5px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-height: 180px;
|
|
}
|
|
.igny8-image-list li img {
|
|
display: block;
|
|
margin: 0 auto 5px auto;
|
|
}
|
|
.igny8-image-actions {
|
|
position: absolute;
|
|
bottom: 5px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
display: flex;
|
|
gap: 5px;
|
|
opacity: 0;
|
|
transition: opacity 0.2s ease;
|
|
z-index: 10;
|
|
}
|
|
.igny8-image-list li:hover .igny8-image-actions {
|
|
opacity: 1;
|
|
}
|
|
.igny8-remove-btn, .igny8-replace-btn {
|
|
background: #f0f0f0;
|
|
border: 1px solid #ccc;
|
|
color: #333;
|
|
font-size: 10px;
|
|
padding: 2px 6px;
|
|
cursor: pointer;
|
|
border-radius: 2px;
|
|
text-decoration: none;
|
|
white-space: nowrap;
|
|
}
|
|
.igny8-remove-btn:hover {
|
|
background: #dc3232;
|
|
color: #fff;
|
|
border-color: #dc3232;
|
|
}
|
|
.igny8-replace-btn:hover {
|
|
background: #0073aa;
|
|
color: #fff;
|
|
border-color: #0073aa;
|
|
}
|
|
.igny8-image-list li strong {
|
|
font-size: 0.8em;
|
|
word-break: break-all;
|
|
}
|
|
</style>
|
|
<?php
|
|
|
|
echo '<div id="igny8-image-gallery">';
|
|
echo '<p><label><input type="radio" name="igny8_image_type" value="desktop" checked> Desktop</label>
|
|
<label><input type="radio" name="igny8_image_type" value="mobile"> Mobile</label></p>';
|
|
echo '<button type="button" class="button button-primary" id="igny8-add-image">Add Image</button>';
|
|
|
|
|
|
|
|
|
|
echo '<ul class="igny8-image-list">';
|
|
|
|
// Sort images by device type and ID
|
|
$sorted_images = [];
|
|
foreach ($images as $label => $data) {
|
|
$sorted_images[$label] = $data;
|
|
}
|
|
|
|
// Custom sort function to order by device type (desktop first) then by ID
|
|
uksort($sorted_images, function($a, $b) {
|
|
$a_parts = explode('-', $a);
|
|
$b_parts = explode('-', $b);
|
|
|
|
$a_device = $a_parts[0];
|
|
$b_device = $b_parts[0];
|
|
|
|
// Desktop comes before mobile
|
|
if ($a_device === 'desktop' && $b_device === 'mobile') return -1;
|
|
if ($a_device === 'mobile' && $b_device === 'desktop') return 1;
|
|
|
|
// If same device, sort by ID number
|
|
if ($a_device === $b_device) {
|
|
$a_id = intval($a_parts[1]);
|
|
$b_id = intval($b_parts[1]);
|
|
return $a_id - $b_id;
|
|
}
|
|
|
|
return 0;
|
|
});
|
|
|
|
foreach ($sorted_images as $label => $data) {
|
|
echo '<li data-label="' . esc_attr($label) . '">';
|
|
echo '<strong>' . esc_html($label) . '</strong><br>';
|
|
echo wp_get_attachment_image($data['attachment_id'], 'thumbnail', false, ['style' => 'width: 150px; height: 150px; object-fit: cover;']);
|
|
echo '<div class="igny8-image-actions">';
|
|
echo '<button type="button" class="igny8-replace-btn">Replace</button>';
|
|
echo '<button type="button" class="igny8-remove-btn">Remove</button>';
|
|
echo '</div>';
|
|
echo '<input type="hidden" name="igny8_image_data[' . esc_attr($label) . '][attachment_id]" value="' . esc_attr($data['attachment_id']) . '">';
|
|
echo '<input type="hidden" name="igny8_image_data[' . esc_attr($label) . '][device]" value="' . esc_attr($data['device']) . '">';
|
|
echo '</li>';
|
|
}
|
|
echo '</ul>';
|
|
|
|
|
|
// Inline JS
|
|
?>
|
|
<script>
|
|
jQuery(document).ready(function($) {
|
|
// Function to get first available ID for a device type
|
|
function getFirstAvailableId(deviceType) {
|
|
let existingIds = [];
|
|
$('.igny8-image-list li').each(function() {
|
|
let label = $(this).data('label');
|
|
if (label && label.startsWith(deviceType + '-')) {
|
|
let id = parseInt(label.split('-')[1]);
|
|
if (!isNaN(id)) {
|
|
existingIds.push(id);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Sort existing IDs and find first gap
|
|
existingIds.sort((a, b) => a - b);
|
|
for (let i = 1; i <= existingIds.length + 1; i++) {
|
|
if (!existingIds.includes(i)) {
|
|
return i;
|
|
}
|
|
}
|
|
return 1; // Fallback
|
|
}
|
|
|
|
$('#igny8-add-image').on('click', function(e) {
|
|
e.preventDefault();
|
|
let type = $('input[name="igny8_image_type"]:checked').val() || 'desktop';
|
|
let availableId = getFirstAvailableId(type);
|
|
let label = type + '-' + availableId;
|
|
|
|
const frame = wp.media({
|
|
title: 'Select Image',
|
|
button: { text: 'Use this image' },
|
|
multiple: false
|
|
});
|
|
|
|
frame.on('select', function() {
|
|
let attachment = frame.state().get('selection').first().toJSON();
|
|
let html = '<li data-label="' + label + '">' +
|
|
'<strong>' + label + '</strong><br>' +
|
|
'<img src="' + attachment.sizes.thumbnail.url + '" style="width: 150px; height: 150px; object-fit: cover;" /><br>' +
|
|
'<div class="igny8-image-actions">' +
|
|
'<button type="button" class="igny8-replace-btn">Replace</button>' +
|
|
'<button type="button" class="igny8-remove-btn">Remove</button>' +
|
|
'</div>' +
|
|
'<input type="hidden" name="igny8_image_data[' + label + '][attachment_id]" value="' + attachment.id + '">' +
|
|
'<input type="hidden" name="igny8_image_data[' + label + '][device]" value="' + type + '">' +
|
|
'</li>';
|
|
$('.igny8-image-list').append(html);
|
|
});
|
|
|
|
frame.open();
|
|
});
|
|
|
|
// Handle image removal (event delegation for dynamically added elements)
|
|
$(document).on('click', '.igny8-remove-btn', function() {
|
|
$(this).closest('li').remove();
|
|
});
|
|
|
|
// Handle image replacement (event delegation for dynamically added elements)
|
|
$(document).on('click', '.igny8-replace-btn', function() {
|
|
let $li = $(this).closest('li');
|
|
let label = $li.data('label');
|
|
let type = label.split('-')[0];
|
|
|
|
const frame = wp.media({
|
|
title: 'Replace Image',
|
|
button: { text: 'Replace this image' },
|
|
multiple: false
|
|
});
|
|
|
|
frame.on('select', function() {
|
|
let attachment = frame.state().get('selection').first().toJSON();
|
|
|
|
// Replace the entire img element to force reload
|
|
let $img = $li.find('img');
|
|
let newImg = $('<img>').attr({
|
|
'src': attachment.sizes.thumbnail.url,
|
|
'style': 'width: 150px; height: 150px; object-fit: cover;'
|
|
});
|
|
$img.replaceWith(newImg);
|
|
|
|
// Update the hidden input
|
|
$li.find('input[name*="[attachment_id]"]').val(attachment.id);
|
|
});
|
|
|
|
frame.open();
|
|
});
|
|
|
|
// Handle Convert Content to Blocks
|
|
$('#igny8-convert-to-blocks').on('click', function(e) {
|
|
e.preventDefault();
|
|
|
|
if (!confirm('This will convert the post content from HTML to WordPress blocks. Continue?')) {
|
|
return;
|
|
}
|
|
|
|
let $button = $(this);
|
|
let originalText = $button.text();
|
|
$button.prop('disabled', true).text('Converting...');
|
|
|
|
// Get post ID from the current post
|
|
let postId = $('#post_ID').val();
|
|
|
|
// Make AJAX request to convert content to blocks
|
|
$.ajax({
|
|
url: ajaxurl,
|
|
type: 'POST',
|
|
data: {
|
|
action: 'igny8_convert_content_to_blocks',
|
|
post_id: postId,
|
|
nonce: '<?php echo wp_create_nonce('igny8_convert_to_blocks'); ?>'
|
|
},
|
|
success: function(response) {
|
|
console.log('Convert to Blocks Response:', response);
|
|
if (response.success) {
|
|
console.log('Success data:', response.data);
|
|
alert('Content converted successfully! ' + response.data.message);
|
|
location.reload();
|
|
} else {
|
|
console.error('Error data:', response.data);
|
|
alert('Error: ' + (response.data || 'Unknown error'));
|
|
}
|
|
},
|
|
error: function(xhr, status, error) {
|
|
console.error('Convert to Blocks Error:', {status, error, responseText: xhr.responseText});
|
|
alert('Error converting content. Check console for details.');
|
|
},
|
|
complete: function() {
|
|
$button.prop('disabled', false).text(originalText);
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
});
|
|
</script>
|
|
<?php
|
|
}
|
|
|
|
// SAVE HANDLER for Image Gallery
|
|
add_action('save_post', function($post_id) {
|
|
if (!isset($_POST['igny8_image_gallery_nonce']) || !wp_verify_nonce($_POST['igny8_image_gallery_nonce'], 'igny8_save_image_gallery')) return;
|
|
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
|
|
if (!current_user_can('edit_post', $post_id)) return;
|
|
|
|
$images = $_POST['igny8_image_data'] ?? [];
|
|
$filtered = [];
|
|
foreach ($images as $label => $data) {
|
|
if (!empty($data['attachment_id'])) {
|
|
$filtered[$label] = [
|
|
'label' => sanitize_text_field($label),
|
|
'attachment_id' => intval($data['attachment_id']),
|
|
'url' => wp_get_attachment_url(intval($data['attachment_id'])),
|
|
'device' => sanitize_text_field($data['device'])
|
|
];
|
|
}
|
|
}
|
|
update_post_meta($post_id, '_igny8_inarticle_images', $filtered);
|
|
|
|
if (WP_DEBUG === true) {
|
|
error_log("[IGNY8 DEBUG] Saving In-Article Images for Post ID: $post_id");
|
|
error_log(print_r($filtered, true));
|
|
}
|
|
});
|
|
|