From 07a33520fc12f39f4bd3935c085fbb2a974f2165 Mon Sep 17 00:00:00 2001 From: Jonas Date: Thu, 26 Feb 2026 23:28:21 +0100 Subject: [PATCH] feat: Implement posts management functionality in the admin panel. --- .DS_Store | Bin 6148 -> 6148 bytes admin/.DS_Store | Bin 6148 -> 6148 bytes admin/css/admin-style.css | 492 ++++++++++++++++++++++++++++++- admin/js/admin-script.js | 225 +++++++++++++- admin/views/tab-posts.php | 438 +++++++++++++++++++++++++++ includes/class-admin.php | 139 ++++++++- includes/class-database.php | 121 +++++++- includes/class-image-handler.php | 60 ++++ public/.DS_Store | Bin 6148 -> 6148 bytes 9 files changed, 1450 insertions(+), 25 deletions(-) create mode 100644 admin/views/tab-posts.php diff --git a/.DS_Store b/.DS_Store index 46e025f462388f627e917e42e3168dea02bffd29..71808ecc1b39481564dc7f2f1c3b463070b62d00 100644 GIT binary patch delta 187 zcmZoMXffEJ%2Lmq&&|NVz`~%%kj{|FP?DSP;*yk;p9B=+$o;D^D|GU4M^yO~yz&JZ zhQZ1CxdlKyKoh_uD?=hf3PUc?^t_yOBv~eod<9Z}^|@X8lt z7zQWj=N16aY!4TF>Oa|=KM3=DCnCQo7Vs|WK~8Oj(k z8B!U_(G)Q{cmP!*$>!#}xFqG|Cjmt{zCMp$;zb delta 188 zcmZoMXffEJ!o*}OIa!SaY!4TF>Oa|=KM3=FoPCQo7Vs}BINSsBV0 zG8s}C%Fz{F%SM&W&3AE0%E?axigHZ7)ar43!f{7*6)6N%6l5SdgOPD_A@ekri3R+d J**X650|1zoFKYk* diff --git a/admin/css/admin-style.css b/admin/css/admin-style.css index 36ce707..03fa3fd 100644 --- a/admin/css/admin-style.css +++ b/admin/css/admin-style.css @@ -18,7 +18,7 @@ --igsp-gray: #6c757d; --igsp-light: #f8f9fa; --igsp-border: #e2e4e7; - --igsp-shadow: 0 2px 8px rgba(0,0,0,0.08); + --igsp-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); --igsp-radius: 8px; } @@ -414,8 +414,15 @@ } @keyframes igsp-progress-pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.7; } + + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.7; + } } .igsp-progress-text { @@ -515,8 +522,13 @@ top: 0; } -.igsp-log-type { width: 100px; } -.igsp-log-date { width: 120px; } +.igsp-log-type { + width: 100px; +} + +.igsp-log-date { + width: 120px; +} .igsp-log-badge { display: inline-block; @@ -527,10 +539,25 @@ text-transform: uppercase; } -.igsp-log-success { background: rgba(40, 167, 69, 0.1); color: var(--igsp-success); } -.igsp-log-error { background: rgba(220, 53, 69, 0.1); color: var(--igsp-error); } -.igsp-log-warning { background: rgba(255, 193, 7, 0.1); color: #856404; } -.igsp-log-info { background: rgba(23, 162, 184, 0.1); color: var(--igsp-info); } +.igsp-log-success { + background: rgba(40, 167, 69, 0.1); + color: var(--igsp-success); +} + +.igsp-log-error { + background: rgba(220, 53, 69, 0.1); + color: var(--igsp-error); +} + +.igsp-log-warning { + background: rgba(255, 193, 7, 0.1); + color: #856404; +} + +.igsp-log-info { + background: rgba(23, 162, 184, 0.1); + color: var(--igsp-info); +} .igsp-no-logs { padding: 40px; @@ -614,12 +641,12 @@ .igsp-admin-wrap { grid-template-columns: 1fr; } - + .igsp-admin-sidebar { flex-direction: row; flex-wrap: wrap; } - + .igsp-sidebar-box { flex: 1; min-width: 250px; @@ -631,19 +658,456 @@ flex-wrap: wrap; padding: 10px; } - + .igsp-tab-link { flex: 1; justify-content: center; padding: 10px; font-size: 12px; } - + .igsp-tab-link .dashicons { display: none; } - + .igsp-layout-selector { grid-template-columns: repeat(2, 1fr); } } + +/* ============================================ + Posts Management Tab + ============================================ */ + +/* Posts Header */ +.igsp-posts-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + flex-wrap: wrap; + gap: 10px; +} + +.igsp-posts-header h2 { + margin: 0 !important; + padding: 0 !important; + border: none !important; +} + +.igsp-post-count { + font-weight: 400; + color: var(--igsp-gray); + font-size: 14px; +} + +/* Search */ +.igsp-search-wrapper { + display: flex; + gap: 5px; +} + +.igsp-search-input { + min-width: 220px; + padding: 6px 12px; + border: 1px solid var(--igsp-border); + border-radius: var(--igsp-radius); +} + +/* Posts Table */ +.igsp-posts-table-wrapper { + border: 1px solid var(--igsp-border); + border-radius: var(--igsp-radius); + overflow: hidden; +} + +.igsp-posts-table { + margin: 0 !important; + border: none !important; +} + +.igsp-posts-table th { + background: var(--igsp-light); + font-weight: 600; + font-size: 13px; + padding: 12px 15px; +} + +.igsp-posts-table td { + padding: 10px 15px; + vertical-align: middle; +} + +.igsp-col-thumb { + width: 70px; +} + +.igsp-col-type { + width: 80px; +} + +.igsp-col-status { + width: 80px; +} + +.igsp-col-link { + width: 50px; + text-align: center; +} + +.igsp-col-date { + width: 150px; +} + +.igsp-col-actions { + width: 100px; +} + +/* Post Thumbnail */ +.igsp-post-thumb { + width: 50px; + height: 50px; + object-fit: cover; + border-radius: 6px; + display: block; +} + +.igsp-post-thumb-placeholder { + width: 50px; + height: 50px; + background: var(--igsp-light); + border-radius: 6px; + display: flex; + align-items: center; + justify-content: center; + color: var(--igsp-gray); +} + +.igsp-post-thumb-placeholder .dashicons { + font-size: 24px; + width: 24px; + height: 24px; +} + +/* Caption */ +.igsp-caption-text { + font-size: 13px; + color: var(--igsp-dark); + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +/* Post Link */ +.igsp-post-link { + color: var(--igsp-primary); + text-decoration: none; +} + +.igsp-post-link:hover { + color: var(--igsp-primary-hover); +} + +.igsp-post-link .dashicons { + font-size: 18px; + width: 18px; + height: 18px; +} + +.igsp-no-link { + color: var(--igsp-gray); +} + +/* Type Badges */ +.igsp-type-badge { + display: inline-block; + padding: 3px 8px; + border-radius: 3px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; +} + +.igsp-type-synced { + background: rgba(23, 162, 184, 0.1); + color: var(--igsp-info); +} + +.igsp-type-manual { + background: rgba(225, 48, 108, 0.1); + color: var(--igsp-primary); +} + +/* Status Badges */ +.igsp-status-badge { + display: inline-block; + padding: 3px 8px; + border-radius: 3px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; +} + +.igsp-status-active { + background: rgba(40, 167, 69, 0.1); + color: var(--igsp-success); +} + +.igsp-status-inactive { + background: rgba(220, 53, 69, 0.1); + color: var(--igsp-error); +} + +/* Inactive Row */ +.igsp-post-inactive { + opacity: 0.6; +} + +.igsp-post-inactive:hover { + opacity: 1; +} + +/* Action Buttons */ +.igsp-action-buttons { + display: flex; + gap: 5px; +} + +.igsp-action-buttons .button { + padding: 2px 6px; + min-height: 28px; + display: flex; + align-items: center; + justify-content: center; +} + +.igsp-action-buttons .dashicons { + font-size: 16px; + width: 16px; + height: 16px; +} + +.igsp-delete-post { + color: var(--igsp-error) !important; + border-color: var(--igsp-error) !important; +} + +.igsp-delete-post:hover { + background: var(--igsp-error) !important; + color: #fff !important; +} + +/* Post Date */ +.igsp-post-date { + font-size: 12px; + color: var(--igsp-gray); +} + +/* No Posts */ +.igsp-no-posts { + padding: 40px; + text-align: center; + color: var(--igsp-gray); + background: var(--igsp-light); + border-radius: var(--igsp-radius); +} + +/* ============================================ + Pagination + ============================================ */ +.igsp-pagination { + display: flex; + align-items: center; + gap: 5px; + margin-top: 20px; + flex-wrap: wrap; +} + +.igsp-pagination .button { + min-width: 36px; + text-align: center; +} + +.igsp-pagination-info { + margin-left: 15px; + font-size: 13px; + color: var(--igsp-gray); +} + +/* ============================================ + Manual Post Form + ============================================ */ +.igsp-manual-post-form { + background: var(--igsp-light); + border-radius: var(--igsp-radius); + padding: 25px; +} + +.igsp-manual-post-fields { + display: grid; + grid-template-columns: auto 1fr; + gap: 20px; + align-items: start; +} + +.igsp-manual-field { + display: flex; + flex-direction: column; + gap: 6px; +} + +.igsp-manual-field label { + font-weight: 600; + font-size: 13px; + color: var(--igsp-dark); +} + +.igsp-manual-field .required { + color: var(--igsp-error); +} + +.igsp-manual-image-field { + grid-row: span 4; +} + +/* Image Upload Area */ +.igsp-image-upload-area { + width: 200px; + height: 200px; + border: 2px dashed var(--igsp-border); + border-radius: var(--igsp-radius); + cursor: pointer; + position: relative; + overflow: hidden; + transition: border-color 0.2s; + background: #fff; +} + +.igsp-image-upload-area:hover { + border-color: var(--igsp-primary); +} + +.igsp-upload-placeholder { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + gap: 8px; + color: var(--igsp-gray); +} + +.igsp-upload-placeholder .dashicons { + font-size: 40px; + width: 40px; + height: 40px; + color: var(--igsp-border); +} + +.igsp-upload-placeholder span:last-child { + font-size: 13px; + font-weight: 500; +} + +/* Select Image Button */ +#igsp-select-image-btn { + margin-top: 10px; + display: inline-flex; + align-items: center; + gap: 6px; + width: 200px; + justify-content: center; +} + +#igsp-select-image-btn .dashicons { + font-size: 16px; + width: 16px; + height: 16px; +} + +/* Image Preview */ +.igsp-image-preview { + width: 100%; + height: 100%; + position: relative; +} + +.igsp-image-preview img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.igsp-remove-image { + position: absolute; + top: 5px; + right: 5px; + background: rgba(220, 53, 69, 0.9); + color: #fff; + border: none; + border-radius: 50%; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + padding: 0; + transition: background 0.2s; +} + +.igsp-remove-image:hover { + background: var(--igsp-error); +} + +.igsp-remove-image .dashicons { + font-size: 16px; + width: 16px; + height: 16px; +} + +/* Manual Post Actions */ +.igsp-manual-post-actions { + margin-top: 20px; + display: flex; + align-items: center; + gap: 15px; +} + +.igsp-manual-post-actions .button { + display: flex; + align-items: center; + gap: 6px; +} + +.igsp-manual-post-actions .dashicons { + font-size: 16px; + width: 16px; + height: 16px; +} + +@media (max-width: 782px) { + .igsp-manual-post-fields { + grid-template-columns: 1fr; + } + + .igsp-manual-image-field { + grid-row: auto; + } + + .igsp-image-upload-area { + width: 100%; + height: 150px; + } + + .igsp-posts-header { + flex-direction: column; + align-items: flex-start; + } + + .igsp-col-date, + .igsp-col-type { + display: none; + } +} \ No newline at end of file diff --git a/admin/js/admin-script.js b/admin/js/admin-script.js index d470527..167cd26 100644 --- a/admin/js/admin-script.js +++ b/admin/js/admin-script.js @@ -25,18 +25,27 @@ this.initSyncButton(); this.initToolButtons(); this.initTabPersistence(); + this.initPostsTab(); + this.initManualPostForm(); }, /** * Initialize color pickers */ initColorPickers: function () { - $('.igsp-color-picker').wpColorPicker({ - change: function (event, ui) { - // Update live preview - IGSPAdmin.updatePreview(); - } - }); + if ($('.igsp-color-picker').length === 0) { + return; + } + try { + $('.igsp-color-picker').wpColorPicker({ + change: function (event, ui) { + // Update live preview + IGSPAdmin.updatePreview(); + } + }); + } catch (e) { + // Color picker not available on this tab + } }, /** @@ -233,6 +242,210 @@ }); }, + /** + * Initialize Posts Tab functionality + */ + initPostsTab: function () { + // Toggle post active/inactive + $(document).on('click', '.igsp-toggle-post', function () { + const $button = $(this); + const postId = $button.data('post-id'); + const $row = $button.closest('.igsp-post-row'); + + $button.prop('disabled', true); + + $.post(igspAdmin.ajaxUrl, { + action: 'igsp_toggle_post', + nonce: igspAdmin.nonce, + post_id: postId + }, function (response) { + if (response.success) { + const isActive = response.data.is_active; + const $icon = $button.find('.dashicons'); + const $badge = $row.find('.igsp-status-badge'); + + if (isActive) { + $row.removeClass('igsp-post-inactive'); + $icon.removeClass('dashicons-visibility').addClass('dashicons-hidden'); + $badge.removeClass('igsp-status-inactive').addClass('igsp-status-active').text('Aktiv'); + $button.attr('title', 'Deaktivieren'); + } else { + $row.addClass('igsp-post-inactive'); + $icon.removeClass('dashicons-hidden').addClass('dashicons-visibility'); + $badge.removeClass('igsp-status-active').addClass('igsp-status-inactive').text('Inaktiv'); + $button.attr('title', 'Aktivieren'); + } + } + $button.prop('disabled', false); + }).fail(function () { + $button.prop('disabled', false); + }); + }); + + // Delete post + $(document).on('click', '.igsp-delete-post', function () { + if (!confirm(igspAdmin.strings.confirmDeletePost)) { + return; + } + + const $button = $(this); + const postId = $button.data('post-id'); + const $row = $button.closest('.igsp-post-row'); + + $button.prop('disabled', true); + + $.post(igspAdmin.ajaxUrl, { + action: 'igsp_delete_post', + nonce: igspAdmin.nonce, + post_id: postId + }, function (response) { + if (response.success) { + $row.fadeOut(300, function () { + $(this).remove(); + // Update count + const $count = $('.igsp-post-count'); + const currentCount = parseInt($count.text().replace(/\D/g, '')) || 0; + $count.text('(' + Math.max(0, currentCount - 1) + ')'); + }); + } else { + $button.prop('disabled', false); + } + }).fail(function () { + $button.prop('disabled', false); + }); + }); + }, + + /** + * Initialize Manual Post Form + */ + initManualPostForm: function () { + // Only init if the button exists on this page + if ($('#igsp-select-image-btn').length === 0) { + return; + } + + let mediaFrame = null; + let selectedAttachmentId = 0; + + // Open media library via button + $('#igsp-select-image-btn').on('click', function (e) { + e.preventDefault(); + e.stopPropagation(); + + // Check if wp.media is available + if (typeof wp === 'undefined' || typeof wp.media === 'undefined') { + alert('WordPress Media Library ist nicht verfügbar. Bitte lade die Seite neu.'); + return; + } + + // If media frame already exists, reopen it + if (mediaFrame) { + mediaFrame.open(); + return; + } + + // Create media frame + mediaFrame = wp.media({ + title: igspAdmin.strings.selectImage, + button: { + text: igspAdmin.strings.useImage + }, + multiple: false, + library: { + type: 'image' + } + }); + + // When image is selected + mediaFrame.on('select', function () { + const attachment = mediaFrame.state().get('selection').first().toJSON(); + + selectedAttachmentId = attachment.id; + $('#igsp-attachment-id').val(attachment.id); + + // Show preview + const previewUrl = attachment.sizes && attachment.sizes.medium + ? attachment.sizes.medium.url + : attachment.url; + + $('#igsp-preview-img').attr('src', previewUrl); + $('#igsp-image-preview').show(); + $('#igsp-upload-placeholder').hide(); + }); + + mediaFrame.open(); + }); + + // Remove image + $('#igsp-remove-image').on('click', function (e) { + e.stopPropagation(); + selectedAttachmentId = 0; + $('#igsp-attachment-id').val(''); + $('#igsp-preview-img').attr('src', ''); + $('#igsp-image-preview').hide(); + $('#igsp-upload-placeholder').show(); + }); + + // Submit manual post + $('#igsp-add-manual-post').on('click', function () { + const $button = $(this); + const $result = $('#igsp-manual-post-result'); + const attachmentId = $('#igsp-attachment-id').val(); + + if (!attachmentId) { + $result.removeClass('success').addClass('error').text('Bitte wähle ein Bild aus.').show(); + return; + } + + $button.prop('disabled', true); + $result.hide(); + + // Convert datetime-local to MySQL format + let postedAt = $('#igsp-manual-date').val(); + if (postedAt) { + postedAt = postedAt.replace('T', ' ') + ':00'; + } + + $.post(igspAdmin.ajaxUrl, { + action: 'igsp_add_manual_post', + nonce: igspAdmin.nonce, + attachment_id: attachmentId, + post_url: $('#igsp-manual-url').val(), + caption: $('#igsp-manual-caption').val(), + posted_at: postedAt + }, function (response) { + $result.removeClass('success error'); + + if (response.success) { + $result.addClass('success').text(response.data.message).show(); + + // Reset form + selectedAttachmentId = 0; + $('#igsp-attachment-id').val(''); + $('#igsp-preview-img').attr('src', ''); + $('#igsp-image-preview').hide(); + $('#igsp-upload-placeholder').show(); + $('#igsp-manual-url').val(''); + $('#igsp-manual-caption').val(''); + $('#igsp-manual-date').val(''); + + // Reload after short delay to show new post in table + setTimeout(function () { + location.reload(); + }, 1500); + } else { + $result.addClass('error').text(response.data.message).show(); + } + + $button.prop('disabled', false); + }).fail(function () { + $result.addClass('error').text('Ein Fehler ist aufgetreten.').show(); + $button.prop('disabled', false); + }); + }); + }, + /** * Update sync status */ diff --git a/admin/views/tab-posts.php b/admin/views/tab-posts.php new file mode 100644 index 0000000..c8845ba --- /dev/null +++ b/admin/views/tab-posts.php @@ -0,0 +1,438 @@ +count_posts_filtered(array( + 'active' => null, + 'search' => $search, +)); + +$posts = $database->get_posts(array( + 'limit' => $per_page, + 'offset' => $offset, + 'order' => 'newest', + 'active' => false, // Show all posts (active + inactive) + 'search' => $search, +)); + +$total_pages = ceil($total_posts / $per_page); +?> + + +
+

+ +

+

+ +

+ +
+
+
+ +
+ +
+ + +
+ +
+ +
+ +
+ + +

+ +

+
+ +
+ + +
+ +
+ + +

+ +

+
+
+ +
+ + +
+
+
+ + +
+
+

+ + ( + ) + +

+ +
+ + +
+ + +
+
+
+ + +
+ +

+ +

+ +

+ +

+ +
+ +
+ + + + + + + + + + + + + + image_thumbnail_path)) { + $thumb_url = $image_handler->get_image_url($post->image_thumbnail_path); + } elseif (!empty($post->image_local_path)) { + $thumb_url = $image_handler->get_image_url($post->image_local_path); + } + + $is_manual = !empty($post->is_manual); + $is_active = !empty($post->is_active); + $caption_short = !empty($post->caption) ? wp_trim_words($post->caption, 12, '...') : '—'; + $posted_date = !empty($post->posted_at) ? date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($post->posted_at)) : '—'; + ?> + + + + + + + + + + + +
+ + + + + + + + + + + + + +
+ + + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+ + 1): ?> +
+ + + + + + + + + +
+ + +
+ + \ No newline at end of file diff --git a/includes/class-admin.php b/includes/class-admin.php index 1873182..5e1671c 100644 --- a/includes/class-admin.php +++ b/includes/class-admin.php @@ -47,6 +47,9 @@ class IGSP_Admin add_action('wp_ajax_igsp_reset_data', array($this, 'ajax_reset_data')); add_action('wp_ajax_igsp_get_sync_status', array($this, 'ajax_get_sync_status')); add_action('wp_ajax_igsp_clear_logs', array($this, 'ajax_clear_logs')); + add_action('wp_ajax_igsp_delete_post', array($this, 'ajax_delete_post')); + add_action('wp_ajax_igsp_toggle_post', array($this, 'ajax_toggle_post')); + add_action('wp_ajax_igsp_add_manual_post', array($this, 'ajax_add_manual_post')); // Add settings link to plugins page add_filter('plugin_action_links_' . IGSP_PLUGIN_BASENAME, array($this, 'add_settings_link')); @@ -173,18 +176,21 @@ class IGSP_Admin 'igsp-admin-style', IGSP_PLUGIN_URL . 'admin/css/admin-style.css', array(), - IGSP_VERSION + IGSP_VERSION . '.' . time() ); // WordPress components wp_enqueue_style('wp-color-picker'); + // WordPress Media Library + wp_enqueue_media(); + // Admin JS wp_enqueue_script( 'igsp-admin-script', IGSP_PLUGIN_URL . 'admin/js/admin-script.js', - array('jquery', 'wp-color-picker'), - IGSP_VERSION, + array('jquery', 'wp-color-picker', 'media-upload'), + IGSP_VERSION . '.' . time(), true ); @@ -193,7 +199,7 @@ class IGSP_Admin 'igsp-ajax-sync', IGSP_PLUGIN_URL . 'admin/js/ajax-sync.js', array('jquery'), - IGSP_VERSION, + IGSP_VERSION . '.' . time(), true ); @@ -207,8 +213,13 @@ class IGSP_Admin 'syncError' => __('Sync failed. Check logs for details.', 'instagram-gallery-sync-pro'), 'confirmReset' => __('Are you sure? This will delete ALL Instagram data including images. This action cannot be undone.', 'instagram-gallery-sync-pro'), 'confirmClearLogs' => __('Are you sure you want to clear all logs?', 'instagram-gallery-sync-pro'), + 'confirmDeletePost' => __('Are you sure you want to delete this post?', 'instagram-gallery-sync-pro'), 'processing' => __('Processing...', 'instagram-gallery-sync-pro'), 'done' => __('Done!', 'instagram-gallery-sync-pro'), + 'postAdded' => __('Post added successfully!', 'instagram-gallery-sync-pro'), + 'postDeleted' => __('Post deleted.', 'instagram-gallery-sync-pro'), + 'selectImage' => __('Select Image', 'instagram-gallery-sync-pro'), + 'useImage' => __('Use this image', 'instagram-gallery-sync-pro'), ), )); } @@ -382,6 +393,10 @@ class IGSP_Admin 'title' => __('Instagram', 'instagram-gallery-sync-pro'), 'icon' => 'dashicons-instagram', ), + 'posts' => array( + 'title' => __('Posts', 'instagram-gallery-sync-pro'), + 'icon' => 'dashicons-images-alt2', + ), 'layout' => array( 'title' => __('Layout', 'instagram-gallery-sync-pro'), 'icon' => 'dashicons-grid-view', @@ -400,4 +415,120 @@ class IGSP_Admin ), ); } + + /** + * AJAX: Delete single post + * + * @return void + */ + public function ajax_delete_post() + { + check_ajax_referer('igsp_admin_nonce', 'nonce'); + + if (!current_user_can('manage_options')) { + wp_send_json_error(array('message' => __('Permission denied.', 'instagram-gallery-sync-pro'))); + } + + $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0; + + if (!$post_id) { + wp_send_json_error(array('message' => __('Invalid post ID.', 'instagram-gallery-sync-pro'))); + } + + $database = new IGSP_Database(); + $result = $database->delete_post($post_id); + + if ($result) { + // Clear gallery cache + igsp()->clear_transients(); + wp_send_json_success(array('message' => __('Post deleted successfully.', 'instagram-gallery-sync-pro'))); + } else { + wp_send_json_error(array('message' => __('Failed to delete post.', 'instagram-gallery-sync-pro'))); + } + } + + /** + * AJAX: Toggle post active/inactive + * + * @return void + */ + public function ajax_toggle_post() + { + check_ajax_referer('igsp_admin_nonce', 'nonce'); + + if (!current_user_can('manage_options')) { + wp_send_json_error(array('message' => __('Permission denied.', 'instagram-gallery-sync-pro'))); + } + + $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0; + + if (!$post_id) { + wp_send_json_error(array('message' => __('Invalid post ID.', 'instagram-gallery-sync-pro'))); + } + + $database = new IGSP_Database(); + $result = $database->toggle_post_active($post_id); + + if ($result) { + $post = $database->get_post($post_id); + igsp()->clear_transients(); + wp_send_json_success(array( + 'message' => __('Post status updated.', 'instagram-gallery-sync-pro'), + 'is_active' => (bool) $post->is_active, + )); + } else { + wp_send_json_error(array('message' => __('Failed to update post status.', 'instagram-gallery-sync-pro'))); + } + } + + /** + * AJAX: Add manual post + * + * @return void + */ + public function ajax_add_manual_post() + { + check_ajax_referer('igsp_admin_nonce', 'nonce'); + + if (!current_user_can('manage_options')) { + wp_send_json_error(array('message' => __('Permission denied.', 'instagram-gallery-sync-pro'))); + } + + $attachment_id = isset($_POST['attachment_id']) ? absint($_POST['attachment_id']) : 0; + $post_url = isset($_POST['post_url']) ? esc_url_raw($_POST['post_url']) : ''; + $caption = isset($_POST['caption']) ? wp_kses_post($_POST['caption']) : ''; + $posted_at = isset($_POST['posted_at']) ? sanitize_text_field($_POST['posted_at']) : ''; + + if (!$attachment_id) { + wp_send_json_error(array('message' => __('Please select an image.', 'instagram-gallery-sync-pro'))); + } + + // Copy from Media Library to plugin upload directory + $image_handler = new IGSP_Image_Handler(); + $image_result = $image_handler->save_from_media_library($attachment_id); + + if (is_wp_error($image_result)) { + wp_send_json_error(array('message' => $image_result->get_error_message())); + } + + // Insert into database + $database = new IGSP_Database(); + $result = $database->insert_manual_post(array( + 'image_local_path' => $image_result['local_path'], + 'image_thumbnail_path' => $image_result['thumbnail_path'], + 'post_url' => $post_url, + 'caption' => $caption, + 'posted_at' => $posted_at, + 'file_size' => $image_result['file_size'], + 'image_width' => $image_result['width'], + 'image_height' => $image_result['height'], + )); + + if ($result) { + igsp()->clear_transients(); + wp_send_json_success(array('message' => __('Post added successfully!', 'instagram-gallery-sync-pro'))); + } else { + wp_send_json_error(array('message' => __('Failed to add post.', 'instagram-gallery-sync-pro'))); + } + } } diff --git a/includes/class-database.php b/includes/class-database.php index cf13158..bb68d88 100644 --- a/includes/class-database.php +++ b/includes/class-database.php @@ -24,7 +24,7 @@ class IGSP_Database * * @var string */ - private $db_version = '1.0.0'; + private $db_version = '1.1.0'; /** * Constructor @@ -64,6 +64,7 @@ class IGSP_Database image_width INT(11) DEFAULT 0, image_height INT(11) DEFAULT 0, is_active TINYINT(1) DEFAULT 1, + is_manual TINYINT(1) DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY instagram_id (instagram_id), KEY username (username), @@ -250,6 +251,7 @@ class IGSP_Database 'order' => 'newest', 'active' => true, 'username' => '', + 'search' => '', ); $args = wp_parse_args($args, $defaults); @@ -267,6 +269,13 @@ class IGSP_Database $values[] = $args['username']; } + if (!empty($args['search'])) { + $where[] = '(caption LIKE %s OR username LIKE %s)'; + $search_term = '%' . $wpdb->esc_like($args['search']) . '%'; + $values[] = $search_term; + $values[] = $search_term; + } + // Order switch ($args['order']) { case 'oldest': @@ -315,6 +324,116 @@ class IGSP_Database ); } + /** + * Count posts with optional filters + * + * @param array $args Filter arguments + * @return int + */ + public function count_posts_filtered($args = array()) + { + global $wpdb; + + $defaults = array( + 'active' => null, + 'search' => '', + ); + + $args = wp_parse_args($args, $defaults); + + $where = array('1=1'); + $values = array(); + + if ($args['active'] !== null) { + $where[] = 'is_active = %d'; + $values[] = $args['active'] ? 1 : 0; + } + + if (!empty($args['search'])) { + $where[] = '(caption LIKE %s OR username LIKE %s)'; + $search_term = '%' . $wpdb->esc_like($args['search']) . '%'; + $values[] = $search_term; + $values[] = $search_term; + } + + $where_clause = implode(' AND ', $where); + $query = "SELECT COUNT(*) FROM " . IGSP_TABLE_POSTS . " WHERE {$where_clause}"; + + if (!empty($values)) { + $query = $wpdb->prepare($query, $values); + } + + return (int) $wpdb->get_var($query); + } + + /** + * Toggle post active status + * + * @param int $id Post ID + * @return bool + */ + public function toggle_post_active($id) + { + global $wpdb; + + $post = $this->get_post($id); + if (!$post) { + return false; + } + + $new_status = $post->is_active ? 0 : 1; + + return $wpdb->update( + IGSP_TABLE_POSTS, + array('is_active' => $new_status), + array('id' => $id), + array('%d'), + array('%d') + ) !== false; + } + + /** + * Insert a manually added post + * + * @param array $data Post data + * @return int|false Insert ID or false on failure + */ + public function insert_manual_post($data) + { + global $wpdb; + + $instagram_id = 'manual_' . time() . '_' . wp_rand(1000, 9999); + + $insert_data = array( + 'instagram_id' => $instagram_id, + 'username' => sanitize_text_field($data['username'] ?? get_option('igsp_username', 'manual')), + 'image_local_path' => isset($data['image_local_path']) ? sanitize_text_field($data['image_local_path']) : '', + 'image_thumbnail_path' => isset($data['image_thumbnail_path']) ? sanitize_text_field($data['image_thumbnail_path']) : '', + 'post_url' => isset($data['post_url']) ? esc_url_raw($data['post_url']) : '', + 'caption' => isset($data['caption']) ? wp_kses_post($data['caption']) : '', + 'likes_count' => 0, + 'comments_count' => 0, + 'posted_at' => isset($data['posted_at']) && !empty($data['posted_at']) ? sanitize_text_field($data['posted_at']) : current_time('mysql'), + 'synced_at' => current_time('mysql'), + 'file_size' => isset($data['file_size']) ? absint($data['file_size']) : 0, + 'image_width' => isset($data['image_width']) ? absint($data['image_width']) : 0, + 'image_height' => isset($data['image_height']) ? absint($data['image_height']) : 0, + 'is_active' => 1, + 'is_manual' => 1, + ); + + $result = $wpdb->insert( + IGSP_TABLE_POSTS, + $insert_data, + array( + '%s', '%s', '%s', '%s', '%s', '%s', + '%d', '%d', '%s', '%s', '%d', '%d', '%d', '%d', '%d' + ) + ); + + return $result ? $wpdb->insert_id : false; + } + /** * Delete post * diff --git a/includes/class-image-handler.php b/includes/class-image-handler.php index 5fdcaae..9ad08a8 100644 --- a/includes/class-image-handler.php +++ b/includes/class-image-handler.php @@ -441,6 +441,66 @@ class IGSP_Image_Handler return size_format($bytes); } + /** + * Save image from WordPress Media Library to plugin upload directory + * + * @param int $attachment_id WordPress attachment ID + * @return array|WP_Error Array with paths or WP_Error + */ + public function save_from_media_library($attachment_id) + { + $file_path = get_attached_file($attachment_id); + + if (!$file_path || !file_exists($file_path)) { + return new WP_Error('file_not_found', __('Attachment file not found.', 'instagram-gallery-sync-pro')); + } + + // Validate + $validation = $this->validate_image($file_path); + if (is_wp_error($validation)) { + return $validation; + } + + $file_info = $validation; + $extension = $file_info['extension']; + + // Generate unique filename + $safe_id = 'manual_' . time() . '_' . wp_rand(1000, 9999); + $filename = $safe_id . '.' . $extension; + $dest_path = $this->upload_path . '/' . $filename; + + // Copy file (don't move - keep original in Media Library) + $copied = copy($file_path, $dest_path); + + if (!$copied) { + return new WP_Error('copy_failed', __('Failed to copy image file.', 'instagram-gallery-sync-pro')); + } + + chmod($dest_path, 0644); + + // Generate thumbnail + $thumbnail_path = $this->generate_thumbnail($dest_path, $safe_id, $extension); + + // Get dimensions + $dimensions = getimagesize($dest_path); + + $result = array( + 'local_path' => IGSP_UPLOAD_DIR . '/' . $filename, + 'thumbnail_path' => $thumbnail_path ?: '', + 'full_url' => $this->upload_url . '/' . $filename, + 'file_size' => filesize($dest_path), + 'width' => $dimensions ? $dimensions[0] : 0, + 'height' => $dimensions ? $dimensions[1] : 0, + ); + + $this->logger->info( + sprintf(__('Manual image saved: %s', 'instagram-gallery-sync-pro'), $filename), + array('size' => $result['file_size']) + ); + + return $result; + } + /** * Clear all stored images * diff --git a/public/.DS_Store b/public/.DS_Store index f64a52d3d415fbfeaac97d45be2bd258c96cb98f..516ee5a10fddcf332938befd51776c87de36c90b 100644 GIT binary patch delta 119 zcmZoMXffEJ!e}#P3j+fKGea^%F+*`~zKcszPJR+lgyYQ9Leb#K#~qPnQ_y55CooD$ xT>z?JU}DH(D9%YY3{K9^EdU8HFh3WbJcZGhiA7-ZM@BKWi3NO{**X650|39xAk_c> delta 119 zcmZoMXffEJ!f4~Pgn@y9nIV~>n4vf~-^C>