Plugin Directory

Changeset 3256149


Ignore:
Timestamp:
03/15/2025 02:56:19 AM (13 months ago)
Author:
palmstrack
Message:

Version 1.3:

  • Removed license key storage (now provided as a separate plugin).
  • Added support for 3rd party subscriptions (tracking plugins, hosting, domain, and other services).
  • Minor UI improvements and bug fixes.
Location:
subscription-tracker/trunk
Files:
4 edited

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
    178:root {
    279    --sea-green: #41BA90;
     
    27104.psm-renewal-date{
    28105    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;
    29226}
    30227#psm-view-table table tr, #psm-view-table table td, #psm-view-table tbody{
     
    612809}
    613810
     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 
     1jQuery(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.
    10138    const $darkModeToggle = $('#psm-dark-mode');
    11139    const $iconSun = $('.icon-sun');
    12140    const $iconMoon = $('.icon-moon');
    13141
    14     // Initialize Dark Mode based on localStorage
    15142    if (localStorage.getItem('psmDarkMode') === 'enabled') {
    16143        $('body').addClass('psm-dark-mode');
    17144        $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)'});
    20147    }
    21 
    22     // Toggle Dark Mode
    23148    $darkModeToggle.on('change', function () {
    24149        if ($(this).is(':checked')) {
    25150            $('body').addClass('psm-dark-mode');
    26151            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)'});
    29154        } else {
    30155            $('body').removeClass('psm-dark-mode');
    31156            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.
    37163    const $highContrastToggle = $('#psm-high-contrast-toggle');
    38 
    39     // Initialize High Contrast based on localStorage
    40164    if (localStorage.getItem('psmHighContrast') === 'enabled') {
    41165        $('body').addClass('high-contrast');
     
    44168        $highContrastToggle.attr('aria-pressed', 'false').find('svg').css('fill', 'var(--white)');
    45169    }
    46 
    47     // Toggle High Contrast
    48170    $highContrastToggle.on('click', function () {
    49171        const isActive = $('body').toggleClass('high-contrast').hasClass('high-contrast');
    50 
    51172        $(this).attr('aria-pressed', isActive.toString());
    52173        $(this).find('svg').css('fill', isActive ? 'var(--black)' : 'var(--white)');
    53 
    54174        localStorage.setItem('psmHighContrast', isActive ? 'enabled' : 'disabled');
    55175    });
    56  
     176
     177    // Tab Switching for showing/hiding views.
    57178    const $tableView = $('#psm-table-view');
     179    const $trialView = $('#psm-trials-view');
     180    const $calendarView = $('#psm-calendar-view');
    58181    const $tabs = $('.psm-view-tab');
    59 
    60     // Tab Switching
     182   
    61183    $tabs.on('click', function () {
    62184        const target = $(this).data('target');
    63         // Toggle active tab
    64185        $tabs.removeClass('active');
    65186        $(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.
    67194        if (target === 'table') {
    68195            $tableView.show();
     196        } else if (target === 'trial') {
     197            $trialView.show();
    69198        } 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.
    79205    $('#psm-export').on('click', function () {
    80206        window.location.href =
    81207            palmssm.ajax_url +
    82             '?action=palmssm_export_plugin_data&format=csv' +
    83             '&nonce=' +
     208            '?action=palmssm_export_plugin_data&format=csv&nonce=' +
    84209            encodeURIComponent(palmssm.nonce);
    85210    });
    86211
    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.
    117213    $('.psm-paid-checkbox').on('change', function() {
    118214        var pluginSlug = $(this).data('plugin');
    119215        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.
    144231    function debounce(func, wait) {
    145232        var timeout;
    146233        return function () {
    147             var context = this,
    148                 args = arguments;
     234            var context = this, args = arguments;
    149235            clearTimeout(timeout);
    150236            timeout = setTimeout(function () {
     
    154240    }
    155241
    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.
    258243    $('#psm-sync').on('click', function (e) {
    259244        e.preventDefault();
    260 
    261245        if (confirm('Are you sure you want to sync plugins? This will add new plugins and remove deleted ones from the database.')) {
    262246            $.ajax({
     
    272256                success: function (response) {
    273257                    if (response.success) {
    274                         alert(response.data); 
    275                         location.reload(); 
     258                        alert(response.data);
     259                        location.reload();
    276260                    } else {
    277261                        alert('Error: ' + response.data);
     
    287271        }
    288272    });
    289 
    290273});
    291274
  • subscription-tracker/trunk/readme.txt

    r3252973 r3256149  
    1 === PalmsTrack ===
     1=== Palms Track - Subscription Tracker ===
    22Contributors: palmstrack
    3 Tags: subscription tracking, subscription management, renewal alerts, expense tracker
     3Tags: subscription tracking, subscription management, renewal alerts, expense tracker, expense manager
    44Requires at least: 5.9
    55Tested up to: 6.7.1
    6 Stable tag: 1.1
     6Stable tag: 1.2
    77Requires PHP: 7.2
    88License: GPLv2 or later
     
    1111
    1212== 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 convenience
     13PalmsTrack 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.
    1414
    1515== Installation ==
     
    3434== Features ==
    3535- **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.
    3839
    3940== 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.
    4242- **Scheduled Email Renewal Digest:** Stay ahead of your expenses with daily, weekly, or monthly email digests of your upcoming renewals.
    43 
    4443- **Advanced Organization Tools:** Tag and filter subscriptions for a clear breakdown of your spending.
    45 
    4644- **License Key Manager:** Store license keys and manage team access to them.
    47 
    4845- **Cost Tracking and Insights:** Analyze monthly and yearly expenses to optimize your budget, and generate downloadable PDF summary reports of your site expenses.
    49 
    5046- **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.
    5348
    5449== Frequently Asked Questions ==
     
    5752
    5853= How do I export my data? =
    59 Go to **My Subscriptions** and click **Download CSV**.
     54Go to **My Subscriptions** and click **Export**.
    6055
    6156== 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 =
    6360* Initial stable release featuring plugin subscription tracking, renewal alerts, and CSV export.
    6461
    6562== Upgrade Notice ==
    66 = 1.1 =
     63= 1.3 =
     64This update adds support for 3rd party subscriptions, expanding tracking capabilities beyond just plugins.
     65= 1.2 =
    6766This is the first stable release of PalmsTrack. No upgrade is necessary at this time.
    6867
  • subscription-tracker/trunk/subscription-tracker.php

    r3252973 r3256149  
    11<?php
    22/*
    3  * Plugin Name:       Subscription Tracker
     3 * Plugin Name:       PalmsTrack Subscription Tracker
    44 * Plugin URI:        https://palmstrack.com
    55 * Description:       Manage your WordPress plugin subscriptions, renewal dates, and license keys.
    6  * Version:           1.2
     6 * Version:           1.3
    77 * Requires at least: 5.2
    88 * Requires PHP:      7.2
     
    1515
    1616if ( ! defined( 'ABSPATH' ) ) {
    17     exit; 
     17    exit;
    1818}
    1919
    2020if ( ! class_exists( 'PalmsSM_Subscription_Tracker' ) ) {
    2121    class PalmsSM_Subscription_Tracker {
    22        
     22
    2323        private static $plugin_file;
    2424        private $plugin_dir;
    2525        private $plugin_url;
     26        // Use the new table name for main subscriptions.
    2627        private $table_name;
     28        private $table_name_3p;
     29        private $table_name_trials;
    2730
    2831        public function __construct() {
    2932            global $wpdb;
    3033            self::$plugin_file = __FILE__;
    31 
    3234            $this->plugin_dir = plugin_dir_path( self::$plugin_file );
    3335            $this->plugin_url = plugin_dir_url( self::$plugin_file );
    3436
     37            // Set table names – note these must match your schema.
    3538            $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';
    3641
    3742            register_activation_hook( self::$plugin_file, array( $this, 'activate_plugin' ) );
     
    4146            add_action( 'wp_ajax_palmssm_save_plugin_data', array( $this, 'palmssm_save_plugin_data' ) );
    4247            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 Widget
    4648            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' ) );
    5049            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' ) );
    5153
    5254            if ( ! function_exists( 'get_plugin_data' ) ) {
     
    5860
    5961        /**
    60          * Plugin Activation Hook
     62         * Plugin Activation Hook – create tables per new definitions.
    6163         */
    6264        public function activate_plugin() {
    6365            global $wpdb;
    64 
    6566            $charset_collate = $wpdb->get_charset_collate();
     67
     68            // Main subscriptions table.
    6669            $sql = "CREATE TABLE {$this->table_name} (
    6770                id INT AUTO_INCREMENT PRIMARY KEY,
     71                subscription_id CHAR(36) NOT NULL UNIQUE,
    6872                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',
    7175                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 0
     76                subscription_price DECIMAL(10,2) DEFAULT 0,
     77                subscription_type ENUM('monthly','annual') DEFAULT 'monthly',
     78                notes TEXT DEFAULT NULL
    7579            ) {$charset_collate};";
    76 
    7780            require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
    7881            dbDelta( $sql );
    7982
     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
    80108            $this->palmssm_populate_initial_data();
    81109        }
    82110
    83111        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         */
    87119        private function palmssm_populate_initial_data() {
    88120            global $wpdb;
    89121            $plugins = get_plugins();
    90 
    91122            foreach ( $plugins as $plugin_slug => $plugin_data ) {
    92123                $plugin_slug_sanitized = sanitize_text_field( $plugin_slug );
     
    96127                        $this->table_name,
    97128                        array(
     129                            'subscription_id'    => wp_generate_uuid4(),
    98130                            'plugin_slug'        => $plugin_slug_sanitized,
     131                            'start_date'         => null,
    99132                            'plugin_type'        => 'free',
    100                             'api_key'            => '',
    101133                            'renewal_date'       => null,
    102                             'subscription_price' => null,
     134                            'subscription_price' => 0,
    103135                            'subscription_type'  => 'monthly',
    104                             'paid'               => 0,
     136                            'notes'              => ''
    105137                        ),
    106                         array( '%s', '%s', '%s', '%s', '%f', '%s', '%d' )
     138                        array( '%s', '%s', '%s', '%s', '%s', '%f', '%s', '%s' )
    107139                    );
    108140                }
     
    114146            $filesystem_plugins = array_keys( get_plugins() );
    115147            $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}" ) );
    118150            $db_plugins = array_map( 'sanitize_text_field', $db_plugins );
    119 
    120151            $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 );
    123153            foreach ( $missing_plugins as $plugin_slug ) {
    124154                $wpdb->insert(
    125155                    $this->table_name,
    126156                    array(
     157                        'subscription_id'    => wp_generate_uuid4(),
    127158                        'plugin_slug'        => $plugin_slug,
    128                         'plugin_type'        => 'free', // Default to 'free';
    129                         'api_key'            => '',
     159                        'plugin_type'        => 'free',
    130160                        'renewal_date'       => null,
    131                         'subscription_price' => null,
     161                        'subscription_price' => 0,
    132162                        'subscription_type'  => 'monthly',
    133                         'paid'               => 0,
     163                        'notes'              => ''
    134164                    ),
    135                     array( '%s', '%s', '%s', '%s', '%f', '%s', '%d' )
     165                    array( '%s', '%s', '%s', '%s', '%f', '%s', '%s' )
    136166                );
    137167            }
    138 
    139168            foreach ( $stale_plugins as $plugin_slug ) {
    140169                $plugin_slug_sanitized = sanitize_text_field( $plugin_slug );
     
    149178        public function palmssm_sync_plugins() {
    150179            check_ajax_referer( 'palmssm_nonce', 'nonce' );
    151 
    152180            if ( ! current_user_can( 'manage_options' ) ) {
    153181                wp_send_json_error( __( 'Unauthorized', 'subscription-tracker' ) );
    154182                exit;
    155183            }
    156 
    157184            $this->palmssm_sync_plugins_to_database();
    158185            wp_send_json_success( __( 'Plugins synchronized successfully.', 'subscription-tracker' ) );
     
    163190                return;
    164191            }
    165 
    166192            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 );
    167193            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 
    169194            wp_enqueue_script( 'palmssm_psm-admin-js' );
    170             wp_enqueue_style( 'palmssm_psm-admin-css' );
    171 
     195            wp_enqueue_style( 'palmssm_psm-admin-css' );
    172196            wp_localize_script( 'palmssm_psm-admin-js', 'palmssm', array(
    173197                'ajax_url' => admin_url( 'admin-ajax.php' ),
    174198                'nonce'    => wp_create_nonce( 'palmssm_nonce' ),
    175199                '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' ),
    180204                ),
    181205            ));
    182206        }
    183207
    184  
    185208        public function palmssm_add_admin_menu() {
    186209            add_menu_page(
     
    195218        }
    196219       
     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
    197274        public function render_admin_page() {
    198275            global $wpdb;
    199             $plugins = get_plugins();
    200             $results = $wpdb->get_results( "SELECT * FROM {$this->table_name}", ARRAY_A );
    201276            $report  = $this->palmssm_generate_report();
     277            // Merge subscriptions from both tables.
     278            $subscriptions = $this->get_all_subscriptions();
    202279            ?>
    203280            <div class="wrap psm-container">
    204281                <div class="psm-accessibility-bar">
    205282                    <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.959
    217                                                  c78.828,0,142.96-64.131,142.96-142.959
    218                                                  C285.919,64.132,221.787,0,142.959,0z M142.959,260.919V142.96V25
    219                                                  c65.043,0,117.96,52.917,117.96,117.96
    220                                                  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>
    251283                </div>
    252 
    253284                <hr class="wp-header-end">
    254 
    255285                <div class="psm-report-bar">
    256286                    <div class="psm-report-item">
     
    287317                                    )
    288318                                );
    289 
    290319                                $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 ) );
    292321                            ?>
    293322                        </div>
     
    295324                    </div>
    296325                </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>
    300333           
    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">&#8942;</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                       
    377487                </div>
    378488                <div class="psm-actions-container" style="margin-top: 20px;">
     
    380490                    <button id="psm-export" class="button button-primary"><?php esc_html_e( 'Export', 'subscription-tracker' ); ?></button>
    381491                </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
    382539            </div>
    383540            <?php
     
    387544            $user_id = get_current_user_id();
    388545            global $wpdb;
    389             $today           = gmdate( 'Y-m-d' );
     546            $today = gmdate( 'Y-m-d' );
    390547            $alert_days = gmdate( 'Y-m-d', strtotime( '+7 days' ) );
    391548            $renewals = $wpdb->get_results(
    392549                $wpdb->prepare(
    393                     "SELECT plugin_slug, renewal_date FROM {$this->table_name}
     550                    "SELECT plugin_slug, start_date FROM {$this->table_name}
    394551                     WHERE renewal_date BETWEEN %s AND %s
    395                      AND plugin_type = %s
    396                      AND paid = %d",
     552                     AND plugin_type = %s",
    397553                    $today,
    398554                    $alert_days,
    399                     'premium',
    400                     0
     555                    'premium'
    401556                )
    402557            );
     
    404559            $alert_dismissed = get_user_meta( $user_id, '_palmssm_renewal_alert_dismissed', true );
    405560            if ( $alert_dismissed ) { return; }
    406 
    407561            echo '<div class="notice notice-warning is-dismissible psm-renewal-alert" style="padding:20px;">';
    408562            echo '<p style="font-size:18px; margin:0"><strong>' . esc_html__( 'Renewal Alert! Due in 7 Days', 'subscription-tracker' ) . '</strong></p>';
     
    412566                $plugin_name = $this->palmssm_get_plugin_name( $renewal->plugin_slug );
    413567                $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' );
    417569                echo '<tr>';
    418570                echo '<td>' . esc_html( $plugin_name_display ) . '</td>';
     
    421573            }
    422574            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' ) );
    431575        }
    432576
     
    437581        public function palmssm_generate_report() {
    438582            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(
    446590                $wpdb->prepare(
    447591                    "SELECT COUNT(*) FROM {$this->table_name} WHERE renewal_date BETWEEN %s AND %s AND plugin_type = %s",
     
    451595                )
    452596            ) );
    453 
    454597            return array(
    455598                'total_plugins'     => $total_plugins,
     
    471614        public function render_dashboard_widget() {
    472615            global $wpdb;
    473             $today      = gmdate( 'Y-m-d' );
     616            $today = gmdate( 'Y-m-d' );
    474617            $next_month = gmdate( 'Y-m-d', strtotime( '+30 days' ) );
    475             $renewals   = $wpdb->get_results(
     618            $renewals = $wpdb->get_results(
    476619                $wpdb->prepare(
    477620                    "SELECT plugin_slug, renewal_date, subscription_price
     
    486629                ARRAY_A
    487630            );
    488 
    489631            if ( ! empty( $renewals ) ) {
    490632                echo '<table class="widefat fixed">';
    491633                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 
    493634                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' );
    496637                    $subscription_price = ! empty( $renewal['subscription_price'] ) ? esc_html( number_format( floatval( $renewal['subscription_price'] ), 2 ) ) : esc_html__( '0.00', 'subscription-tracker' );
    497 
    498638                    echo '<tr>';
    499639                    echo '<td>' . esc_html( $plugin_name ) . '</td>';
     
    502642                    echo '</tr>';
    503643                }
    504 
    505644                echo '</tbody></table>';
    506645            } else {
     
    509648        }
    510649
     650        // Update plugin data – now without license key.
    511651        public function palmssm_save_plugin_data() {
    512652            check_ajax_referer( 'palmssm_nonce', 'nonce' );
    513 
    514653            if ( ! current_user_can( 'manage_options' ) ) {
    515654                wp_send_json_error( __( 'Unauthorized', 'subscription-tracker' ) );
    516655                exit;
    517656            }
    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'] ) : '';
    521659            $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');
    529664            if ( ! in_array( $plugin_type, $valid_plugin_types, true ) ) {
    530665                wp_send_json_error( __( 'Invalid plugin type.', 'subscription-tracker' ) );
    531666                exit;
    532667            }
    533 
    534668            if ( 'premium' === $plugin_type && ! in_array( $subscription_type, $valid_subscription_types, true ) ) {
    535669                wp_send_json_error( __( 'Invalid subscription type.', 'subscription-tracker' ) );
    536670                exit;
    537671            }
    538 
    539672            $data = array(
    540673                'plugin_type'        => $plugin_type,
    541674                'subscription_type'  => $subscription_type,
    542                 'api_key'            => $api_key,
    543675                'renewal_date'       => $renewal_date,
    544676                'subscription_price' => $subscription_price,
    545677            );
    546 
    547             $format = array(
    548                 '%s',
    549                 '%s',
    550                 '%s',
    551                 '%s',
    552                 '%f',
    553             );
    554 
     678            $format = array( '%s', '%s', '%s', '%f' );
    555679            global $wpdb;
    556680            $result = $wpdb->update(
     
    561685                array( '%s' )
    562686            );
    563 
    564687            if ( false === $result ) {
    565688                wp_send_json_error( __( 'Database update failed.', 'subscription-tracker' ) );
     
    569692        }
    570693
     694        // Export function uses only the main table.
    571695        public function palmssm_export_plugin_data() {
    572696            check_ajax_referer( 'palmssm_nonce', 'nonce' );
    573 
    574697            if ( ! current_user_can( 'manage_options' ) ) {
    575698                wp_die( esc_html__( 'Unauthorized access.', 'subscription-tracker' ) );
    576699            }
    577 
    578700            global $wpdb, $wp_filesystem;
    579 
    580701            if ( ! function_exists( 'WP_Filesystem' ) ) {
    581702                require_once ABSPATH . 'wp-admin/includes/file.php';
    582703            }
    583 
    584704            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';
    587706            $allowed_formats = array( 'csv' );
    588 
    589707            if ( ! in_array( strtolower( $format ), $allowed_formats, true ) ) {
    590708                wp_die( esc_html__( 'Invalid format specified.', 'subscription-tracker' ) );
    591709            }
    592 
    593710            $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$this->table_name} ORDER BY plugin_slug ASC" ), ARRAY_A );
    594 
    595711            if ( empty( $results ) ) {
    596712                wp_die( esc_html__( 'No data available for export.', 'subscription-tracker' ) );
    597713            }
    598 
    599714            $site_url = sanitize_file_name( wp_parse_url( get_site_url(), PHP_URL_HOST ) );
    600715            $filename = "palms_subscription_tracker_{$site_url}.csv";
    601716            $csv_content = '';
    602             $headers = array( 'Plugin', 'Type', 'License Key', 'Renewal Date', 'Cost', 'Recurrence', 'Paid' );
     717            $headers = array( 'Plugin', 'Type', 'Renewal Date', 'Cost', 'Recurrence' );
    603718            $csv_content .= implode( ',', array_map( array( $this, 'palmssm_esc_csv' ), $headers ) ) . "\n";
    604 
    605719            foreach ( $results as $row ) {
    606720                $plugin_name = $this->palmssm_get_plugin_name( $row['plugin_slug'] );
     
    608722                    $plugin_name,
    609723                    ucfirst( sanitize_text_field( $row['plugin_type'] ) ),
    610                     $row['api_key'],
    611724                    $row['renewal_date'],
    612725                    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'] ) )
    615727                );
    616728                $csv_content .= implode( ',', array_map( array( $this, 'palmssm_esc_csv' ), $data ) ) . "\n";
    617729            }
    618 
    619730            $tmp_file = wp_tempnam( $filename );
    620 
    621731            if ( ! $wp_filesystem->put_contents( $tmp_file, $csv_content, FS_CHMOD_FILE ) ) {
    622732                wp_die( esc_html__( 'Failed to write CSV file.', 'subscription-tracker' ) );
    623733            }
    624 
    625734            header( 'Content-Type: text/csv; charset=utf-8' );
    626735            header( 'Content-Disposition: attachment; filename=' . esc_attr( $filename ) );
     
    639748        }
    640749
     750        // Use plugin_slug to get the plugin name.
    641751        private function palmssm_get_plugin_name( $plugin_slug ) {
    642             $plugin_path = WP_PLUGIN_DIR . '/' . $plugin_slug; // Corrected path to use WP_PLUGIN_DIR
     752            $plugin_path = WP_PLUGIN_DIR . '/' . $plugin_slug;
    643753            if ( file_exists( $plugin_path ) ) {
    644754                $plugin_data = get_plugin_data( $plugin_path );
     
    649759        }
    650760
    651         public function palmssm_toggle_paid_status() {
     761        public function palmssm_add_subscription() {
    652762            check_ajax_referer( 'palmssm_nonce', 'nonce' );
    653 
    654763            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       
    707838        private function palmssm_validate_date( $date, $format = 'Y-m-d' ) {
    708839            $d = DateTime::createFromFormat( $format, $date );
    709840            return $d && $d->format( $format ) === $date;
    710841        }
     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 */
     863private function calculate_next_renewal( $start_date, $subscription_type ) {
     864    $start_timestamp = strtotime( $start_date );
     865    if ( ! $start_timestamp ) {
     866        return null;
    711867    }
    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    }
    713958    new PalmsSM_Subscription_Tracker();
    714959}
    715960?>
     961
Note: See TracChangeset for help on using the changeset viewer.