Changeset 3256149
- Timestamp:
- 03/15/2025 02:56:19 AM (13 months ago)
- Location:
- subscription-tracker/trunk
- Files:
-
- 4 edited
-
js/psm-admin.css (modified) (3 diffs)
-
js/psm-admin.js (modified) (5 diffs)
-
readme.txt (modified) (4 diffs)
-
subscription-tracker.php (modified) (27 diffs)
Legend:
- Unmodified
- Added
- Removed
-
subscription-tracker/trunk/js/psm-admin.css
r3252973 r3256149 1 .modal-body input, .modal-body select{ 2 width:100%; 3 border:1px solid #ccc !important; 4 padding:5px 10px !important; 5 } 6 .psm-modal { 7 position: fixed; 8 top: 0; 9 left: 0; 10 width: 100%; 11 height: 100%; 12 display: none; 13 background: rgba(0, 0, 0, 0.5); 14 z-index: 1000; 15 } 16 17 .modal-on { 18 display: flex; 19 align-items: center; 20 justify-content: center; 21 } 22 23 /* Modal content box */ 24 .modal-content { 25 background: #fff; 26 padding: 20px; 27 border-radius: 8px; 28 max-width: 300px; 29 width: 90%; 30 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); 31 } 32 33 34 /* Modal body (optional if you want extra spacing) */ 35 .modal-body { 36 margin: 0; 37 } 38 39 /* Form styling */ 40 .form-group { 41 margin-bottom: 15px; 42 } 43 .form-group label { 44 display: block; 45 margin-bottom: 5px; 46 font-weight: bold; 47 } 48 .form-group input, 49 .form-group select, 50 .form-group textarea { 51 width: 100%; 52 padding: 8px; 53 border: 1px solid #ddd; 54 border-radius: 4px; 55 box-sizing: border-box; 56 } 57 58 /* Form actions (buttons) */ 59 .form-actions { 60 text-align: right; 61 } 62 .form-actions .button { 63 padding: 8px 16px; 64 border: none; 65 border-radius: 4px; 66 cursor: pointer; 67 margin-left: 10px; 68 } 69 .form-actions .cancel { 70 background: #ccc; 71 color: #333; 72 } 73 .form-actions .save { 74 background: #0073aa; 75 color: #fff; 76 } 77 1 78 :root { 2 79 --sea-green: #41BA90; … … 27 104 .psm-renewal-date{ 28 105 cursor:pointer; 106 } 107 .psm-trials { 108 width: 100%; 109 border-collapse: collapse; 110 margin-top: 20px; 111 } 112 113 .psm-trials th, 114 .psm-trials td { 115 border: 1px solid #ddd; 116 padding: 8px; 117 text-align: left; 118 } 119 120 .psm-trials th { 121 background-color: #f4f4f4; 122 } 123 124 .psm-trials td { 125 vertical-align: middle; 126 } 127 128 /* Dropdown menu styles (if not already defined) */ 129 .dropdown { 130 position: relative; 131 display: inline-block; 132 } 133 134 .dropdown-toggle { 135 background: transparent; 136 border: none; 137 cursor: pointer; 138 } 139 140 .vertical-dots { 141 font-size: 18px; 142 } 143 144 .dropdown-menu { 145 display: none; 146 position: absolute; 147 background-color: #fff; 148 min-width: 100px; 149 box-shadow: 0 2px 5px rgba(0,0,0,0.15); 150 z-index: 100; 151 border: 1px solid #ddd; 152 border-radius: 4px; 153 padding: 5px 0; 154 } 155 156 .dropdown:hover .dropdown-menu { 157 display: block; 158 } 159 160 .dropdown-menu div { 161 padding: 8px 12px; 162 cursor: pointer; 163 } 164 165 .dropdown-menu div:hover { 166 background-color: #f4f4f4; 167 } 168 .palmss_controls { 169 display: flex; 170 justify-content: space-between; 171 align-items: center; 172 margin: 20px 0; 173 /* Optional styling for the parent container */ 174 padding: 10px; 175 background-color: #f9f9f9; 176 border: 1px solid #ddd; 177 border-radius: 4px; 178 } 179 180 .psm-view-tabs, 181 .palmsst_controls { 182 display: flex; 183 gap: 10px; 184 } 185 186 /* Example button styles (preserve your existing ones) */ 187 .button { 188 padding: 8px 12px; 189 font-size: 14px; 190 border: none; 191 border-radius: 4px; 192 cursor: pointer; 193 transition: background-color 0.3s ease; 194 } 195 196 .button-primary { 197 background-color: #0056b3; 198 color: #fff; 199 } 200 201 .button-primary:hover { 202 background-color: #004494; 203 } 204 205 .button-secondary { 206 background-color: #e0e0e0; 207 color: #333; 208 } 209 210 .button-secondary:hover { 211 background-color: #ccc; 212 } 213 214 /* Example styling for psm-view-tab buttons */ 215 .psm-view-tab { 216 padding: 8px 12px; 217 background-color: #fff; 218 border: 1px solid #ddd; 219 border-radius: 4px; 220 cursor: pointer; 221 } 222 223 .psm-view-tab.active { 224 background-color: #0056b3; 225 color: #fff; 29 226 } 30 227 #psm-view-table table tr, #psm-view-table table td, #psm-view-table tbody{ … … 612 809 } 613 810 811 -
subscription-tracker/trunk/js/psm-admin.js
r3252973 r3256149 1 jQuery(document).on('click', '.psm-renewal-alert .notice-dismiss', function () { 2 jQuery.post(palmssm.ajax_url, { 3 action: 'palmssm_clear_renewal_alert', 4 nonce: palmssm.nonce 5 }); 6 }); 7 8 jQuery(document).ready(function ($) { 9 1 jQuery(document).ready(function($) { 2 3 // Open modals by adding the modal-on class. 4 $('#add-subscription').on('click', function(){ 5 $('#subscription-modal').addClass('modal-on'); 6 }); 7 $('#add-free-trial').on('click', function(){ 8 $('#free-trial-modal').addClass('modal-on'); 9 }); 10 11 // Close modal when clicking the close button or clicking on the overlay. 12 $('.psm-close').on('click', function(){ 13 $(this).closest('.psm-modal').removeClass('modal-on'); 14 }); 15 $(window).on('click', function(e) { 16 if ($(e.target).hasClass('psm-modal')) { 17 $(e.target).removeClass('modal-on'); 18 } 19 }); 20 21 // Subscription form submission (corrected) 22 $('#add-subscription-form').on('submit', function(e) { 23 e.preventDefault(); 24 var data = { 25 action: 'palmssm_add_subscription', 26 nonce: palmssm.nonce, 27 plugin_name: $('#subscription-name').val(), // Correct field ID 28 start_date: $('#start-date').val() // Correct field ID 29 }; 30 $.post(palmssm.ajax_url, data, function(response){ 31 if(response.success){ 32 var sub = response.data; 33 var pluginSlug = sub.plugin_name.toLowerCase().replace(/[^a-z0-9]+/g, '-'); 34 // Construct new third-party row with default options. 35 var newRow = '<tr class="psm-plugin-row" data-id="'+ sub.id +'" data-source="third_party">'+ 36 '<td>'+ sub.plugin_name +'</td>'+ 37 '<td><select class="psm-plugin-type" data-id="'+ sub.id +'">'+ 38 '<option value="domain">Domain</option>'+ 39 '<option value="hosting">Hosting</option>'+ 40 '<option value="theme">Theme</option>'+ 41 '<option value="service">Service</option>'+ 42 '<option value="other" selected>Other</option>'+ 43 '</select></td>'+ 44 '<td><select class="psm-subscription-type" data-id="'+ sub.id +'">'+ 45 '<option value="monthly" selected>Monthly</option>'+ 46 '<option value="annual">Annual</option>'+ 47 '<option value="lifetime">Lifetime</option>'+ 48 '</select></td>'+ 49 '<td><input type="date" class="psm-start-date" data-id="'+ sub.id +'" value="'+ sub.start_date +'"></td>'+ 50 '<td style="display:flex"><input type="number" step="0.01" min="0" class="psm-subscription-price" data-id="'+ sub.id +'" value="0"></td>'+ 51 '</tr>'; 52 $('#psm-table-view table tbody').append(newRow); 53 alert('Subscription added successfully!'); 54 $('#subscription-modal').removeClass('modal-on'); 55 $('#add-subscription-form')[0].reset(); 56 } else { 57 alert('Error: ' + response.data); 58 } 59 }); 60 }); 61 62 // Free trial form submission. 63 $('#free-trial-form').on('submit', function(e) { 64 e.preventDefault(); 65 var data = { 66 action: 'palmssm_add_free_trial', 67 nonce: palmssm.nonce, 68 plugin_name: $('#trial-plugin-name').val(), 69 start_date: $('#trial-start-date').val(), 70 end_date: $('#trial-end-date').val() 71 }; 72 $.post(palmssm.ajax_url, data, function(response){ 73 if(response.success){ 74 alert('Free trial added successfully!'); 75 $('#free-trial-modal').removeClass('modal-on'); 76 $('#free-trial-form')[0].reset(); 77 } else { 78 alert('Error: ' + response.data); 79 } 80 }); 81 }); 82 83 // When the plugin type dropdown changes, update the disabled state for other inputs (for main rows only) 84 $(document).on('change', '.psm-plugin-type', function () { 85 var $row = $(this).closest('tr'); 86 var plugin_type = $(this).val(); 87 var source = $row.data('source'); // "plugin" or "third_party" 88 89 // For main table rows, disable inputs when type is "free" 90 if (source === 'plugin') { 91 if (plugin_type === 'free') { 92 $row.find('.psm-subscription-type, .psm-start-date, .psm-subscription-price').prop('disabled', true); 93 } else { 94 $row.find('.psm-subscription-type, .psm-start-date, .psm-subscription-price').prop('disabled', false); 95 } 96 } 97 }); 98 99 // Unified event listener for changes on any subscription row inputs/selects. 100 $(document).on('change', '.psm-plugin-type, .psm-subscription-type, .psm-start-date, .psm-subscription-price', function(){ 101 var $row = $(this).closest('.psm-plugin-row'); 102 var subscription_id = $row.data('id'); 103 var source = $row.data('source'); // "plugin" or "third_party" 104 var plugin_type = $row.find('.psm-plugin-type').val(); 105 var subscription_type = $row.find('.psm-subscription-type').val(); 106 var start_date = $row.find('.psm-start-date').val(); 107 var subscription_price = $row.find('.psm-subscription-price').val(); 108 109 var data = { 110 action: 'palmssm_update_subscription', 111 nonce: palmssm.nonce, 112 subscription_id: subscription_id, 113 source: source, 114 plugin_type: plugin_type, 115 subscription_type: subscription_type, 116 start_date: start_date, 117 subscription_price: subscription_price 118 }; 119 120 $.post(palmssm.ajax_url, data, function(response) { 121 if(response.success){ 122 console.log("Subscription " + subscription_id + " updated successfully."); 123 } else { 124 alert("Update failed: " + response.data); 125 } 126 }); 127 }); 128 129 // Renewal alert dismiss handler. 130 $(document).on('click', '.psm-renewal-alert .notice-dismiss', function () { 131 $.post(palmssm.ajax_url, { 132 action: 'palmssm_clear_renewal_alert', 133 nonce: palmssm.nonce 134 }); 135 }); 136 137 // Dark Mode toggle. 10 138 const $darkModeToggle = $('#psm-dark-mode'); 11 139 const $iconSun = $('.icon-sun'); 12 140 const $iconMoon = $('.icon-moon'); 13 141 14 // Initialize Dark Mode based on localStorage15 142 if (localStorage.getItem('psmDarkMode') === 'enabled') { 16 143 $('body').addClass('psm-dark-mode'); 17 144 $darkModeToggle.prop('checked', true); 18 $iconSun.css( 'opacity', '0').css('visibility', 'hidden');19 $iconMoon.css( 'opacity', '1').css('visibility', 'visible').css('fill', 'var(--sea-green)');145 $iconSun.css({opacity:'0', visibility:'hidden'}); 146 $iconMoon.css({opacity:'1', visibility:'visible', fill:'var(--sea-green)'}); 20 147 } 21 22 // Toggle Dark Mode23 148 $darkModeToggle.on('change', function () { 24 149 if ($(this).is(':checked')) { 25 150 $('body').addClass('psm-dark-mode'); 26 151 localStorage.setItem('psmDarkMode', 'enabled'); 27 $iconSun.css( 'opacity', '0').css('visibility', 'hidden');28 $iconMoon.css( 'opacity', '1').css('visibility', 'visible').css('fill', 'var(--sea-green)');152 $iconSun.css({opacity:'0', visibility:'hidden'}); 153 $iconMoon.css({opacity:'1', visibility:'visible', fill:'var(--sea-green)'}); 29 154 } else { 30 155 $('body').removeClass('psm-dark-mode'); 31 156 localStorage.setItem('psmDarkMode', 'disabled'); 32 $iconSun.css('opacity', '1').css('visibility', 'visible'); 33 $iconMoon.css('opacity', '0').css('visibility', 'hidden'); 34 } 35 }); 36 157 $iconSun.css({opacity:'1', visibility:'visible'}); 158 $iconMoon.css({opacity:'0', visibility:'hidden'}); 159 } 160 }); 161 162 // High Contrast toggle. 37 163 const $highContrastToggle = $('#psm-high-contrast-toggle'); 38 39 // Initialize High Contrast based on localStorage40 164 if (localStorage.getItem('psmHighContrast') === 'enabled') { 41 165 $('body').addClass('high-contrast'); … … 44 168 $highContrastToggle.attr('aria-pressed', 'false').find('svg').css('fill', 'var(--white)'); 45 169 } 46 47 // Toggle High Contrast48 170 $highContrastToggle.on('click', function () { 49 171 const isActive = $('body').toggleClass('high-contrast').hasClass('high-contrast'); 50 51 172 $(this).attr('aria-pressed', isActive.toString()); 52 173 $(this).find('svg').css('fill', isActive ? 'var(--black)' : 'var(--white)'); 53 54 174 localStorage.setItem('psmHighContrast', isActive ? 'enabled' : 'disabled'); 55 175 }); 56 176 177 // Tab Switching for showing/hiding views. 57 178 const $tableView = $('#psm-table-view'); 179 const $trialView = $('#psm-trials-view'); 180 const $calendarView = $('#psm-calendar-view'); 58 181 const $tabs = $('.psm-view-tab'); 59 60 // Tab Switching 182 61 183 $tabs.on('click', function () { 62 184 const target = $(this).data('target'); 63 // Toggle active tab64 185 $tabs.removeClass('active'); 65 186 $(this).addClass('active'); 66 // Toggle views 187 188 // Hide all view containers. 189 $tableView.hide(); 190 $trialView.hide(); 191 $calendarView.hide(); 192 193 // Show the selected view container. 67 194 if (target === 'table') { 68 195 $tableView.show(); 196 } else if (target === 'trial') { 197 $trialView.show(); 69 198 } else if (target === 'calendar') { 70 $tableView.hide(); 71 } 72 }); 73 74 // Set Default View to Table 75 $('#psm-tab-table').click(); 76 77 78 // Export Button Handler 199 $calendarView.show(); 200 } 201 }); 202 $('#psm-tab-table').click(); 203 204 // Export Button Handler. 79 205 $('#psm-export').on('click', function () { 80 206 window.location.href = 81 207 palmssm.ajax_url + 82 '?action=palmssm_export_plugin_data&format=csv' + 83 '&nonce=' + 208 '?action=palmssm_export_plugin_data&format=csv&nonce=' + 84 209 encodeURIComponent(palmssm.nonce); 85 210 }); 86 211 87 // Function to Save Plugin Data via AJAX 88 function savePluginData(plugin_slug, data) { 89 $.ajax({ 90 url: palmssm.ajax_url, 91 type: 'POST', 92 data: { 93 action: 'palmssm_save_plugin_data', 94 nonce: palmssm.nonce, 95 plugin: plugin_slug, 96 plugin_type: data.plugin_type, 97 subscription_type: data.subscription_type, 98 api_key: data.api_key, 99 renewal_date: data.renewal_date, 100 subscription_price: data.subscription_price, 101 }, 102 success: function (response) { 103 if (response.success) { 104 console.log('Data saved successfully for plugin:', plugin_slug); 105 } else { 106 alert('Error saving data: ' + response.data); 107 } 108 }, 109 error: function (xhr, status, error) { 110 console.error('AJAX error:', status, error); 111 alert('An error occurred while saving the data.'); 112 } 113 }); 114 } 115 116 // Toggle Paid Status Handler 212 // Paid Checkbox Handler. 117 213 $('.psm-paid-checkbox').on('change', function() { 118 214 var pluginSlug = $(this).data('plugin'); 119 215 var paidStatus = $(this).is(':checked') ? 1 : 0; 120 121 $.ajax({ 122 url: palmssm.ajax_url, 123 type: 'POST', 124 data: { 125 action: 'palmssm_toggle_paid_status', 126 nonce: palmssm.nonce, 127 plugin_slug: pluginSlug, 128 paid: paidStatus 129 }, 130 success: function(response) { 131 if (response.success) { 132 console.log(response.data); 133 } else { 134 alert('Error: ' + response.data); 135 } 136 }, 137 error: function(xhr, status, error) { 138 alert('AJAX error: ' + error); 139 } 140 }); 141 }); 142 143 // Debounce Function to Limit AJAX Calls 216 $.post(palmssm.ajax_url, { 217 action: 'palmssm_toggle_paid_status', 218 nonce: palmssm.nonce, 219 plugin_slug: pluginSlug, 220 paid: paidStatus 221 }, function(response) { 222 if(response.success){ 223 console.log(response.data); 224 } else { 225 alert('Error: ' + response.data); 226 } 227 }); 228 }); 229 230 // Debounce Function to limit AJAX calls. 144 231 function debounce(func, wait) { 145 232 var timeout; 146 233 return function () { 147 var context = this, 148 args = arguments; 234 var context = this, args = arguments; 149 235 clearTimeout(timeout); 150 236 timeout = setTimeout(function () { … … 154 240 } 155 241 156 // Event Handlers for Plugin Data Changes 157 $(document).on('change', '.psm-plugin-type', function () { 158 var $row = $(this).closest('tr'); 159 var plugin_slug = $row.data('plugin'); 160 var plugin_type = $(this).val(); 161 162 if (plugin_type === 'custom' || plugin_type === 'premium') { 163 $row.find('.psm-subscription-type, .psm-api-key, .psm-renewal-date, .psm-subscription-price, .psm-paid-checkbox').prop('disabled', false); 164 } else if (plugin_type === 'free') { 165 $row.find('.psm-subscription-type, .psm-api-key, .psm-renewal-date, .psm-subscription-price, .psm-paid-checkbox').prop('disabled', true); 166 $row.find('.psm-paid-checkbox').prop('checked', false); // Uncheck if disabled 167 } 168 169 // Gather data to save 170 var data = { 171 plugin_type: plugin_type, 172 subscription_type: $row.find('.psm-subscription-type').val(), 173 api_key: $row.find('.psm-api-key').val(), 174 renewal_date: $row.find('.psm-renewal-date').val(), 175 subscription_price: $row.find('.psm-subscription-price').val(), 176 }; 177 178 savePluginData(plugin_slug, data); 179 }); 180 181 $(document).on('change', '.psm-subscription-type', function () { 182 var $row = $(this).closest('tr'); 183 var plugin_slug = $row.data('plugin'); 184 185 var data = { 186 plugin_type: $row.find('.psm-plugin-type').val(), 187 subscription_type: $(this).val(), 188 api_key: $row.find('.psm-api-key').val(), 189 renewal_date: $row.find('.psm-renewal-date').val(), 190 subscription_price: $row.find('.psm-subscription-price').val(), 191 }; 192 193 savePluginData(plugin_slug, data); 194 }); 195 196 $(document).on('change', '.psm-api-key', function () { 197 var $row = $(this).closest('tr'); 198 var plugin_slug = $row.data('plugin'); 199 200 var data = { 201 plugin_type: $row.find('.psm-plugin-type').val(), 202 subscription_type: $row.find('.psm-subscription-type').val(), 203 api_key: $(this).val(), 204 renewal_date: $row.find('.psm-renewal-date').val(), 205 subscription_price: $row.find('.psm-subscription-price').val(), 206 }; 207 208 savePluginData(plugin_slug, data); 209 }); 210 211 $(document).on('change', '.psm-renewal-date', function () { 212 var $row = $(this).closest('tr'); 213 var plugin_slug = $row.data('plugin'); 214 215 var data = { 216 plugin_type: $row.find('.psm-plugin-type').val(), 217 subscription_type: $row.find('.psm-subscription-type').val(), 218 api_key: $row.find('.psm-api-key').val(), 219 renewal_date: $(this).val(), 220 subscription_price: $row.find('.psm-subscription-price').val(), 221 }; 222 223 savePluginData(plugin_slug, data); 224 }); 225 226 // Event listener for changes in the Subscription Price input with debounce 227 $('.psm-api-key, .psm-renewal-date, .psm-subscription-price').on( 228 'change', 229 debounce(function () { 230 var $row = $(this).closest('tr'); 231 var plugin_slug = $row.data('plugin'); 232 233 var data = { 234 plugin_type: $row.find('.psm-plugin-type').val(), 235 subscription_type: $row.find('.psm-subscription-type').val(), 236 api_key: $row.find('.psm-api-key').val(), 237 renewal_date: $row.find('.psm-renewal-date').val(), 238 subscription_price: $row.find('.psm-subscription-price').val(), 239 }; 240 savePluginData(plugin_slug, data); 241 }, 500) 242 ); 243 244 // Initialize Paid Checkbox States 245 $('.psm-plugin-row').each(function () { 246 var $row = $(this); 247 var plugin_type = $row.find('.psm-plugin-type').val(); 248 249 if (plugin_type === 'free') { 250 $row.find('.psm-paid-checkbox').prop('disabled', true); 251 $row.find('.psm-paid-checkbox').prop('checked', false); 252 } else { 253 $row.find('.psm-paid-checkbox').prop('disabled', false); 254 } 255 }); 256 257 // Sync Plugins Button Handler 242 // Sync Plugins Button Handler. 258 243 $('#psm-sync').on('click', function (e) { 259 244 e.preventDefault(); 260 261 245 if (confirm('Are you sure you want to sync plugins? This will add new plugins and remove deleted ones from the database.')) { 262 246 $.ajax({ … … 272 256 success: function (response) { 273 257 if (response.success) { 274 alert(response.data); 275 location.reload(); 258 alert(response.data); 259 location.reload(); 276 260 } else { 277 261 alert('Error: ' + response.data); … … 287 271 } 288 272 }); 289 290 273 }); 291 274 -
subscription-tracker/trunk/readme.txt
r3252973 r3256149 1 === Palms Track===1 === Palms Track - Subscription Tracker === 2 2 Contributors: palmstrack 3 Tags: subscription tracking, subscription management, renewal alerts, expense tracker 3 Tags: subscription tracking, subscription management, renewal alerts, expense tracker, expense manager 4 4 Requires at least: 5.9 5 5 Tested up to: 6.7.1 6 Stable tag: 1. 16 Stable tag: 1.2 7 7 Requires PHP: 7.2 8 8 License: GPLv2 or later … … 11 11 12 12 == Description == 13 PalmsTrack Subscription Tracker for WordPress helps you manage your plugin subscriptions from a centralized dashboard. Monitor upcoming renewal dates from your WordPress admin dashboard, store license keys, and export your data to CSV for your convenience13 PalmsTrack Subscription Tracker for WordPress helps you manage your plugin subscriptions from a centralized dashboard. Monitor upcoming renewal dates from your WordPress admin dashboard, and export your data to CSV for your convenience. 14 14 15 15 == Installation == … … 34 34 == Features == 35 35 - **Plugin Tracking:** Automatically sync your installed plugins to manage their subscriptions. 36 - **Renewal Alerts:** Get dashboard notifications for upcoming plugin renewals days in advance. 37 - **CSV Export:** Backup your plugin subscription data by exporting it into a CSV file. 36 - **3rd Party Subscriptions:** Track hosting, domain, and other third‑party services alongside your plugins. 37 - **Renewal Alerts:** Get dashboard notifications for upcoming renewals days in advance. 38 - **CSV Export:** Backup your subscription data by exporting it into a CSV file. 38 39 39 40 == Premium Features == 40 - **Extended Subscription Tracking:** Manage recurring subscriptions and other expenses from third-party services such as hosting, domain, and SaaS services. 41 41 - **Extended Subscription Tracking:** Manage recurring subscriptions and other expenses from third‑party services such as hosting, domain, and SaaS services. 42 42 - **Scheduled Email Renewal Digest:** Stay ahead of your expenses with daily, weekly, or monthly email digests of your upcoming renewals. 43 44 43 - **Advanced Organization Tools:** Tag and filter subscriptions for a clear breakdown of your spending. 45 46 44 - **License Key Manager:** Store license keys and manage team access to them. 47 48 45 - **Cost Tracking and Insights:** Analyze monthly and yearly expenses to optimize your budget, and generate downloadable PDF summary reports of your site expenses. 49 50 46 - **WordPress Insights API:** Compare your spending with similar WordPress sites to make smarter financial decisions. 51 52 - **Your Data, Your Server:** All subscription data stays on your WordPress site—no external servers, no third-party access. Full control, maximum privacy. *Data is shared only with your consent for insights. 47 - **Your Data, Your Server:** All subscription data stays on your WordPress site—no external servers, no third‑party access. Full control, maximum privacy. *Data is shared only with your consent for insights. 53 48 54 49 == Frequently Asked Questions == … … 57 52 58 53 = How do I export my data? = 59 Go to **My Subscriptions** and click ** Download CSV**.54 Go to **My Subscriptions** and click **Export**. 60 55 61 56 == Changelog == 62 = 1.1 = 57 = 1.3 = 58 * Added support for 3rd party subscriptions. Now you can track hosting, domain, and other third‑party services alongside plugins. 59 = 1.2 = 63 60 * Initial stable release featuring plugin subscription tracking, renewal alerts, and CSV export. 64 61 65 62 == Upgrade Notice == 66 = 1.1 = 63 = 1.3 = 64 This update adds support for 3rd party subscriptions, expanding tracking capabilities beyond just plugins. 65 = 1.2 = 67 66 This is the first stable release of PalmsTrack. No upgrade is necessary at this time. 68 67 -
subscription-tracker/trunk/subscription-tracker.php
r3252973 r3256149 1 1 <?php 2 2 /* 3 * Plugin Name: Subscription Tracker3 * Plugin Name: PalmsTrack Subscription Tracker 4 4 * Plugin URI: https://palmstrack.com 5 5 * Description: Manage your WordPress plugin subscriptions, renewal dates, and license keys. 6 * Version: 1. 26 * Version: 1.3 7 7 * Requires at least: 5.2 8 8 * Requires PHP: 7.2 … … 15 15 16 16 if ( ! defined( 'ABSPATH' ) ) { 17 exit; 17 exit; 18 18 } 19 19 20 20 if ( ! class_exists( 'PalmsSM_Subscription_Tracker' ) ) { 21 21 class PalmsSM_Subscription_Tracker { 22 22 23 23 private static $plugin_file; 24 24 private $plugin_dir; 25 25 private $plugin_url; 26 // Use the new table name for main subscriptions. 26 27 private $table_name; 28 private $table_name_3p; 29 private $table_name_trials; 27 30 28 31 public function __construct() { 29 32 global $wpdb; 30 33 self::$plugin_file = __FILE__; 31 32 34 $this->plugin_dir = plugin_dir_path( self::$plugin_file ); 33 35 $this->plugin_url = plugin_dir_url( self::$plugin_file ); 34 36 37 // Set table names – note these must match your schema. 35 38 $this->table_name = $wpdb->prefix . 'palms_subscription_tracker'; 39 $this->table_name_3p = $wpdb->prefix . 'palms_3p'; 40 $this->table_name_trials = $wpdb->prefix . 'palms_trials'; 36 41 37 42 register_activation_hook( self::$plugin_file, array( $this, 'activate_plugin' ) ); … … 41 46 add_action( 'wp_ajax_palmssm_save_plugin_data', array( $this, 'palmssm_save_plugin_data' ) ); 42 47 add_action( 'wp_ajax_palmssm_export_plugin_data', array( $this, 'palmssm_export_plugin_data' ) ); 43 add_action( 'wp_ajax_palmssm_clear_renewal_alert', array( $this, 'palmssm_clear_renewal_alert' ) );44 45 // Corrected Hook for Dashboard Widget46 48 add_action( 'wp_dashboard_setup', array( $this, 'palmssm_add_dashboard_widget' ) ); 47 48 add_action( 'wp_ajax_palmssm_get_calendar_events', array( $this, 'palmssm_get_calendar_events' ) );49 add_action( 'wp_ajax_palmssm_toggle_paid_status', array( $this, 'palmssm_toggle_paid_status' ) );50 49 add_action( 'wp_ajax_palmssm_sync_plugins', array( $this, 'palmssm_sync_plugins' ) ); 50 add_action( 'wp_ajax_palmssm_add_subscription', array( $this, 'palmssm_add_subscription' ) ); 51 add_action( 'wp_ajax_palmssm_add_free_trial', array( $this, 'palmssm_add_free_trial' ) ); 52 add_action( 'wp_ajax_palmssm_update_subscription', array( $this, 'palmssm_update_subscription' ) ); 51 53 52 54 if ( ! function_exists( 'get_plugin_data' ) ) { … … 58 60 59 61 /** 60 * Plugin Activation Hook 62 * Plugin Activation Hook – create tables per new definitions. 61 63 */ 62 64 public function activate_plugin() { 63 65 global $wpdb; 64 65 66 $charset_collate = $wpdb->get_charset_collate(); 67 68 // Main subscriptions table. 66 69 $sql = "CREATE TABLE {$this->table_name} ( 67 70 id INT AUTO_INCREMENT PRIMARY KEY, 71 subscription_id CHAR(36) NOT NULL UNIQUE, 68 72 plugin_slug VARCHAR(255) NOT NULL UNIQUE, 69 plugin_type ENUM('free', 'premium', 'custom') DEFAULT 'free',70 api_key VARCHAR(255) DEFAULT NULL,73 start_date DATE DEFAULT NULL, 74 plugin_type ENUM('free','premium','custom') DEFAULT 'free', 71 75 renewal_date DATE DEFAULT NULL, 72 subscription_price DECIMAL(10,2) DEFAULT NULL,73 subscription_type ENUM('monthly', 'annual') DEFAULT 'monthly',74 paid TINYINT(1) DEFAULT 076 subscription_price DECIMAL(10,2) DEFAULT 0, 77 subscription_type ENUM('monthly','annual') DEFAULT 'monthly', 78 notes TEXT DEFAULT NULL 75 79 ) {$charset_collate};"; 76 77 80 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); 78 81 dbDelta( $sql ); 79 82 83 $sql3p = "CREATE TABLE {$this->table_name_3p} ( 84 id INT AUTO_INCREMENT PRIMARY KEY, 85 subscription_id CHAR(36) NOT NULL UNIQUE, 86 name VARCHAR(255) NOT NULL, 87 start_date DATE DEFAULT NULL, 88 plugin_type ENUM('domain','hosting','theme','service','other') DEFAULT 'other', 89 renewal_date DATE DEFAULT NULL, 90 subscription_price DECIMAL(10,2) DEFAULT 0, 91 subscription_type ENUM('monthly','annual','lifetime') DEFAULT 'monthly', 92 notes TEXT DEFAULT NULL 93 ) {$charset_collate};"; 94 dbDelta( $sql3p ); 95 96 $sql_trials = "CREATE TABLE {$this->table_name_trials} ( 97 id INT AUTO_INCREMENT PRIMARY KEY, 98 trial_id CHAR(36) NOT NULL UNIQUE, 99 name VARCHAR(255) NOT NULL, 100 trial_type ENUM('7','14','30') NOT NULL DEFAULT '7', 101 amount_after_trial DECIMAL(10,2) DEFAULT NULL, 102 start_date DATE DEFAULT NULL, 103 end_date DATE DEFAULT NULL, 104 notes TEXT DEFAULT NULL 105 ) {$charset_collate};"; 106 dbDelta( $sql_trials ); 107 80 108 $this->palmssm_populate_initial_data(); 81 109 } 82 110 83 111 public function deactivate_plugin() { 84 // Placeholder for deactivation logic if needed in the future 85 } 86 112 // Placeholder for future deactivation logic. 113 } 114 115 /** 116 * Populate the main table with plugins if not already present. 117 * Now also generates a subscription_id. 118 */ 87 119 private function palmssm_populate_initial_data() { 88 120 global $wpdb; 89 121 $plugins = get_plugins(); 90 91 122 foreach ( $plugins as $plugin_slug => $plugin_data ) { 92 123 $plugin_slug_sanitized = sanitize_text_field( $plugin_slug ); … … 96 127 $this->table_name, 97 128 array( 129 'subscription_id' => wp_generate_uuid4(), 98 130 'plugin_slug' => $plugin_slug_sanitized, 131 'start_date' => null, 99 132 'plugin_type' => 'free', 100 'api_key' => '',101 133 'renewal_date' => null, 102 'subscription_price' => null,134 'subscription_price' => 0, 103 135 'subscription_type' => 'monthly', 104 ' paid' => 0,136 'notes' => '' 105 137 ), 106 array( '%s', '%s', '%s', '%s', '% f', '%s', '%d' )138 array( '%s', '%s', '%s', '%s', '%s', '%f', '%s', '%s' ) 107 139 ); 108 140 } … … 114 146 $filesystem_plugins = array_keys( get_plugins() ); 115 147 $filesystem_plugins = array_map( 'sanitize_text_field', $filesystem_plugins ); 116 117 $db_plugins = $wpdb->get_col( "SELECT plugin_slug FROM {$this->table_name}");148 // Use prepare() even though no placeholders are used. 149 $db_plugins = $wpdb->get_col( $wpdb->prepare( "SELECT plugin_slug FROM {$this->table_name}" ) ); 118 150 $db_plugins = array_map( 'sanitize_text_field', $db_plugins ); 119 120 151 $missing_plugins = array_diff( $filesystem_plugins, $db_plugins ); 121 $stale_plugins = array_diff( $db_plugins, $filesystem_plugins ); 122 152 $stale_plugins = array_diff( $db_plugins, $filesystem_plugins ); 123 153 foreach ( $missing_plugins as $plugin_slug ) { 124 154 $wpdb->insert( 125 155 $this->table_name, 126 156 array( 157 'subscription_id' => wp_generate_uuid4(), 127 158 'plugin_slug' => $plugin_slug, 128 'plugin_type' => 'free', // Default to 'free'; 129 'api_key' => '', 159 'plugin_type' => 'free', 130 160 'renewal_date' => null, 131 'subscription_price' => null,161 'subscription_price' => 0, 132 162 'subscription_type' => 'monthly', 133 ' paid' => 0,163 'notes' => '' 134 164 ), 135 array( '%s', '%s', '%s', '%s', '%f', '%s', '% d' )165 array( '%s', '%s', '%s', '%s', '%f', '%s', '%s' ) 136 166 ); 137 167 } 138 139 168 foreach ( $stale_plugins as $plugin_slug ) { 140 169 $plugin_slug_sanitized = sanitize_text_field( $plugin_slug ); … … 149 178 public function palmssm_sync_plugins() { 150 179 check_ajax_referer( 'palmssm_nonce', 'nonce' ); 151 152 180 if ( ! current_user_can( 'manage_options' ) ) { 153 181 wp_send_json_error( __( 'Unauthorized', 'subscription-tracker' ) ); 154 182 exit; 155 183 } 156 157 184 $this->palmssm_sync_plugins_to_database(); 158 185 wp_send_json_success( __( 'Plugins synchronized successfully.', 'subscription-tracker' ) ); … … 163 190 return; 164 191 } 165 166 192 wp_register_script( 'palmssm_psm-admin-js', $this->plugin_url . 'js/psm-admin.js', file_exists( $this->plugin_dir . 'js/psm-admin.js' ) ? filemtime( $this->plugin_dir . 'js/psm-admin.js' ) : '1.0', true ); 167 193 wp_register_style( 'palmssm_psm-admin-css', $this->plugin_url . 'js/psm-admin.css', array(), file_exists( $this->plugin_dir . 'js/psm-admin.css' ) ? filemtime( $this->plugin_dir . 'js/psm-admin.css' ) : '1.0' ); 168 169 194 wp_enqueue_script( 'palmssm_psm-admin-js' ); 170 wp_enqueue_style( 'palmssm_psm-admin-css' ); 171 195 wp_enqueue_style( 'palmssm_psm-admin-css' ); 172 196 wp_localize_script( 'palmssm_psm-admin-js', 'palmssm', array( 173 197 'ajax_url' => admin_url( 'admin-ajax.php' ), 174 198 'nonce' => wp_create_nonce( 'palmssm_nonce' ), 175 199 'i18n' => array( 176 'unauthorized' => __( 'Unauthorized', 'subscription-tracker' ),177 'plugins_synchronized' => __( 'Plugins synchronized successfully.', 'subscription-tracker' ),178 'sync_failed' => __( 'Plugin synchronization failed.', 'subscription-tracker' ),179 'export_failed' => __( 'Export failed.', 'subscription-tracker' ),200 'unauthorized' => __( 'Unauthorized', 'subscription-tracker' ), 201 'plugins_synchronized' => __( 'Plugins synchronized successfully.', 'subscription-tracker' ), 202 'sync_failed' => __( 'Plugin synchronization failed.', 'subscription-tracker' ), 203 'export_failed' => __( 'Export failed.', 'subscription-tracker' ), 180 204 ), 181 205 )); 182 206 } 183 207 184 185 208 public function palmssm_add_admin_menu() { 186 209 add_menu_page( … … 195 218 } 196 219 220 /** 221 * Merge subscriptions from both the main and 3p tables. 222 * The main table now returns its own subscription_id and uses plugin_slug. 223 * The 3p query renames its “name” column to plugin_name and returns a blank for api_key. 224 */ 225 private function get_all_subscriptions() { 226 global $wpdb; 227 // Main subscriptions query. 228 $query_main = $wpdb->prepare( 229 "SELECT 230 subscription_id, 231 plugin_slug, 232 start_date, 233 plugin_type, 234 renewal_date, 235 subscription_price, 236 subscription_type, 237 notes, 238 'plugin' as source_type 239 FROM {$this->table_name}" 240 ); 241 $subscriptions_main = $wpdb->get_results($query_main, ARRAY_A); 242 243 // If main table is empty, force-populate. 244 if ( empty($subscriptions_main) ) { 245 $this->palmssm_populate_initial_data(); 246 $subscriptions_main = $wpdb->get_results($query_main, ARRAY_A); 247 } 248 249 // Third-party subscriptions query. 250 $query_3p = $wpdb->prepare( 251 "SELECT 252 subscription_id, 253 name as plugin_name, 254 start_date, 255 plugin_type, 256 renewal_date, 257 subscription_price, 258 subscription_type, 259 notes, 260 'third_party' as source_type 261 FROM {$this->table_name_3p}" 262 ); 263 $subscriptions_3p = $wpdb->get_results($query_3p, ARRAY_A); 264 265 // For main subscriptions, derive a plugin name using the helper. 266 foreach ( $subscriptions_main as &$sub ) { 267 $sub['plugin_name'] = $this->palmssm_get_plugin_name( $sub['plugin_slug'] ); 268 } 269 unset($sub); 270 271 return array_merge( $subscriptions_main, $subscriptions_3p ); 272 } 273 197 274 public function render_admin_page() { 198 275 global $wpdb; 199 $plugins = get_plugins();200 $results = $wpdb->get_results( "SELECT * FROM {$this->table_name}", ARRAY_A );201 276 $report = $this->palmssm_generate_report(); 277 // Merge subscriptions from both tables. 278 $subscriptions = $this->get_all_subscriptions(); 202 279 ?> 203 280 <div class="wrap psm-container"> 204 281 <div class="psm-accessibility-bar"> 205 282 <h1 class="wp-heading-inline"><?php esc_html_e( 'Subscription Tracker by PalmsTrack', 'subscription-tracker' ); ?></h1> 206 <div class="psm-accessibility-group">207 <!-- Contrast Toggle Button -->208 <div class="psm-high-contrast-toggle">209 <button id="psm-high-contrast-toggle" class="accessibility-button" aria-pressed="false" aria-label="<?php esc_attr_e( 'Toggle High Contrast', 'subscription-tracker' ); ?>">210 <!-- SVG Icon for Contrast Toggle -->211 <svg fill="#fff" height="24px" width="24px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg"212 xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 285.919 285.919" xml:space="preserve">213 <g id="SVGRepo_bgCarrier" stroke-width="0"></g>214 <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>215 <g id="SVGRepo_iconCarrier">216 <path d="M142.959,0C64.131,0,0,64.132,0,142.96c0,78.828,64.131,142.959,142.959,142.959217 c78.828,0,142.96-64.131,142.96-142.959218 C285.919,64.132,221.787,0,142.959,0z M142.959,260.919V142.96V25219 c65.043,0,117.96,52.917,117.96,117.96220 C260.919,208.003,208.002,260.919,142.959,260.919z"></path>221 </g>222 </svg>223 </button>224 </div>225 226 <!-- Dark Mode Toggle -->227 <div class="psm-dark-mode-toggle">228 <label for="psm-dark-mode">229 <input type="checkbox" id="psm-dark-mode" />230 <span class="accessibility-button" aria-label="<?php esc_attr_e( 'Toggle Dark Mode', 'subscription-tracker' ); ?>">231 <span class="icon-moon">232 233 <svg width="31px" height="31px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M13.3986 7.64605C13.495 7.37724 13.88 7.37724 13.9764 7.64605L14.2401 8.38111C14.271 8.46715 14.3395 8.53484 14.4266 8.56533L15.1709 8.82579C15.443 8.92103 15.443 9.30119 15.1709 9.39644L14.4266 9.65689C14.3395 9.68738 14.271 9.75507 14.2401 9.84112L13.9764 10.5762C13.88 10.845 13.495 10.845 13.3986 10.5762L13.1349 9.84112C13.104 9.75507 13.0355 9.68738 12.9484 9.65689L12.2041 9.39644C11.932 9.30119 11.932 8.92103 12.2041 8.82579L12.9484 8.56533C13.0355 8.53484 13.104 8.46715 13.1349 8.38111L13.3986 7.64605Z" fill="#fff"></path> <path d="M16.3074 10.9122C16.3717 10.733 16.6283 10.733 16.6926 10.9122L16.8684 11.4022C16.889 11.4596 16.9347 11.5047 16.9928 11.525L17.4889 11.6987C17.6704 11.7622 17.6704 12.0156 17.4889 12.0791L16.9928 12.2527C16.9347 12.2731 16.889 12.3182 16.8684 12.3756L16.6926 12.8656C16.6283 13.0448 16.3717 13.0448 16.3074 12.8656L16.1316 12.3756C16.111 12.3182 16.0653 12.2731 16.0072 12.2527L15.5111 12.0791C15.3296 12.0156 15.3296 11.7622 15.5111 11.6987L16.0072 11.525C16.0653 11.5047 16.111 11.4596 16.1316 11.4022L16.3074 10.9122Z" fill="#fff"></path> <path d="M17.7693 3.29184C17.9089 2.90272 18.4661 2.90272 18.6057 3.29184L19.0842 4.62551C19.1288 4.75006 19.2281 4.84805 19.3542 4.89219L20.7045 5.36475C21.0985 5.50263 21.0985 6.05293 20.7045 6.19081L19.3542 6.66337C19.2281 6.7075 19.1288 6.80549 19.0842 6.93005L18.6057 8.26372C18.4661 8.65284 17.9089 8.65284 17.7693 8.26372L17.2908 6.93005C17.2462 6.80549 17.1469 6.7075 17.0208 6.66337L15.6705 6.19081C15.2765 6.05293 15.2765 5.50263 15.6705 5.36475L17.0208 4.89219C17.1469 4.84805 17.2462 4.75006 17.2908 4.62551L17.7693 3.29184Z" fill="#fff"></path> <path d="M3 13.4597C3 17.6241 6.4742 21 10.7598 21C14.0591 21 16.8774 18.9993 18 16.1783C17.1109 16.5841 16.1181 16.8109 15.0709 16.8109C11.2614 16.8109 8.17323 13.8101 8.17323 10.1084C8.17323 8.56025 8.71338 7.13471 9.62054 6C5.87502 6.5355 3 9.67132 3 13.4597Z" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>234 </span>235 <span class="icon-sun">236 237 <svg width="31px" height="31px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">238 <g id="SVGRepo_bgCarrier" stroke-width="0"></g>239 <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>240 <g id="SVGRepo_iconCarrier">241 <path d="M17 12C17 14.7614 14.7614 17 12 17C9.23858 17 7 14.7614 7 12C7 9.23858 9.23858 7 12 7C14.7614 7 17 9.23858 17 12Z" fill="#fff"></path>242 <path fill-rule="evenodd" clip-rule="evenodd" d="M12 1.25C12.4142 1.25 12.75 1.58579 12.75 2V4C12.75 4.41421 12.4142 4.75 12 4.75C11.5858 4.75 11.25 4.41421 11.25 4V2C11.25 1.58579 11.5858 1.25 12 1.25ZM3.66865 3.71609C3.94815 3.41039 4.42255 3.38915 4.72825 3.66865L6.95026 5.70024C7.25596 5.97974 7.2772 6.45413 6.9977 6.75983C6.7182 7.06553 6.2438 7.08677 5.9381 6.80727L3.71609 4.77569C3.41039 4.49619 3.38915 4.02179 3.66865 3.71609ZM20.3314 3.71609C20.6109 4.02179 20.5896 4.49619 20.2839 4.77569L18.0619 6.80727C17.7562 7.08677 17.2818 7.06553 17.0023 6.75983C16.7228 6.45413 16.744 5.97974 17.0497 5.70024L19.2718 3.66865C19.5775 3.38915 20.0518 3.41039 20.3314 3.71609ZM1.25 12C1.25 11.5858 1.58579 11.25 2 11.25H4C4.41421 11.25 4.75 11.5858 4.75 12C4.75 12.4142 4.41421 12.75 4 12.75H2C1.58579 12.75 1.25 12.4142 1.25 12ZM19.25 12C19.25 11.5858 19.5858 11.25 20 11.25H22C22.4142 11.25 22.75 11.5858 22.75 12C22.75 12.4142 22.4142 12.75 22 12.75H20C19.5858 12.75 19.25 12.4142 19.25 12ZM17.0255 17.0252C17.3184 16.7323 17.7933 16.7323 18.0862 17.0252L20.3082 19.2475C20.6011 19.5404 20.601 20.0153 20.3081 20.3082C20.0152 20.6011 19.5403 20.601 19.2475 20.3081L17.0255 18.0858C16.7326 17.7929 16.7326 17.3181 17.0255 17.0252ZM6.97467 17.0253C7.26756 17.3182 7.26756 17.7931 6.97467 18.086L4.75244 20.3082C4.45955 20.6011 3.98468 20.6011 3.69178 20.3082C3.39889 20.0153 3.39889 19.5404 3.69178 19.2476L5.91401 17.0253C6.2069 16.7324 6.68177 16.7324 6.97467 17.0253ZM12 19.25C12.4142 19.25 12.75 19.5858 12.75 20V22C12.75 22.4142 12.4142 22.75 12 22.75C11.5858 22.75 11.25 22.4142 11.25 22V20C11.25 19.5858 11.5858 19.25 12 19.25Z" fill="#fff"></path>243 </g>244 </svg>245 </span>246 </span>247 </label>248 </div>249 250 </div>251 283 </div> 252 253 284 <hr class="wp-header-end"> 254 255 285 <div class="psm-report-bar"> 256 286 <div class="psm-report-item"> … … 287 317 ) 288 318 ); 289 290 319 $total_subscription_cost = ( ! is_null( $total_subscription_cost ) ) ? floatval( $total_subscription_cost ) : 0; 291 echo esc_html( number_format( $total_subscription_cost, 2 ) ); 320 echo esc_html( number_format( $total_subscription_cost, 2 ) ); 292 321 ?> 293 322 </div> … … 295 324 </div> 296 325 </div> 297 298 <!-- Views Container --> 299 <div class="psm-views-container"> 326 <div class="palmss_controls"> 327 <div class="psm-view-tabs"> 328 <button id="psm-tab-table" class="psm-view-tab active" data-target="table"> 329 <span class="psm-legend-square" style="background-color: var(--psm-subscriptions-color);"></span> 330 <span class="dashicons dashicons-list-view"></span> 331 <?php esc_html_e( 'Subscriptions', 'subscription-tracker' ); ?> 332 </button> 300 333 301 <!-- Plugin Table --> 302 <div id="psm-table-view"> 303 <table class="wp-list-table widefat fixed striped psm-table"> 304 <thead> 305 <tr> 306 <th><?php esc_html_e( 'Plugin Name', 'subscription-tracker' ); ?></th> 307 <th><?php esc_html_e( 'Type', 'subscription-tracker' ); ?></th> 308 <th><?php esc_html_e( 'Billing', 'subscription-tracker' ); ?></th> 309 <th><?php esc_html_e( 'License Key', 'subscription-tracker' ); ?></th> 310 <th><?php esc_html_e( 'Renewal Date', 'subscription-tracker' ); ?></th> 311 <th><?php esc_html_e( 'Amount', 'subscription-tracker' ); ?></th> 312 <th><?php esc_html_e( 'Paid', 'subscription-tracker' ); ?></th> 313 </tr> 314 </thead> 315 <tbody> 316 <?php foreach ( $plugins as $plugin_slug => $plugin_data ) : 317 $subscription = $wpdb->get_row( 318 $wpdb->prepare( "SELECT * FROM {$this->table_name} WHERE plugin_slug = %s", sanitize_text_field( $plugin_slug ) ), 319 ARRAY_A 320 ); 321 $id = isset( $subscription['id'] ) ? intval( $subscription['id'] ) : 0; 322 $plugin_type = isset( $subscription['plugin_type'] ) ? sanitize_text_field( $subscription['plugin_type'] ) : 'free'; 323 $subscription_type = isset( $subscription['subscription_type'] ) ? sanitize_text_field( $subscription['subscription_type'] ) : 'monthly'; 324 $api_key = isset( $subscription['api_key'] ) ? sanitize_text_field( $subscription['api_key'] ) : ''; 325 $renewal_date = isset( $subscription['renewal_date'] ) ? sanitize_text_field( $subscription['renewal_date'] ) : ''; 326 $subscription_price= isset( $subscription['subscription_price'] ) ? floatval( $subscription['subscription_price'] ) : 0.00; 327 $paid = isset( $subscription['paid'] ) ? intval( $subscription['paid'] ) : 0; 328 329 $valid_plugin_types = array( 'free', 'premium', 'custom' ); 330 $valid_subscription_types = array( 'monthly', 'annual' ); 331 332 if ( ! in_array( $plugin_type, $valid_plugin_types, true ) ) { 333 $plugin_type = 'free'; 334 } 335 336 if ( 'premium' === $plugin_type && ! in_array( $subscription_type, $valid_subscription_types, true ) ) { 337 $subscription_type = 'monthly'; 338 } 339 340 ?> 341 <tr class="psm-plugin-row" data-plugin="<?php echo esc_attr( $plugin_slug ); ?>" data-id="<?php echo esc_attr( $id ); ?>"> 342 <td><?php echo esc_html( $plugin_data['Name'] ); ?></td> 343 <td> 344 <select class="psm-plugin-type" data-plugin="<?php echo esc_attr( $plugin_slug ); ?>" data-id="<?php echo esc_attr( $id ); ?>"> 345 <option value="free" <?php selected( $plugin_type, 'free' ); ?>><?php esc_html_e( 'Free', 'subscription-tracker' ); ?></option> 346 <option value="premium" <?php selected( $plugin_type, 'premium' ); ?>><?php esc_html_e( 'Premium', 'subscription-tracker' ); ?></option> 347 <option value="custom" <?php selected( $plugin_type, 'custom' ); ?>><?php esc_html_e( 'Custom', 'subscription-tracker' ); ?></option> 348 </select> 349 </td> 350 <td> 351 <select class="psm-subscription-type" data-plugin="<?php echo esc_attr( $plugin_slug ); ?>" data-id="<?php echo esc_attr( $id ); ?>" <?php echo ( 'premium' !== $plugin_type ) ? 'disabled' : ''; ?>> 352 <option value="monthly" <?php selected( $subscription_type, 'monthly' ); ?>><?php esc_html_e( 'Monthly', 'subscription-tracker' ); ?></option> 353 <option value="annual" <?php selected( $subscription_type, 'annual' ); ?>><?php esc_html_e( 'Annual', 'subscription-tracker' ); ?></option> 354 </select> 355 </td> 356 <td> 357 <input placeholder="<?php esc_attr_e( 'License Key (e.g., 12345-ABCDE)', 'subscription-tracker' ); ?>" type="text" class="psm-api-key" data-plugin="<?php echo esc_attr( $plugin_slug ); ?>" data-id="<?php echo esc_attr( $id ); ?>" value="<?php echo esc_attr( $api_key ); ?>" <?php echo ( 'premium' !== $plugin_type ) ? 'disabled' : ''; ?>> 358 </td> 359 <td> 360 <input type="date" class="psm-renewal-date" data-plugin="<?php echo esc_attr( $plugin_slug ); ?>" data-id="<?php echo esc_attr( $id ); ?>" value="<?php echo esc_attr( $renewal_date ); ?>" <?php echo ( 'premium' !== $plugin_type ) ? 'disabled' : ''; ?>> 361 </td> 362 <td style="display:flex"> 363 <input placeholder="<?php esc_attr_e( 'Price per month/year (e.g., 39.99)', 'subscription-tracker' ); ?>" type="number" step="0.01" min="0" class="psm-subscription-price" data-plugin="<?php echo esc_attr( $plugin_slug ); ?>" data-id="<?php echo esc_attr( $id ); ?>" value="<?php echo esc_attr( $subscription_price ); ?>" <?php echo ( 'premium' !== $plugin_type ) ? 'disabled' : ''; ?>> 364 </td> 365 <td> 366 <label class="checkbox-container"> 367 <input type="checkbox" class="psm-paid-checkbox" data-plugin="<?php echo esc_attr( $plugin_slug ); ?>" data-id="<?php echo esc_attr( $id ); ?>" <?php checked( $paid, 1 ); ?>> 368 <span class="checkmark"></span> 369 </label> 370 </td> 371 </tr> 372 <?php endforeach; ?> 373 </tbody> 374 </table> 375 </div> 376 334 </div> 335 336 <div class="palmsst_controls"> 337 <button id="add-subscription" class="button button-primary">Add Subscription</button> 338 </div> 339 </div> 340 <div class="psm-views-container"> 341 <div id="psm-calendar-view" style="display: none; position: relative;"> 342 <div class="calendar-glass-overlay"> 343 <button class="button button-primary upgrade-to-pro-button">Upgrade to Pro</button> 344 </div> 345 <div id="psm-calendar"></div> 346 </div> 347 <div id="psm-trials-view" style="display: none;"> 348 <table class="psm-trials"> 349 <thead> 350 <tr> 351 <th><?php esc_html_e( 'Name', 'subscription-tracker' ); ?></th> 352 <th><?php esc_html_e( 'Trial Type', 'subscription-tracker' ); ?></th> 353 <th><?php esc_html_e( 'Start Date', 'subscription-tracker' ); ?></th> 354 <th><?php esc_html_e( 'End Date', 'subscription-tracker' ); ?></th> 355 <th><?php esc_html_e( 'Days Left', 'subscription-tracker' ); ?></th> 356 <th><?php esc_html_e( 'Amount (after trial)', 'subscription-tracker' ); ?> (<?php echo esc_html( $currency_symbol ); ?>)</th> 357 <th><?php esc_html_e( 'Manage', 'subscription-tracker' ); ?></th> 358 </tr> 359 </thead> 360 <tbody> 361 <?php 362 // Retrieve trials from the database. 363 $trials_table = $wpdb->prefix . 'palms_subscription_tracker_trials'; 364 // Using prepare() with no placeholders to satisfy code sniffer. 365 $trials = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$trials_table} ORDER BY end_date ASC" ), ARRAY_A ); 366 if ( $trials ) { 367 foreach ( $trials as $trial ) { 368 // Use gmdate() to get today's date. 369 $today = strtotime(gmdate('Y-m-d')); 370 $end_date = strtotime($trial['end_date']); 371 $days_left = ($end_date > $today) ? floor(($end_date - $today) / (60 * 60 * 24)) : 0; 372 ?> 373 <tr class="psm-plugin-row" data-trial_id="<?php echo esc_attr( $trial['trial_id'] ); ?>"> 374 <td class="psm-col-name"><?php echo esc_html( $trial['name'] ); ?></td> 375 <td><?php echo esc_html( $trial['trial_type'] ); ?> Day</td> 376 <td><?php echo esc_html( $trial['start_date'] ); ?></td> 377 <td><?php echo esc_html( $trial['end_date'] ); ?></td> 378 <td><?php echo esc_html( $days_left ); ?></td> 379 <td><?php echo ($trial['amount_after_trial'] !== null) ? esc_html(number_format($trial['amount_after_trial'], 2)) : ''; ?></td> 380 <td class="psm-actions"> 381 <div class="dropdown"> 382 <button class="dropdown-toggle" title="<?php esc_attr_e( 'Actions', 'subscription-tracker' ); ?>"> 383 <span class="vertical-dots">⋮</span> 384 </button> 385 <div class="dropdown-menu"> 386 <div class="trial-notes-button palms-action-button" 387 data-trial_id="<?php echo esc_attr( $trial['trial_id'] ); ?>" 388 title="<?php esc_attr_e( 'Add/View Notes', 'subscription-tracker' ); ?>"> 389 <span>Notes</span> 390 </div> 391 <div class="delete-trial-button palms-action-button" 392 data-trial_id="<?php echo esc_attr( $trial['trial_id'] ); ?>" 393 title="<?php esc_attr_e( 'Delete Trial', 'subscription-tracker' ); ?>"> 394 <span>Delete</span> 395 </div> 396 </div> 397 </div> 398 </td> 399 </tr> 400 <?php 401 } 402 } else { 403 echo '<tr><td colspan="7">' . esc_html__( 'No trials found.', 'subscription-tracker' ) . '</td></tr>'; 404 } 405 ?> 406 </tbody> 407 </table> 408 </div> 409 410 <div id="psm-table-view"> 411 <table class="wp-list-table widefat fixed striped psm-table"> 412 <thead> 413 <tr> 414 <th><?php esc_html_e( 'Name', 'subscription-tracker' ); ?></th> 415 <th><?php esc_html_e( 'Type', 'subscription-tracker' ); ?></th> 416 <th><?php esc_html_e( 'Billing', 'subscription-tracker' ); ?></th> 417 <th><?php esc_html_e( 'Start Date', 'subscription-tracker' ); ?></th> 418 <th><?php esc_html_e( 'Renewal Date', 'subscription-tracker' ); ?></th> 419 <th><?php esc_html_e( 'Amount', 'subscription-tracker' ); ?></th> 420 </tr> 421 </thead> 422 <tbody> 423 <?php foreach ( $subscriptions as $sub ) : 424 if ( 'third_party' === $sub['source_type'] ) { 425 $plugin_name = $sub['plugin_name']; 426 } else { 427 $plugin_name = $this->palmssm_get_plugin_name( $sub['plugin_slug'] ); 428 } 429 $plugin_type = isset( $sub['plugin_type'] ) ? sanitize_text_field( $sub['plugin_type'] ) : 'free'; 430 $subscription_type = isset( $sub['subscription_type'] ) ? sanitize_text_field( $sub['subscription_type'] ) : 'monthly'; 431 $start_date = isset( $sub['start_date'] ) ? sanitize_text_field( $sub['start_date'] ) : ''; 432 $renewal_date = isset( $sub['renewal_date'] ) ? sanitize_text_field( $sub['renewal_date'] ) : ''; 433 $subscription_price = isset( $sub['subscription_price'] ) ? floatval( $sub['subscription_price'] ) : 0.00; 434 435 if ( 'plugin' === $sub['source_type'] && $plugin_type === 'free' ) { 436 $disabled_others = 'disabled'; 437 $disabled_type = ''; 438 } else { 439 $disabled_others = ''; 440 $disabled_type = ''; 441 } 442 ?> 443 <tr class="psm-plugin-row" data-id="<?php echo esc_attr( $sub['subscription_id'] ); ?>" data-source="<?php echo esc_attr( $sub['source_type'] ); ?>"> 444 <td><?php echo esc_html( $plugin_name ); ?></td> 445 <td> 446 <?php if ( 'third_party' === $sub['source_type'] ) : ?> 447 <select class="psm-plugin-type" data-id="<?php echo esc_attr( $sub['subscription_id'] ); ?>"> 448 <option value="domain" <?php selected( $plugin_type, 'domain' ); ?>><?php esc_html_e( 'Domain', 'subscription-tracker' ); ?></option> 449 <option value="hosting" <?php selected( $plugin_type, 'hosting' ); ?>><?php esc_html_e( 'Hosting', 'subscription-tracker' ); ?></option> 450 <option value="theme" <?php selected( $plugin_type, 'theme' ); ?>><?php esc_html_e( 'Theme', 'subscription-tracker' ); ?></option> 451 <option value="service" <?php selected( $plugin_type, 'service' ); ?>><?php esc_html_e( 'Service', 'subscription-tracker' ); ?></option> 452 <option value="other" <?php selected( $plugin_type, 'other' ); ?>><?php esc_html_e( 'Other', 'subscription-tracker' ); ?></option> 453 </select> 454 <?php else : ?> 455 <select class="psm-plugin-type" data-id="<?php echo esc_attr( $sub['subscription_id'] ); ?>" <?php echo esc_attr( $disabled_type ); ?>> 456 <option value="free" <?php selected( $plugin_type, 'free' ); ?>><?php esc_html_e( 'Free', 'subscription-tracker' ); ?></option> 457 <option value="premium" <?php selected( $plugin_type, 'premium' ); ?>><?php esc_html_e( 'Premium', 'subscription-tracker' ); ?></option> 458 <option value="custom" <?php selected( $plugin_type, 'custom' ); ?>><?php esc_html_e( 'Custom', 'subscription-tracker' ); ?></option> 459 </select> 460 <?php endif; ?> 461 </td> 462 <td> 463 <select class="psm-subscription-type" data-id="<?php echo esc_attr( $sub['subscription_id'] ); ?>" <?php echo esc_attr( $disabled_others ); ?>> 464 <option value="monthly" <?php selected( $subscription_type, 'monthly' ); ?>><?php esc_html_e( 'Monthly', 'subscription-tracker' ); ?></option> 465 <option value="annual" <?php selected( $subscription_type, 'annual' ); ?>><?php esc_html_e( 'Annual', 'subscription-tracker' ); ?></option> 466 <?php if ( 'third_party' === $sub['source_type'] ) : ?> 467 <option value="lifetime" <?php selected( $subscription_type, 'lifetime' ); ?>><?php esc_html_e( 'Lifetime', 'subscription-tracker' ); ?></option> 468 <?php endif; ?> 469 </select> 470 </td> 471 <td> 472 <input type="date" class="psm-start-date" data-id="<?php echo esc_attr( $sub['subscription_id'] ); ?>" value="<?php echo esc_attr( $start_date ); ?>" <?php echo esc_attr( $disabled_others ); ?>> 473 </td> 474 <td> 475 <input type="date" class="psm-renewal-date" data-id="<?php echo esc_attr( $sub['subscription_id'] ); ?>" value="<?php echo esc_attr( $renewal_date ); ?>" <?php echo esc_attr( $disabled_others ); ?>> 476 </td> 477 <td style="display:flex"> 478 <input placeholder="<?php esc_attr_e( 'Amount (e.g., 39.99)', 'subscription-tracker' ); ?>" type="number" step="0.01" min="0" class="psm-subscription-price" data-id="<?php echo esc_attr( $sub['subscription_id'] ); ?>" value="<?php echo esc_attr( $subscription_price ); ?>" <?php echo esc_attr( $disabled_others ); ?>> 479 </td> 480 481 </tr> 482 <?php endforeach; ?> 483 </tbody> 484 </table> 485 </div> 486 377 487 </div> 378 488 <div class="psm-actions-container" style="margin-top: 20px;"> … … 380 490 <button id="psm-export" class="button button-primary"><?php esc_html_e( 'Export', 'subscription-tracker' ); ?></button> 381 491 </div> 492 493 494 495 <div id="subscription-modal" class="psm-modal"> 496 <div class="modal-content"> 497 <div class="modal-body"> 498 <form id="add-subscription-form"> 499 <div class="form-group"> 500 <label for="subscription-name">Subscription Name</label> 501 <input type="text" id="subscription-name" name="subscription_name" placeholder="Enter subscription name" required> 502 </div> 503 <div class="form-group"> 504 <label for="subscription-type">Subscription Type</label> 505 <select id="subscription-type" name="plugin_type"> 506 <option value="domain">Domain</option> 507 <option value="hosting">Hosting</option> 508 <option value="theme">Theme</option> 509 <option value="service">Service</option> 510 <option value="other">Other</option> 511 </select> 512 </div> 513 <div class="form-group"> 514 <label for="start-date">Start Date</label> 515 <input type="date" id="start-date" name="start_date"> 516 </div> 517 <div class="form-group"> 518 <label for="subscription-price">Amount</label> 519 <input type="number" id="subscription-price" name="price" step="0.01" min="0" placeholder="Enter price"> 520 </div> 521 <div class="form-group"> 522 <label for="billing-type">Billing Type</label> 523 <select id="billing-type" name="subscription_type"> 524 <option value="monthly">Monthly</option> 525 <option value="annual">Yearly</option> 526 <option value="lifetime">Lifetime</option> 527 </select> 528 </div> 529 <div class="form-actions"> 530 <button type="submit" class="button save">Save Subscription</button> 531 </div> 532 </form> 533 </div> 534 </div> 535 </div> 536 537 538 382 539 </div> 383 540 <?php … … 387 544 $user_id = get_current_user_id(); 388 545 global $wpdb; 389 $today = gmdate( 'Y-m-d' );546 $today = gmdate( 'Y-m-d' ); 390 547 $alert_days = gmdate( 'Y-m-d', strtotime( '+7 days' ) ); 391 548 $renewals = $wpdb->get_results( 392 549 $wpdb->prepare( 393 "SELECT plugin_slug, renewal_date FROM {$this->table_name}550 "SELECT plugin_slug, start_date FROM {$this->table_name} 394 551 WHERE renewal_date BETWEEN %s AND %s 395 AND plugin_type = %s 396 AND paid = %d", 552 AND plugin_type = %s", 397 553 $today, 398 554 $alert_days, 399 'premium', 400 0 555 'premium' 401 556 ) 402 557 ); … … 404 559 $alert_dismissed = get_user_meta( $user_id, '_palmssm_renewal_alert_dismissed', true ); 405 560 if ( $alert_dismissed ) { return; } 406 407 561 echo '<div class="notice notice-warning is-dismissible psm-renewal-alert" style="padding:20px;">'; 408 562 echo '<p style="font-size:18px; margin:0"><strong>' . esc_html__( 'Renewal Alert! Due in 7 Days', 'subscription-tracker' ) . '</strong></p>'; … … 412 566 $plugin_name = $this->palmssm_get_plugin_name( $renewal->plugin_slug ); 413 567 $plugin_name_display = ! empty( $plugin_name ) ? esc_html( $plugin_name ) : esc_html( $renewal->plugin_slug ); 414 $renewal_date = ! empty( $renewal->renewal_date ) 415 ? esc_html( gmdate( 'M j, Y', strtotime( $renewal->renewal_date ) ) ) 416 : esc_html__( 'N/A', 'subscription-tracker' ); 568 $renewal_date = ! empty( $renewal->renewal_date ) ? esc_html( gmdate( 'M j, Y', strtotime( $renewal->renewal_date ) ) ) : esc_html__( 'N/A', 'subscription-tracker' ); 417 569 echo '<tr>'; 418 570 echo '<td>' . esc_html( $plugin_name_display ) . '</td>'; … … 421 573 } 422 574 echo '</tbody></table></div>'; 423 424 }425 426 public function palmssm_clear_renewal_alert() {427 check_ajax_referer( 'palmssm_nonce', 'nonce' );428 $user_id = get_current_user_id();429 update_user_meta( $user_id, '_palmssm_renewal_alert_dismissed', true );430 wp_send_json_success( __( 'Alert cleared.', 'subscription-tracker' ) );431 575 } 432 576 … … 437 581 public function palmssm_generate_report() { 438 582 global $wpdb; 439 $total_plugins = intval( $wpdb->get_var( "SELECT COUNT(*) FROM {$this->table_name}") );440 $free_plugins = intval( $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$this->table_name} WHERE plugin_type = %s", 'free' ) ) );441 $premium_plugins = intval( $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$this->table_name} WHERE plugin_type = %s", 'premium' ) ) );442 $custom_plugins = intval( $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$this->table_name} WHERE plugin_type = %s", 'custom' ) ) );443 $today = gmdate( 'Y-m-d' );444 $next_month = gmdate( 'Y-m-d', strtotime( '+30 days' ) );445 $upcoming_renewals = intval( $wpdb->get_var(583 $total_plugins = intval( $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$this->table_name}" ) ) ); 584 $free_plugins = intval( $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$this->table_name} WHERE plugin_type = %s", 'free' ) ) ); 585 $premium_plugins = intval( $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$this->table_name} WHERE plugin_type = %s", 'premium' ) ) ); 586 $custom_plugins = intval( $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$this->table_name} WHERE plugin_type = %s", 'custom' ) ) ); 587 $today = gmdate( 'Y-m-d' ); 588 $next_month = gmdate( 'Y-m-d', strtotime( '+30 days' ) ); 589 $upcoming_renewals = intval( $wpdb->get_var( 446 590 $wpdb->prepare( 447 591 "SELECT COUNT(*) FROM {$this->table_name} WHERE renewal_date BETWEEN %s AND %s AND plugin_type = %s", … … 451 595 ) 452 596 ) ); 453 454 597 return array( 455 598 'total_plugins' => $total_plugins, … … 471 614 public function render_dashboard_widget() { 472 615 global $wpdb; 473 $today = gmdate( 'Y-m-d' );616 $today = gmdate( 'Y-m-d' ); 474 617 $next_month = gmdate( 'Y-m-d', strtotime( '+30 days' ) ); 475 $renewals = $wpdb->get_results(618 $renewals = $wpdb->get_results( 476 619 $wpdb->prepare( 477 620 "SELECT plugin_slug, renewal_date, subscription_price … … 486 629 ARRAY_A 487 630 ); 488 489 631 if ( ! empty( $renewals ) ) { 490 632 echo '<table class="widefat fixed">'; 491 633 echo '<thead><tr><th>' . esc_html__( 'Plugin', 'subscription-tracker' ) . '</th><th>' . esc_html__( 'Renewal Date', 'subscription-tracker' ) . '</th><th>' . esc_html__( 'Amount', 'subscription-tracker' ) . '</th></tr></thead><tbody>'; 492 493 634 foreach ( $renewals as $renewal ) { 494 $plugin_name = $this->palmssm_get_plugin_name( $renewal['plugin_slug'] );495 $renewal_date = ! empty( $renewal['renewal_date'] ) ? esc_html( gmdate( 'F j, Y', strtotime( $renewal['renewal_date'] ) ) ) : esc_html__( 'N/A', 'subscription-tracker' );635 $plugin_name = $this->palmssm_get_plugin_name( $renewal['plugin_slug'] ); 636 $renewal_date = ! empty( $renewal['renewal_date'] ) ? esc_html( gmdate( 'F j, Y', strtotime( $renewal['renewal_date'] ) ) ) : esc_html__( 'N/A', 'subscription-tracker' ); 496 637 $subscription_price = ! empty( $renewal['subscription_price'] ) ? esc_html( number_format( floatval( $renewal['subscription_price'] ), 2 ) ) : esc_html__( '0.00', 'subscription-tracker' ); 497 498 638 echo '<tr>'; 499 639 echo '<td>' . esc_html( $plugin_name ) . '</td>'; … … 502 642 echo '</tr>'; 503 643 } 504 505 644 echo '</tbody></table>'; 506 645 } else { … … 509 648 } 510 649 650 // Update plugin data – now without license key. 511 651 public function palmssm_save_plugin_data() { 512 652 check_ajax_referer( 'palmssm_nonce', 'nonce' ); 513 514 653 if ( ! current_user_can( 'manage_options' ) ) { 515 654 wp_send_json_error( __( 'Unauthorized', 'subscription-tracker' ) ); 516 655 exit; 517 656 } 518 519 $plugin_slug = isset( $_POST['plugin'] ) ? sanitize_text_field( $_POST['plugin'] ) : ''; 520 $plugin_type = isset( $_POST['plugin_type'] ) ? sanitize_text_field( $_POST['plugin_type'] ) : ''; 657 $plugin_slug = isset( $_POST['plugin'] ) ? sanitize_text_field( $_POST['plugin'] ) : ''; 658 $plugin_type = isset( $_POST['plugin_type'] ) ? sanitize_text_field( $_POST['plugin_type'] ) : ''; 521 659 $subscription_type = isset( $_POST['subscription_type'] ) ? sanitize_text_field( $_POST['subscription_type'] ) : ''; 522 $api_key = isset( $_POST['api_key'] ) ? sanitize_text_field( $_POST['api_key'] ) : ''; 523 $renewal_date = isset( $_POST['renewal_date'] ) ? sanitize_text_field( $_POST['renewal_date'] ) : ''; 524 $subscription_price= isset( $_POST['subscription_price'] ) ? floatval( $_POST['subscription_price'] ) : 0.00; 525 526 $valid_plugin_types = array( 'free', 'premium', 'custom' ); 527 $valid_subscription_types = array( 'monthly', 'annual' ); 528 660 $renewal_date = isset( $_POST['renewal_date'] ) ? sanitize_text_field( $_POST['renewal_date'] ) : ''; 661 $subscription_price = isset( $_POST['subscription_price'] ) ? floatval( $_POST['subscription_price'] ) : 0.00; 662 $valid_plugin_types = array( 'free', 'premium', 'custom' ); 663 $valid_subscription_types = array( 'monthly', 'annual', 'lifetime'); 529 664 if ( ! in_array( $plugin_type, $valid_plugin_types, true ) ) { 530 665 wp_send_json_error( __( 'Invalid plugin type.', 'subscription-tracker' ) ); 531 666 exit; 532 667 } 533 534 668 if ( 'premium' === $plugin_type && ! in_array( $subscription_type, $valid_subscription_types, true ) ) { 535 669 wp_send_json_error( __( 'Invalid subscription type.', 'subscription-tracker' ) ); 536 670 exit; 537 671 } 538 539 672 $data = array( 540 673 'plugin_type' => $plugin_type, 541 674 'subscription_type' => $subscription_type, 542 'api_key' => $api_key,543 675 'renewal_date' => $renewal_date, 544 676 'subscription_price' => $subscription_price, 545 677 ); 546 547 $format = array( 548 '%s', 549 '%s', 550 '%s', 551 '%s', 552 '%f', 553 ); 554 678 $format = array( '%s', '%s', '%s', '%f' ); 555 679 global $wpdb; 556 680 $result = $wpdb->update( … … 561 685 array( '%s' ) 562 686 ); 563 564 687 if ( false === $result ) { 565 688 wp_send_json_error( __( 'Database update failed.', 'subscription-tracker' ) ); … … 569 692 } 570 693 694 // Export function uses only the main table. 571 695 public function palmssm_export_plugin_data() { 572 696 check_ajax_referer( 'palmssm_nonce', 'nonce' ); 573 574 697 if ( ! current_user_can( 'manage_options' ) ) { 575 698 wp_die( esc_html__( 'Unauthorized access.', 'subscription-tracker' ) ); 576 699 } 577 578 700 global $wpdb, $wp_filesystem; 579 580 701 if ( ! function_exists( 'WP_Filesystem' ) ) { 581 702 require_once ABSPATH . 'wp-admin/includes/file.php'; 582 703 } 583 584 704 WP_Filesystem(); 585 586 $format = isset( $_GET['format'] ) ? sanitize_text_field( $_GET['format'] ) : 'csv'; 705 $format = isset( $_GET['format'] ) ? sanitize_text_field($_GET['format']) : 'csv'; 587 706 $allowed_formats = array( 'csv' ); 588 589 707 if ( ! in_array( strtolower( $format ), $allowed_formats, true ) ) { 590 708 wp_die( esc_html__( 'Invalid format specified.', 'subscription-tracker' ) ); 591 709 } 592 593 710 $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$this->table_name} ORDER BY plugin_slug ASC" ), ARRAY_A ); 594 595 711 if ( empty( $results ) ) { 596 712 wp_die( esc_html__( 'No data available for export.', 'subscription-tracker' ) ); 597 713 } 598 599 714 $site_url = sanitize_file_name( wp_parse_url( get_site_url(), PHP_URL_HOST ) ); 600 715 $filename = "palms_subscription_tracker_{$site_url}.csv"; 601 716 $csv_content = ''; 602 $headers = array( 'Plugin', 'Type', ' License Key', 'Renewal Date', 'Cost', 'Recurrence', 'Paid' );717 $headers = array( 'Plugin', 'Type', 'Renewal Date', 'Cost', 'Recurrence' ); 603 718 $csv_content .= implode( ',', array_map( array( $this, 'palmssm_esc_csv' ), $headers ) ) . "\n"; 604 605 719 foreach ( $results as $row ) { 606 720 $plugin_name = $this->palmssm_get_plugin_name( $row['plugin_slug'] ); … … 608 722 $plugin_name, 609 723 ucfirst( sanitize_text_field( $row['plugin_type'] ) ), 610 $row['api_key'],611 724 $row['renewal_date'], 612 725 number_format( floatval( $row['subscription_price'] ), 2 ), 613 ucfirst( sanitize_text_field( $row['subscription_type'] ) ), 614 $row['paid'] ? __( 'Yes', 'subscription-tracker' ) : __( 'No', 'subscription-tracker' ), 726 ucfirst( sanitize_text_field( $row['subscription_type'] ) ) 615 727 ); 616 728 $csv_content .= implode( ',', array_map( array( $this, 'palmssm_esc_csv' ), $data ) ) . "\n"; 617 729 } 618 619 730 $tmp_file = wp_tempnam( $filename ); 620 621 731 if ( ! $wp_filesystem->put_contents( $tmp_file, $csv_content, FS_CHMOD_FILE ) ) { 622 732 wp_die( esc_html__( 'Failed to write CSV file.', 'subscription-tracker' ) ); 623 733 } 624 625 734 header( 'Content-Type: text/csv; charset=utf-8' ); 626 735 header( 'Content-Disposition: attachment; filename=' . esc_attr( $filename ) ); … … 639 748 } 640 749 750 // Use plugin_slug to get the plugin name. 641 751 private function palmssm_get_plugin_name( $plugin_slug ) { 642 $plugin_path = WP_PLUGIN_DIR . '/' . $plugin_slug; // Corrected path to use WP_PLUGIN_DIR752 $plugin_path = WP_PLUGIN_DIR . '/' . $plugin_slug; 643 753 if ( file_exists( $plugin_path ) ) { 644 754 $plugin_data = get_plugin_data( $plugin_path ); … … 649 759 } 650 760 651 public function palmssm_ toggle_paid_status() {761 public function palmssm_add_subscription() { 652 762 check_ajax_referer( 'palmssm_nonce', 'nonce' ); 653 654 763 if ( ! current_user_can( 'manage_options' ) ) { 655 wp_send_json_error( __( 'Unauthorized', 'subscription-tracker' ) ); 656 exit; 657 } 658 659 $plugin_slug = isset( $_POST['plugin_slug'] ) ? sanitize_text_field( $_POST['plugin_slug'] ) : ''; 660 $paid_status = isset( $_POST['paid'] ) ? intval( $_POST['paid'] ) : 0; 661 662 $paid_status = in_array( $paid_status, array( 0, 1 ), true ) ? $paid_status : 0; 663 664 if ( empty( $plugin_slug ) ) { 665 wp_send_json_error( __( 'Invalid plugin slug.', 'subscription-tracker' ) ); 666 exit; 667 } 668 669 global $wpdb; 670 $plugin_exists = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$this->table_name} WHERE plugin_slug = %s", $plugin_slug ) ); 671 if ( ! $plugin_exists ) { 672 wp_send_json_error( __( 'Plugin does not exist.', 'subscription-tracker' ) ); 673 exit; 674 } 675 676 $result = $wpdb->update( 677 $this->table_name, 678 array( 'paid' => $paid_status ), 679 array( 'plugin_slug' => $plugin_slug ), 680 array( '%d' ), 681 array( '%s' ) 682 ); 683 684 if ( false === $result ) { 685 wp_send_json_error( __( 'Failed to update paid status.', 'subscription-tracker' ) ); 686 } else { 687 $unpaid_renewals = $wpdb->get_var( 688 $wpdb->prepare( 689 "SELECT COUNT(*) FROM {$this->table_name} 690 WHERE plugin_type = %s AND paid = %d AND renewal_date BETWEEN %s AND %s", 691 'premium', 692 0, 693 gmdate( 'Y-m-d' ), 694 gmdate( 'Y-m-d', strtotime( '+3 days' ) ) 695 ) 696 ); 697 698 if ( $unpaid_renewals == 0 ) { 699 update_user_meta( get_current_user_id(), '_palmssm_renewal_alert_dismissed', true ); 700 } else { 701 delete_user_meta( get_current_user_id(), '_palmssm_renewal_alert_dismissed' ); 702 } 703 wp_send_json_success( __( 'Paid status updated.', 'subscription-tracker' ) ); 704 } 705 } 706 764 wp_send_json_error( 'Unauthorized' ); 765 } 766 global $wpdb; 767 $plugin_name = isset($_POST['plugin_name']) ? sanitize_text_field($_POST['plugin_name']) : ''; 768 $start_date = isset($_POST['start_date']) ? sanitize_text_field($_POST['start_date']) : ''; 769 if ( empty( $plugin_name ) ) { 770 wp_send_json_error( 'Plugin name is required.' ); 771 } 772 // Generate a plugin slug from the plugin name. 773 $plugin_slug = sanitize_title( $plugin_name ); 774 // Calculate the renewal date using the same logic (assuming 'monthly' billing for third‐party subscriptions). 775 $renewal_date = !empty($start_date) ? $this->calculate_next_renewal($start_date, 'monthly') : null; 776 // Insert into the third-party table using a valid default for plugin_type. 777 $result = $wpdb->insert( 778 $this->table_name_3p, 779 array( 780 'subscription_id' => wp_generate_uuid4(), 781 'name' => $plugin_name, 782 'start_date' => $start_date, 783 'plugin_type' => 'other', // Changed from 'premium' to 'other' 784 'renewal_date' => $renewal_date, 785 'subscription_price' => 0, 786 'subscription_type' => 'monthly', 787 'notes' => '' 788 ), 789 array('%s','%s','%s','%s','%s','%f','%s','%s') 790 ); 791 if ( $result === false ) { 792 wp_send_json_error( 'Database insert failed.' ); 793 } 794 $insert_id = $wpdb->insert_id; 795 wp_send_json_success( array( 796 'id' => $insert_id, 797 'subscription_id' => $wpdb->insert_id, 798 'plugin_name' => $plugin_name, 799 'start_date' => $start_date, 800 'plugin_type' => 'other', // Returned value updated to match. 801 'subscription_type' => 'monthly', 802 'subscription_price'=> 0 803 ) ); 804 } 805 806 807 public function palmssm_add_free_trial() { 808 check_ajax_referer( 'palmssm_nonce', 'nonce' ); 809 if ( ! current_user_can( 'manage_options' ) ) { 810 wp_send_json_error('Unauthorized'); 811 } 812 global $wpdb; 813 $plugin_name = isset($_POST['plugin_name']) ? sanitize_text_field($_POST['plugin_name']) : ''; 814 $start_date = isset($_POST['start_date']) ? sanitize_text_field($_POST['start_date']) : ''; 815 $end_date = isset($_POST['end_date']) ? sanitize_text_field($_POST['end_date']) : ''; 816 if(empty($plugin_name)){ 817 wp_send_json_error('Plugin name is required.'); 818 } 819 $result = $wpdb->insert( 820 $this->table_name_trials, 821 array( 822 'trial_id' => wp_generate_uuid4(), 823 'name' => $plugin_name, 824 'start_date' => $start_date, 825 'plugin_type' => 'premium', 826 'end_date' => '', 827 'subscription_price' => 0, 828 'notes' => '' 829 ), 830 array('%s','%s','%s','%s','%s','%f','%s') 831 ); 832 if($result === false){ 833 wp_send_json_error('Database insert failed.'); 834 } 835 wp_send_json_success(); 836 } 837 707 838 private function palmssm_validate_date( $date, $format = 'Y-m-d' ) { 708 839 $d = DateTime::createFromFormat( $format, $date ); 709 840 return $d && $d->format( $format ) === $date; 710 841 } 842 843 /** 844 * Calculate the next logical renewal date. 845 * For 'monthly' subscriptions, iteratively add 1 month until the renewal date is after today. 846 * For 'annual' subscriptions, add 1 year until the date is after today. 847 * 848 * @param string $start_date The original start date in 'Y-m-d' format. 849 * @param string $subscription_type 'monthly' or 'annual' 850 * @return string The next renewal date in 'Y-m-d' format. 851 */ 852 /** 853 * Calculate the next logical renewal date. 854 * For 'monthly' subscriptions, the renewal_date is the start_date plus 1 month. 855 * For 'annual' subscriptions, it is the start_date plus 1 year. 856 * If that calculated date is in the past relative to today, then add 857 * additional intervals until the renewal date is in the future. 858 * 859 * @param string $start_date The original start date in 'Y-m-d' format. 860 * @param string $subscription_type 'monthly' or 'annual' 861 * @return string|null The next renewal date in 'Y-m-d' format or null on failure. 862 */ 863 private function calculate_next_renewal( $start_date, $subscription_type ) { 864 $start_timestamp = strtotime( $start_date ); 865 if ( ! $start_timestamp ) { 866 return null; 711 867 } 712 868 // Always add one interval to the start_date. 869 if ( $subscription_type === 'monthly' ) { 870 $renewal_timestamp = strtotime('+1 month', $start_timestamp); 871 } elseif ( $subscription_type === 'annual' ) { 872 $renewal_timestamp = strtotime('+1 year', $start_timestamp); 873 } else { 874 return $start_date; 875 } 876 $today = strtotime( gmdate('Y-m-d') ); 877 // If the computed renewal date is in the past or today, add intervals until it is in the future. 878 while ( $renewal_timestamp <= $today ) { 879 if ( $subscription_type === 'monthly' ) { 880 $renewal_timestamp = strtotime('+1 month', $renewal_timestamp); 881 } elseif ( $subscription_type === 'annual' ) { 882 $renewal_timestamp = strtotime('+1 year', $renewal_timestamp); 883 } 884 } 885 return gmdate( 'Y-m-d', $renewal_timestamp ); 886 } 887 888 public function palmssm_update_subscription() { 889 check_ajax_referer('palmssm_nonce', 'nonce'); 890 if ( ! current_user_can('manage_options') ) { 891 wp_send_json_error(__('Unauthorized', 'subscription-tracker')); 892 exit; 893 } 894 895 global $wpdb; 896 $subscription_id = isset($_POST['subscription_id']) ? sanitize_text_field($_POST['subscription_id']) : ''; 897 $source = isset($_POST['source']) ? sanitize_text_field($_POST['source']) : 'plugin'; 898 $plugin_type = isset($_POST['plugin_type']) ? sanitize_text_field($_POST['plugin_type']) : ''; 899 $subscription_type = isset($_POST['subscription_type']) ? sanitize_text_field($_POST['subscription_type']) : ''; 900 $start_date = isset($_POST['start_date']) ? sanitize_text_field($_POST['start_date']) : ''; 901 $subscription_price= isset($_POST['subscription_price']) ? floatval($_POST['subscription_price']) : 0.00; 902 903 if ( 'plugin' === $source ) { 904 $valid_plugin_types = array( 'free', 'premium', 'custom' ); 905 } elseif ( 'third_party' === $source ) { 906 $valid_plugin_types = array( 'domain', 'hosting', 'theme', 'service', 'other' ); 907 } else { 908 wp_send_json_error(__('Invalid source', 'subscription-tracker')); 909 exit; 910 } 911 912 913 $valid_subscription_types = array( 'monthly', 'annual', 'lifetime' ); 914 915 if ( ! in_array( $plugin_type, $valid_plugin_types, true ) ) { 916 wp_send_json_error( __( 'Invalid plugin type.', 'subscription-tracker' ) ); 917 exit; 918 } 919 if ( 'premium' === $plugin_type && ! in_array( $subscription_type, $valid_subscription_types, true ) ) { 920 wp_send_json_error( __( 'Invalid subscription type.', 'subscription-tracker' ) ); 921 exit; 922 } 923 924 // Prepare data to update. 925 $data = array( 926 'plugin_type' => $plugin_type, 927 'subscription_type' => $subscription_type, 928 'start_date' => $start_date, 929 'subscription_price' => $subscription_price 930 ); 931 $format = array('%s', '%s', '%s', '%f'); 932 933 if ( in_array( $subscription_type, array( 'monthly', 'annual' ) ) && ! empty( $start_date ) ) { 934 $renewal_date = $this->calculate_next_renewal($start_date, $subscription_type); 935 } else { 936 $renewal_date = null; 937 } 938 $data['renewal_date'] = $renewal_date; 939 940 if ( 'plugin' === $source ) { 941 $table = $this->table_name; 942 } elseif ( 'third_party' === $source ) { 943 $table = $this->table_name_3p; 944 } 945 946 $where = array('subscription_id' => $subscription_id); 947 $wformat = array('%s'); 948 949 $result = $wpdb->update($table, $data, $where, $format, $wformat); 950 951 if ($result === false) { 952 wp_send_json_error(__('Update failed', 'subscription-tracker')); 953 } else { 954 wp_send_json_success(); 955 } 956 } 957 } 713 958 new PalmsSM_Subscription_Tracker(); 714 959 } 715 960 ?> 961
Note: See TracChangeset
for help on using the changeset viewer.