Changeset 3358149
- Timestamp:
- 09/08/2025 08:00:47 PM (6 months ago)
- Location:
- ait-easy-post-customization
- Files:
-
- 9 added
- 10 edited
- 1 copied
-
assets/Custom fields metabox on a post edit screen.png (added)
-
assets/Expiry date metabox on a post edit screen.png (added)
-
assets/banner-1544x500.png.png (added)
-
assets/logo.png (added)
-
assets/screenshot-1.png.png (added)
-
tags/1.0.2 (copied) (copied from ait-easy-post-customization/trunk)
-
tags/1.0.2/ait-easy-post-customization.php (modified) (6 diffs)
-
tags/1.0.2/assets/css/aitepc-admin.css (added)
-
tags/1.0.2/functions.php (modified) (1 diff)
-
tags/1.0.2/plugin-files/aitepc-custom-fields.php (added)
-
tags/1.0.2/plugin-files/aitepc-expiry-metabox.php (modified) (4 diffs)
-
tags/1.0.2/plugin-files/aitepc-settings.php (modified) (4 diffs)
-
tags/1.0.2/readme.txt (modified) (5 diffs)
-
trunk/ait-easy-post-customization.php (modified) (6 diffs)
-
trunk/assets/css/aitepc-admin.css (added)
-
trunk/functions.php (modified) (1 diff)
-
trunk/plugin-files/aitepc-custom-fields.php (added)
-
trunk/plugin-files/aitepc-expiry-metabox.php (modified) (4 diffs)
-
trunk/plugin-files/aitepc-settings.php (modified) (4 diffs)
-
trunk/readme.txt (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
ait-easy-post-customization/tags/1.0.2/ait-easy-post-customization.php
r3353149 r3358149 1 1 <?php 2 3 2 /** 4 3 * Plugin Name: AIT Easy Post Customization 5 * Description: This plugin is essential for the proper theme functionality .6 * Version: 1.0. 17 * Author: kamranchannar8 * License: GPL 24 * Description: This plugin is essential for the proper theme functionality, including expiry date and custom fields management. 5 * Version: 1.0.2 6 * Author: Muhammad Kamran 7 * License: GPL-2.0-or-later 9 8 * Text Domain: ait-easy-post-customization 9 * Requires at least: 5.0 10 * Requires PHP: 7.4 10 11 */ 11 12 … … 17 18 register_activation_hook(__FILE__, 'aitepc_expiry_date_plugin_activate'); 18 19 function aitepc_expiry_date_plugin_activate() { 19 if (!get_option(' expiry_date_post_types')) {20 update_option(' expiry_date_post_types', array('post'));20 if (!get_option('aitepc_expiry_date_post_types')) { 21 update_option('aitepc_expiry_date_post_types', array('post')); 21 22 } 22 23 … … 41 42 function aitepc_check_expired_posts() { 42 43 $post_types = get_option('aitepc_expiry_date_post_types', array('post')); 43 $tomorrow = gmdate('Ymd', strtotime('+1 day')); // Changed to gmdate()44 $tomorrow = gmdate('Ymd', strtotime('+1 day')); 44 45 45 46 $args = array( … … 48 49 'meta_query' => array( 49 50 array( 50 'key' => '_aitepc_expiry_date', // Ensure consistency with your meta key51 'key' => '_aitepc_expiry_date', 51 52 'value' => $tomorrow, 52 53 'compare' => '<', … … 70 71 } 71 72 wp_reset_postdata(); 72 } 73 } 74 } 75 76 // Load translation 77 add_action('plugins_loaded', 'aitepc_load_textdomain'); 78 function aitepc_load_textdomain() { 79 load_plugin_textdomain('ait-easy-post-customization', false, dirname(plugin_basename(__FILE__)) . '/languages'); 80 } 81 82 // Register settings for Settings API 83 add_action('admin_init', 'aitepc_register_settings'); 84 function aitepc_register_settings() { 85 register_setting('aitepc_expiry_group', 'aitepc_expiry_date_post_types', array( 86 'sanitize_callback' => 'aitepc_sanitize_post_types', 87 'default' => array('post') 88 )); 89 90 add_settings_section( 91 'aitepc_post_types_section', 92 __('Select Post Types for Expiry Date Metabox', 'ait-easy-post-customization'), 93 null, 94 'aitepc-expiry-date-settings' 95 ); 96 97 $all_post_types = get_post_types(array('public' => true), 'objects'); 98 $filtered_post_types = array_filter($all_post_types, function($post_type) { 99 return !in_array($post_type->name, array('page', 'attachment')); 100 }); 101 102 foreach ($filtered_post_types as $post_type) { 103 add_settings_field( 104 'aitepc_pt_' . $post_type->name, 105 $post_type->label, 106 'aitepc_pt_checkbox_cb', 107 'aitepc-expiry-date-settings', 108 'aitepc_post_types_section', 109 array('pt' => $post_type->name) 110 ); 111 } 112 } 113 114 function aitepc_sanitize_post_types($input) { 115 return is_array($input) ? array_map('sanitize_text_field', $input) : array(); 116 } 117 118 function aitepc_pt_checkbox_cb($args) { 119 $options = get_option('aitepc_expiry_date_post_types', array()); 120 $checked = in_array($args['pt'], $options) ? 'checked' : ''; 121 printf( 122 '<input type="checkbox" name="aitepc_expiry_date_post_types[]" value="%s" %s class="form-check-input" />', 123 esc_attr($args['pt']), 124 $checked 125 ); 73 126 } 74 127 … … 77 130 require_once plugin_dir_path(__FILE__) . 'plugin-files/aitepc-settings.php'; 78 131 require_once plugin_dir_path(__FILE__) . 'plugin-files/aitepc-expiry-metabox.php'; 132 require_once plugin_dir_path(__FILE__) . 'plugin-files/aitepc-custom-fields.php'; -
ait-easy-post-customization/tags/1.0.2/functions.php
r3353149 r3358149 5 5 // Enqueue local Bootstrap CSS and JS 6 6 wp_enqueue_style('ait-bootstrap-css', plugins_url('assets/css/bootstrap.min.css', __FILE__), array(), '5.3.3'); 7 wp_enqueue_style('aitepc-admin-css', plugins_url('assets/css/aitepc-admin.css', __FILE__), array(), '1.0.0'); 7 8 wp_enqueue_script('ait-bootstrap-js', plugins_url('assets/js/bootstrap.bundle.min.js', __FILE__), array('jquery'), '5.3.3', true); 9 10 wp_enqueue_style('dashicons'); 8 11 } 9 12 } -
ait-easy-post-customization/tags/1.0.2/plugin-files/aitepc-expiry-metabox.php
r3353149 r3358149 1 1 <?php 2 // Prevent direct access to the file 2 /** 3 * AIT Easy Post Customization - Expiry Metabox 4 * 5 * @package AIT Easy Post Customization 6 */ 7 3 8 if (!defined('ABSPATH')) { 4 9 exit; … … 7 12 // Add Expiry Date Metabox to selected post types 8 13 function aitepc_add_expiry_date_metabox_to_selected_post_types() { 9 // Get selected post types from options10 14 $selected_post_types = get_option('aitepc_expiry_date_post_types', array()); 11 15 12 // Register the metabox for each selected post type13 16 foreach ($selected_post_types as $post_type) { 14 17 add_meta_box( 15 'aitepc_expiry_date_metabox', 16 'Expiry Date',17 'aitepc_expiry_date_metabox_callback', 18 $post_type, 19 'side', 20 'default' 18 'aitepc_expiry_date_metabox', 19 __('Expiry Date', 'ait-easy-post-customization'), 20 'aitepc_expiry_date_metabox_callback', 21 $post_type, 22 'side', 23 'default' 21 24 ); 22 25 } … … 28 31 wp_nonce_field('aitepc_save_expiry_date', 'aitepc_expiry_date_nonce'); 29 32 $expiry_date = get_post_meta($post->ID, '_aitepc_expiry_date', true); 30 echo '<label for="aitepc_expiry_date">Expiry Date: </label>'; 31 echo '<input type="date" id="aitepc_expiry_date" name="aitepc_expiry_date" value="' . esc_attr($expiry_date) . '" />'; 33 ?> 34 <div class="mb-3"> 35 <label for="aitepc_expiry_date" class="form-label"><?php _e('Expiry Date:', 'ait-easy-post-customization'); ?></label> 36 <input type="date" id="aitepc_expiry_date" name="aitepc_expiry_date" value="<?php echo esc_attr($expiry_date); ?>" class="form-control" /> 37 </div> 38 <?php 32 39 } 33 40 … … 49 56 $expiry_date = sanitize_text_field(wp_unslash($_POST['aitepc_expiry_date'])); 50 57 update_post_meta($post_id, '_aitepc_expiry_date', $expiry_date); 58 } else { 59 delete_post_meta($post_id, '_aitepc_expiry_date'); 51 60 } 52 61 } -
ait-easy-post-customization/tags/1.0.2/plugin-files/aitepc-settings.php
r3353149 r3358149 1 1 <?php 2 // Prevent direct access to the file 2 /** 3 * AIT Easy Post Customization - Settings Page 4 * 5 * @package AIT Easy Post Customization 6 */ 7 3 8 if (!defined('ABSPATH')) { 4 9 exit; … … 8 13 function aitepc_expiry_date_menu_page() { 9 14 add_menu_page( 10 'Expiry Date Settings',11 'Expiry Date',15 __('Easy Post Customization Settings', 'ait-easy-post-customization'), 16 __('Easy Post Customization', 'ait-easy-post-customization'), 12 17 'manage_options', 13 18 'aitepc-expiry-date-settings', … … 21 26 // Render the settings page HTML 22 27 function aitepc_expiry_date_settings_page_html() { 23 // Check user capabilities24 28 if (!current_user_can('manage_options')) { 25 return; 26 } 27 28 // Save options when the form is submitted 29 if (isset($_POST['aitepc_expiry_date_post_types'])) { 30 // Verify the nonce 31 if (!isset($_POST['aitepc_expiry_date_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['aitepc_expiry_date_nonce'])), 'aitepc_expiry_date_nonce_action')) { 32 echo '<div class="alert alert-danger" role="alert">Security check failed. Please try again.</div>'; 33 return; 34 } 35 36 // Sanitize and validate input 37 $post_types = isset($_POST['aitepc_expiry_date_post_types']) ? array_map('sanitize_text_field', wp_unslash($_POST['aitepc_expiry_date_post_types'])) : []; 38 39 // Update the option with the validated post types 40 update_option('aitepc_expiry_date_post_types', $post_types); 41 echo '<div class="alert alert-success" role="alert">Settings saved successfully!</div>'; 42 } 43 44 // Retrieve current settings 45 $selected_post_types = get_option('aitepc_expiry_date_post_types', array()); 46 47 // Get all built-in public post types 29 wp_die(__('You do not have sufficient permissions to access this page.', 'ait-easy-post-customization')); 30 } 31 32 // Handle custom fields actions 33 aitepc_handle_custom_fields_submission(); 34 35 // Retrieve filtered post types for custom fields form 48 36 $all_post_types = get_post_types(array('public' => true), 'objects'); 49 50 // Exclude 'page' and 'attachment'51 37 $filtered_post_types = array_filter($all_post_types, function($post_type) { 52 38 return !in_array($post_type->name, array('page', 'attachment')); 53 39 }); 54 40 55 // Display the settings form with tabs56 41 ?> 57 <div class="container mt-4"> 58 <h2 class="mb-4">Expiry Date Metabox Settings</h2> 59 <!-- Nav tabs --> 60 <ul class="nav nav-tabs" id="myTab" role="tablist"> 42 <div class="wrap aitepc-wrap"> 43 <h1 class="aitepc-title"><span class="dashicons dashicons-admin-settings me-2"></span><?php _e('Easy Post Customization', 'ait-easy-post-customization'); ?></h1> 44 <ul class="nav nav-tabs aitepc-tabs" id="myTab" role="tablist"> 61 45 <li class="nav-item" role="presentation"> 62 <a class="nav-link active" id="post-types-tab" data-bs-toggle="tab" href="#post-types" role="tab" aria-controls="post-types" aria-selected="true">Assign Metabox</a> 46 <button class="nav-link active" id="post-types-tab" data-bs-toggle="tab" data-bs-target="#post-types" type="button" role="tab" aria-controls="post-types" aria-selected="true"> 47 <span class="dashicons dashicons-post-status me-1"></span><?php _e('Assign Metabox', 'ait-easy-post-customization'); ?> 48 </button> 63 49 </li> 64 50 <li class="nav-item" role="presentation"> 65 <a class="nav-link" id="coming-soon-tab" data-bs-toggle="tab" href="#coming-soon" role="tab" aria-controls="coming-soon" aria-selected="false">Coming Soon</a> 51 <button class="nav-link" id="custom-fields-tab" data-bs-toggle="tab" data-bs-target="#custom-fields" type="button" role="tab" aria-controls="custom-fields" aria-selected="false"> 52 <span class="dashicons dashicons-edit me-1"></span><?php _e('Custom Fields', 'ait-easy-post-customization'); ?> 53 </button> 54 </li> 55 <li class="nav-item" role="presentation"> 56 <button class="nav-link" id="coming-soon-tab" data-bs-toggle="tab" data-bs-target="#coming-soon" type="button" role="tab" aria-controls="coming-soon" aria-selected="false"> 57 <span class="dashicons dashicons-clock me-1"></span><?php _e('Coming Soon', 'ait-easy-post-customization'); ?> 58 </button> 66 59 </li> 67 60 </ul> 68 61 69 <!-- Tab content --> 70 <div class="tab-content mt-3" id="myTabContent"> 71 <!-- First tab for assigning post types --> 62 <div class="tab-content mt-4" id="myTabContent"> 72 63 <div class="tab-pane fade show active" id="post-types" role="tabpanel" aria-labelledby="post-types-tab"> 73 <form method="post" class="border p-4 rounded shadow-sm bg-light"> 74 <h4 class="mb-3">Select Post Types for Expiry Date Metabox</h4> 75 76 <!-- Nonce field for security --> 77 <?php wp_nonce_field('aitepc_expiry_date_nonce_action', 'aitepc_expiry_date_nonce'); ?> 78 64 <div class="card aitepc-card"> 65 <div class="card-body"> 66 <h2 class="card-title"><?php _e('Assign Expiry Date Metabox', 'ait-easy-post-customization'); ?></h2> 67 <p class="card-text text-muted"><?php _e('Select the post types where the expiry date metabox should appear.', 'ait-easy-post-customization'); ?></p> 68 <form method="post" action="options.php"> 69 <?php 70 settings_fields('aitepc_expiry_group'); 71 do_settings_sections('aitepc-expiry-date-settings'); 72 submit_button(__('Save Changes', 'ait-easy-post-customization'), 'primary aitepc-btn', 'submit', true); 73 ?> 74 </form> 75 </div> 76 </div> 77 </div> 78 79 <div class="tab-pane fade" id="custom-fields" role="tabpanel" aria-labelledby="custom-fields-tab"> 80 <?php aitepc_display_custom_fields_form($filtered_post_types); ?> 81 </div> 82 83 <div class="tab-pane fade" id="coming-soon" role="tabpanel" aria-labelledby="coming-soon-tab"> 84 <div class="card aitepc-card text-center"> 85 <div class="card-body"> 86 <h2 class="card-title"><?php _e('Coming Soon', 'ait-easy-post-customization'); ?></h2> 87 <p class="card-text"><?php _e('Exciting new features are under development. Stay tuned for updates!', 'ait-easy-post-customization'); ?></p> 88 <p><?php _e('Have ideas or feedback? We’d love to hear from you.', 'ait-easy-post-customization'); ?></p> 89 <p><?php _e('Contact:', 'ait-easy-post-customization'); ?> <a href="mailto:[email protected]" class="text-decoration-none">[email protected]</a></p> 90 <div class="alert alert-info"><?php _e('Thank you for your patience and support!', 'ait-easy-post-customization'); ?></div> 91 </div> 92 </div> 93 </div> 94 </div> 95 </div> 96 <?php 97 } 98 99 // Function to handle custom fields form submissions 100 function aitepc_handle_custom_fields_submission() { 101 if (!isset($_POST['aitepc_custom_fields_action'])) { 102 return; 103 } 104 105 if (!isset($_POST['aitepc_custom_fields_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['aitepc_custom_fields_nonce'])), 'aitepc_custom_fields_action')) { 106 echo '<div class="alert alert-danger" role="alert">' . __('Security check failed. Please try again.', 'ait-easy-post-customization') . '</div>'; 107 return; 108 } 109 110 $custom_fields = get_option('aitepc_custom_fields', array()); 111 $action = sanitize_text_field(wp_unslash($_POST['aitepc_custom_fields_action'])); 112 113 if ($action === 'add' || $action === 'edit') { 114 $key = sanitize_key(wp_unslash($_POST['field_key'])); 115 $label = sanitize_text_field(wp_unslash($_POST['field_label'])); 116 $type = sanitize_text_field(wp_unslash($_POST['field_type'])); 117 $post_types = isset($_POST['field_post_types']) ? array_map('sanitize_text_field', wp_unslash($_POST['field_post_types'])) : []; 118 119 if (empty($key) || empty($label) || empty($type)) { 120 echo '<div class="alert alert-danger" role="alert">' . __('All fields are required.', 'ait-easy-post-customization') . '</div>'; 121 return; 122 } 123 124 if ($action === 'add' && array_key_exists($key, $custom_fields)) { 125 echo '<div class="alert alert-danger" role="alert">' . __('Field key already exists.', 'ait-easy-post-customization') . '</div>'; 126 return; 127 } 128 129 $custom_fields[$key] = array( 130 'label' => $label, 131 'type' => $type, 132 'post_types' => $post_types 133 ); 134 135 update_option('aitepc_custom_fields', $custom_fields); 136 echo '<div class="alert alert-success" role="alert">' . __('Custom field ', 'ait-easy-post-customization') . ($action === 'add' ? __('added', 'ait-easy-post-customization') : __('updated', 'ait-easy-post-customization')) . __(' successfully!', 'ait-easy-post-customization') . '</div>'; 137 } elseif ($action === 'delete') { 138 $key = sanitize_key(wp_unslash($_POST['field_key'])); 139 if (array_key_exists($key, $custom_fields)) { 140 unset($custom_fields[$key]); 141 update_option('aitepc_custom_fields', $custom_fields); 142 echo '<div class="alert alert-success" role="alert">' . __('Custom field deleted successfully!', 'ait-easy-post-customization') . '</div>'; 143 } 144 } 145 } 146 147 // Function to display custom fields management 148 function aitepc_display_custom_fields_form($filtered_post_types) { 149 $custom_fields = get_option('aitepc_custom_fields', array()); 150 $edit_key = isset($_GET['edit']) ? sanitize_key($_GET['edit']) : ''; 151 $edit_field = $edit_key && isset($custom_fields[$edit_key]) ? $custom_fields[$edit_key] : null; 152 ?> 153 <div class="card aitepc-card"> 154 <div class="card-body"> 155 <h2 class="card-title mb-4"><?php echo $edit_field ? __('Edit Custom Field', 'ait-easy-post-customization') : __('Add New Custom Field', 'ait-easy-post-customization'); ?></h2> 156 <form method="post" class="needs-validation" novalidate> 157 <?php wp_nonce_field('aitepc_custom_fields_action', 'aitepc_custom_fields_nonce'); ?> 158 <input type="hidden" name="aitepc_custom_fields_action" value="<?php echo $edit_field ? 'edit' : 'add'; ?>"> 159 <?php if ($edit_field) : ?> 160 <input type="hidden" name="field_key" value="<?php echo esc_attr($edit_key); ?>"> 161 <?php endif; ?> 162 <div class="mb-3"> 163 <label for="field_label" class="form-label"><?php _e('Field Label', 'ait-easy-post-customization'); ?> <span class="text-danger">*</span></label> 164 <input type="text" class="form-control" id="field_label" name="field_label" value="<?php echo $edit_field ? esc_attr($edit_field['label']) : ''; ?>" required> 165 <div class="invalid-feedback"><?php _e('Please enter a field label.', 'ait-easy-post-customization'); ?></div> 166 </div> 167 <div class="mb-3"> 168 <label for="field_key" class="form-label"><?php _e('Field Key (slug)', 'ait-easy-post-customization'); ?> <span class="text-danger">*</span></label> 169 <input type="text" class="form-control" id="field_key" name="field_key" value="<?php echo $edit_field ? esc_attr($edit_key) : ''; ?>" <?php echo $edit_field ? 'readonly' : 'required'; ?> pattern="[a-z0-9_]+"> 170 <small class="form-text text-muted"><?php _e('Use lowercase letters, numbers, and underscores only. Cannot be changed after creation.', 'ait-easy-post-customization'); ?></small> 171 <div class="invalid-feedback"><?php _e('Please enter a valid field key (lowercase letters, numbers, underscores only).', 'ait-easy-post-customization'); ?></div> 172 </div> 173 <div class="mb-3"> 174 <label for="field_type" class="form-label"><?php _e('Field Type', 'ait-easy-post-customization'); ?> <span class="text-danger">*</span></label> 175 <select class="form-select" id="field_type" name="field_type" required> 176 <option value="text" <?php selected($edit_field ? $edit_field['type'] : '', 'text'); ?>><?php _e('Text', 'ait-easy-post-customization'); ?></option> 177 <option value="textarea" <?php selected($edit_field ? $edit_field['type'] : '', 'textarea'); ?>><?php _e('Textarea', 'ait-easy-post-customization'); ?></option> 178 <option value="date" <?php selected($edit_field ? $edit_field['type'] : '', 'date'); ?>><?php _e('Date', 'ait-easy-post-customization'); ?></option> 179 </select> 180 <div class="invalid-feedback"><?php _e('Please select a field type.', 'ait-easy-post-customization'); ?></div> 181 </div> 182 <div class="mb-4"> 183 <h5 class="mb-3"><?php _e('Select Post Types', 'ait-easy-post-customization'); ?></h5> 79 184 <div class="row"> 80 185 <?php foreach ($filtered_post_types as $post_type) : 81 $checked = in_array($post_type->name, $selected_post_types) ? 'checked="checked"' : ''; ?>186 $checked = $edit_field && in_array($post_type->name, $edit_field['post_types']) ? 'checked="checked"' : ''; ?> 82 187 <div class="col-md-4 mb-3 d-flex align-items-center"> 83 <input type="checkbox" class="form-check-input me-2" name=" aitepc_expiry_date_post_types[]" value="<?php echo esc_attr($post_type->name); ?>" <?php echo esc_attr($checked); ?> id="aitepc_<?php echo esc_attr($post_type->name); ?>">84 <label class="form-check-label" for=" aitepc_<?php echo esc_attr($post_type->name); ?>">188 <input type="checkbox" class="form-check-input me-2" name="field_post_types[]" value="<?php echo esc_attr($post_type->name); ?>" <?php echo esc_attr($checked); ?> id="field_<?php echo esc_attr($post_type->name); ?>"> 189 <label class="form-check-label" for="field_<?php echo esc_attr($post_type->name); ?>"> 85 190 <?php echo esc_html($post_type->label); ?> 86 191 </label> … … 88 193 <?php endforeach; ?> 89 194 </div> 90 <button type="submit" class="btn btn-primary mt-3">Save Changes</button> 91 </form> 92 </div> 93 94 <!-- Second tab for coming soon --> 95 <div class="tab-pane fade" id="coming-soon" role="tabpanel" aria-labelledby="coming-soon-tab"> 96 <div class="text-center"> 97 <h4>Coming Soon</h4> 98 <p>This feature is currently under development. Stay tuned for updates!</p> 99 <p>We’d love to hear your feedback! Let us know what features you’d like to see in the upcoming release.</p> 100 <p>Contact: <a href="mailto:[email protected]">[email protected]</a></p> 101 <div class="alert alert-info" role="alert"> 102 We appreciate your patience and support! 103 </div> 104 </div> 105 </div> 195 </div> 196 <div class="d-flex gap-2"> 197 <button type="submit" class="btn btn-primary aitepc-btn"><?php echo $edit_field ? __('Update Field', 'ait-easy-post-customization') : __('Add Field', 'ait-easy-post-customization'); ?></button> 198 <?php if ($edit_field) : ?> 199 <a href="<?php echo esc_url(admin_url('admin.php?page=aitepc-expiry-date-settings')); ?>" class="btn btn-outline-secondary"><?php _e('Cancel', 'ait-easy-post-customization'); ?></a> 200 <?php endif; ?> 201 </div> 202 </form> 106 203 </div> 107 204 </div> 205 206 <div class="card aitepc-card mt-4"> 207 <div class="card-body"> 208 <h2 class="card-title mb-4"><?php _e('Existing Custom Fields', 'ait-easy-post-customization'); ?></h2> 209 <?php if (!empty($custom_fields)) : ?> 210 <div class="table-responsive"> 211 <table class="table table-hover aitepc-table"> 212 <thead> 213 <tr> 214 <th><?php _e('Label', 'ait-easy-post-customization'); ?></th> 215 <th><?php _e('Key', 'ait-easy-post-customization'); ?></th> 216 <th><?php _e('Type', 'ait-easy-post-customization'); ?></th> 217 <th><?php _e('Post Types', 'ait-easy-post-customization'); ?></th> 218 <th><?php _e('Actions', 'ait-easy-post-customization'); ?></th> 219 </tr> 220 </thead> 221 <tbody> 222 <?php foreach ($custom_fields as $key => $field) : ?> 223 <tr> 224 <td><?php echo esc_html($field['label']); ?></td> 225 <td><?php echo esc_html($key); ?></td> 226 <td><?php echo esc_html($field['type']); ?></td> 227 <td><?php echo esc_html(implode(', ', $field['post_types'])); ?></td> 228 <td> 229 <a href="<?php echo esc_url(add_query_arg('edit', $key, admin_url('admin.php?page=aitepc-expiry-date-settings'))); ?>" class="btn btn-sm btn-primary aitepc-btn me-1"><?php _e('Edit', 'ait-easy-post-customization'); ?></a> 230 <form method="post" style="display:inline;"> 231 <?php wp_nonce_field('aitepc_custom_fields_action', 'aitepc_custom_fields_nonce'); ?> 232 <input type="hidden" name="aitepc_custom_fields_action" value="delete"> 233 <input type="hidden" name="field_key" value="<?php echo esc_attr($key); ?>"> 234 <button type="submit" class="btn btn-sm btn-danger aitepc-btn" onclick="return confirm('<?php esc_attr_e('Are you sure you want to delete this field?', 'ait-easy-post-customization'); ?>');"><?php _e('Delete', 'ait-easy-post-customization'); ?></button> 235 </form> 236 </td> 237 </tr> 238 <?php endforeach; ?> 239 </tbody> 240 </table> 241 </div> 242 <?php else : ?> 243 <p class="text-muted"><?php _e('No custom fields added yet.', 'ait-easy-post-customization'); ?></p> 244 <?php endif; ?> 245 </div> 246 </div> 247 <script> 248 // Bootstrap form validation 249 (function () { 250 'use strict'; 251 const forms = document.querySelectorAll('.needs-validation'); 252 Array.from(forms).forEach(form => { 253 form.addEventListener('submit', event => { 254 if (!form.checkValidity()) { 255 event.preventDefault(); 256 event.stopPropagation(); 257 } 258 form.classList.add('was-validated'); 259 }, false); 260 }); 261 })(); 262 </script> 108 263 <?php 109 264 } 110 ?> -
ait-easy-post-customization/tags/1.0.2/readme.txt
r3353149 r3358149 1 1 === AIT Easy Post Customization === 2 2 Contributors: Muhammad Kamran 3 Tags: post expiration, post expiry, expiry date, schedule post, auto expire4 Requires at least: 5.0 5 Tested up to: 6. 66 Requires PHP: 7. 27 Stable tag: 1.0. 18 License: GPLv2 or later 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html 3 Tags: post expiration, post expiry, expiry date, schedule post, auto expire, custom fields 4 Requires at least: 5.0 5 Tested up to: 6.8 6 Requires PHP: 7.4 7 Stable tag: 1.0.2 8 License: GPLv2 or later 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html 10 10 11 11 Easily set expiry dates for posts and custom post types, automatically unpublishing content when it becomes outdated. 12 12 13 == AIT Easy Post Customization ==13 == Description == 14 14 15 15 AIT Easy Post Customization is a powerful yet easy-to-use plugin designed to give users control over the lifespan of their posts and custom post types by allowing you to set an expiry date. If you're managing time-sensitive content, like event announcements, special promotions, or limited-time offers, this plugin automates the process of unpublishing or archiving content, making content management hassle-free. 16 What problem does this plugin solve? 16 17 **What problem does this plugin solve?** 17 18 18 19 Manually tracking and updating expired content can be tedious and prone to errors, especially if you have a large volume of posts. Without this plugin, expired content can remain live, leading to outdated or irrelevant information on your website. AIT Easy Post Customization solves this problem by allowing you to set expiry dates on posts, ensuring that content is automatically hidden or removed after the specified date. This keeps your site fresh and up-to-date without manual intervention. 19 How does it improve the user experience?20 20 21 1. Automatic Expiry Handling: Set an expiration date for any post or custom post type, and let the plugin automatically unpublish it when the time comes. This eliminates the need for manual updates and improves content accuracy. 22 2. Custom Post Type Support: Whether you're using standard WordPress posts or custom post types (like events, job listings, products, etc.), you can easily add expiry functionality to all of them. 23 3. User-Friendly Interface: The plugin integrates seamlessly with the WordPress editor, making it easy for users of all skill levels to manage expiry dates. The expiry date can be set directly from the post editing screen. 21 **How does it improve the user experience?** 24 22 25 Key Features: 23 1. **Automatic Expiry Handling**: Set an expiration date for any post or custom post type, and let the plugin automatically unpublish it when the time comes. This eliminates the need for manual updates and improves content accuracy. 24 2. **Custom Post Type Support**: Whether you're using standard WordPress posts or custom post types (like events, job listings, products, etc.), you can easily add expiry functionality to all of them. 25 3. **User-Friendly Interface**: The plugin integrates seamlessly with the WordPress editor, making it easy for users of all skill levels to manage expiry dates. The expiry date can be set directly from the post editing screen. 26 4. **Custom Fields Management**: Add custom fields (text, textarea, date) to posts for enhanced content customization. 27 5. **Modern Admin Interface**: A sleek, responsive admin panel with sticky tabs, form validation, and intuitive navigation. 26 28 27 1. Post Expiration: Set expiration dates for posts and custom post types, and automatically unpublish them when the time is up. 28 2. Customizable Actions: Choose what happens to the post after it expires (e.g., move to draft, delete, or do nothing). 29 3. Supports All Post Types: Works with standard WordPress posts and custom post types, allowing flexible use across various content types. 30 4. Seamless Integration: Integrated into the post editor, making it easy to manage for both beginners and advanced users. 31 5. Scheduled Content Management: Perfect for managing content like event updates, limited-time offers, and seasonal announcements. 29 **Key Features**: 32 30 33 Notable Features: 31 1. **Post Expiration**: Set expiration dates for posts and custom post types, automatically moving them to draft status when expired. 32 2. **Custom Fields**: Add and manage custom fields (text, textarea, date) for selected post types. 33 3. **Supports All Post Types**: Works with standard WordPress posts and custom post types, allowing flexible use across various content types. 34 4. **Seamless Integration**: Integrated into the post editor with a modern, user-friendly interface. 35 5. **Scheduled Content Management**: Perfect for managing content like event updates, limited-time offers, and seasonal announcements. 36 6. **Localization Ready**: Fully translation-ready for multilingual websites. 34 37 35 1. Custom Widgets: Optionally display a widget that shows upcoming or recently expired content to site admins for better tracking. 36 2. Developer-Friendly Hooks and Filters: Developers can further customize the plugin’s behavior using built-in hooks and filters. 37 3. Localization Ready: Fully translation-ready, making it suitable for websites in any language. 38 **Notable Features**: 39 40 1. **Custom Widgets**: Optionally display a widget that shows upcoming or recently expired content to site admins for better tracking (coming soon). 41 2. **Developer-Friendly Hooks and Filters**: Developers can customize the plugin’s behavior using built-in hooks and filters. 42 3. **Modern Design**: Features a responsive admin interface with sticky tabs, Dashicons, and gradient effects for a premium experience. 38 43 39 44 With AIT Easy Post Customization, you’ll never have to worry about outdated or irrelevant content cluttering your site again. Keep your content fresh, current, and timely with minimal effort. … … 43 48 Follow these steps to install and activate AIT Easy Post Customization: 44 49 45 Upload Method:46 1. Upload the plugin files to the /wp-content/plugins/ait-easy-post-customization/directory.50 **Upload Method**: 51 1. Upload the plugin files to the `/wp-content/plugins/ait-easy-post-customization/` directory. 47 52 48 OR 53 **OR** 49 54 50 Direct Install via WordPress:51 1. Go to the WordPress dashboard, navigate to Plugins -> Add New, and search for AIT Easy Post Customization. Click Install Now.55 **Direct Install via WordPress**: 56 1. Go to the WordPress dashboard, navigate to Plugins -> Add New, and search for AIT Easy Post Customization. Click Install Now. 52 57 53 Activate the Plugin:54 1. Once installed, go to the Plugins screen in WordPress and click Activate next to AIT Easy Post Customization.58 **Activate the Plugin**: 59 1. Once installed, go to the Plugins screen in WordPress and click Activate next to AIT Easy Post Customization. 55 60 56 Configure the Plugin:57 1. After activation, a new menu item called AIT Expiry Date will appear in the WordPress sidebar. Click on it to access the plugin settings and configure the expiry dateoptions as needed.61 **Configure the Plugin**: 62 1. After activation, a new menu item called **Easy Post Customization** will appear in the WordPress sidebar. Click on it to access the plugin settings and configure the expiry date and custom field options as needed. 58 63 59 64 == Frequently Asked Questions == 60 65 61 = 1. What is AIT Easy Post Customization?=62 AIT Easy Post Customization is a WordPress plugin that allows users to easily set expiry dates for posts and custom post types, helping to manage content visibility and relevance effectively.66 === 1. What is AIT Easy Post Customization? === 67 AIT Easy Post Customization is a WordPress plugin that allows users to set expiry dates for posts and custom post types and manage custom fields, ensuring content remains relevant and up-to-date. 63 68 64 69 === 2. How do I install the plugin? === 65 70 You can install the plugin by uploading the plugin files to the `/wp-content/plugins/ait-easy-post-customization/` directory or by installing it directly through the WordPress plugins screen. After installation, activate the plugin through the 'Plugins' screen in WordPress. 66 71 67 === 3. Where can I configure the expiry datesettings? ===68 After activating the plugin, you can configure the expiry date settings by navigating to the **AIT Expiry Date** link in the WordPress sidebar. Here, you can manage expiry settings for your posts and custom post types.72 === 3. Where can I configure the settings? === 73 After activating the plugin, you can configure settings by navigating to the **Easy Post Customization** link in the WordPress sidebar. Here, you can manage expiry dates and custom fields for your posts and custom post types. 69 74 70 75 === 4. Can I set an expiry date for custom post types? === … … 72 77 73 78 === 5. What happens to a post when it expires? === 74 Once a post expires, it will be automatically hidden from public view. You can also configure settings to notify users or take specific actions when a post expires.79 Once a post expires, it will be automatically moved to draft status, hiding it from public view. Future updates may include options for other actions like deletion. 75 80 76 81 === 6. Is there any support available if I encounter issues? === … … 83 88 Yes, AIT Easy Post Customization is completely free to use and is licensed under the GPLv2 or later. 84 89 90 == Screenshots == 91 1. Admin settings page with modern tabbed interface and sticky navigation. 92 2. Expiry date metabox on a post edit screen. 93 3. Custom fields metabox on a post edit screen. 94 85 95 == Changelog == 96 = 1.0.2 - 2025-09-09 = 97 * Changed admin menu name from "AIT Expiry Date" to "Easy Post Customization" for better branding. 98 * Introduced modern admin interface with custom CSS (`aitepc-admin.css`), sticky tabs, Dashicons, and gradient effects. 99 * Added client-side form validation for custom fields with inline feedback for improved usability. 100 * Enhanced custom fields table with sticky headers and hover effects for better navigation. 101 * Optimized CSS with variables for maintainability and improved responsive design. 102 * Improved security with proper sanitization, nonce verification, and output escaping. 103 * Ensured compatibility with WordPress 6.6 and PHP 7.2+. 86 104 87 = 1.0.1 =105 = 1.0.1 - 2025-09-01 = 88 106 * Initial release of AIT Easy Post Customization. 89 107 * Added functionality to set expiry dates for posts and custom post types. 90 108 * Integrated user-friendly interface for managing expiry settings. 109 * Added custom fields management for text, textarea, and date fields. 110 111 == Upgrade Notice == 112 = 1.0.2 = 113 This update introduces a modern admin interface with sticky tabs, form validation, and enhanced usability. The menu name is now "Easy Post Customization" for consistency. Update for a more user-friendly experience. 91 114 92 115 == License & Copyright == 93 94 This plugin is licensed under the GPLv2 or later. 116 This plugin is licensed under the GPLv2 or later. 95 117 You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. 96 118 … … 98 120 99 121 == Contact == 100 101 122 For inquiries, project discussions, or collaboration opportunities, feel free to reach out to me: 102 123 103 - Email: [[email protected]](mailto:[email protected]) 104 - LinkedIn: [Muhammad Kamran](https://linkedin.com/in/muhammad-kamran-64a33a166) 124 - Email: [[email protected]](mailto:[email protected]) 125 - LinkedIn: [Muhammad Kamran](https://linkedin.com/in/muhammad-kamran-64a33a166) 105 126 106 127 I look forward to connecting with you! 107 -
ait-easy-post-customization/trunk/ait-easy-post-customization.php
r3353149 r3358149 1 1 <?php 2 3 2 /** 4 3 * Plugin Name: AIT Easy Post Customization 5 * Description: This plugin is essential for the proper theme functionality .6 * Version: 1.0. 17 * Author: kamranchannar8 * License: GPL 24 * Description: This plugin is essential for the proper theme functionality, including expiry date and custom fields management. 5 * Version: 1.0.2 6 * Author: Muhammad Kamran 7 * License: GPL-2.0-or-later 9 8 * Text Domain: ait-easy-post-customization 9 * Requires at least: 5.0 10 * Requires PHP: 7.4 10 11 */ 11 12 … … 17 18 register_activation_hook(__FILE__, 'aitepc_expiry_date_plugin_activate'); 18 19 function aitepc_expiry_date_plugin_activate() { 19 if (!get_option(' expiry_date_post_types')) {20 update_option(' expiry_date_post_types', array('post'));20 if (!get_option('aitepc_expiry_date_post_types')) { 21 update_option('aitepc_expiry_date_post_types', array('post')); 21 22 } 22 23 … … 41 42 function aitepc_check_expired_posts() { 42 43 $post_types = get_option('aitepc_expiry_date_post_types', array('post')); 43 $tomorrow = gmdate('Ymd', strtotime('+1 day')); // Changed to gmdate()44 $tomorrow = gmdate('Ymd', strtotime('+1 day')); 44 45 45 46 $args = array( … … 48 49 'meta_query' => array( 49 50 array( 50 'key' => '_aitepc_expiry_date', // Ensure consistency with your meta key51 'key' => '_aitepc_expiry_date', 51 52 'value' => $tomorrow, 52 53 'compare' => '<', … … 70 71 } 71 72 wp_reset_postdata(); 72 } 73 } 74 } 75 76 // Load translation 77 add_action('plugins_loaded', 'aitepc_load_textdomain'); 78 function aitepc_load_textdomain() { 79 load_plugin_textdomain('ait-easy-post-customization', false, dirname(plugin_basename(__FILE__)) . '/languages'); 80 } 81 82 // Register settings for Settings API 83 add_action('admin_init', 'aitepc_register_settings'); 84 function aitepc_register_settings() { 85 register_setting('aitepc_expiry_group', 'aitepc_expiry_date_post_types', array( 86 'sanitize_callback' => 'aitepc_sanitize_post_types', 87 'default' => array('post') 88 )); 89 90 add_settings_section( 91 'aitepc_post_types_section', 92 __('Select Post Types for Expiry Date Metabox', 'ait-easy-post-customization'), 93 null, 94 'aitepc-expiry-date-settings' 95 ); 96 97 $all_post_types = get_post_types(array('public' => true), 'objects'); 98 $filtered_post_types = array_filter($all_post_types, function($post_type) { 99 return !in_array($post_type->name, array('page', 'attachment')); 100 }); 101 102 foreach ($filtered_post_types as $post_type) { 103 add_settings_field( 104 'aitepc_pt_' . $post_type->name, 105 $post_type->label, 106 'aitepc_pt_checkbox_cb', 107 'aitepc-expiry-date-settings', 108 'aitepc_post_types_section', 109 array('pt' => $post_type->name) 110 ); 111 } 112 } 113 114 function aitepc_sanitize_post_types($input) { 115 return is_array($input) ? array_map('sanitize_text_field', $input) : array(); 116 } 117 118 function aitepc_pt_checkbox_cb($args) { 119 $options = get_option('aitepc_expiry_date_post_types', array()); 120 $checked = in_array($args['pt'], $options) ? 'checked' : ''; 121 printf( 122 '<input type="checkbox" name="aitepc_expiry_date_post_types[]" value="%s" %s class="form-check-input" />', 123 esc_attr($args['pt']), 124 $checked 125 ); 73 126 } 74 127 … … 77 130 require_once plugin_dir_path(__FILE__) . 'plugin-files/aitepc-settings.php'; 78 131 require_once plugin_dir_path(__FILE__) . 'plugin-files/aitepc-expiry-metabox.php'; 132 require_once plugin_dir_path(__FILE__) . 'plugin-files/aitepc-custom-fields.php'; -
ait-easy-post-customization/trunk/functions.php
r3353149 r3358149 5 5 // Enqueue local Bootstrap CSS and JS 6 6 wp_enqueue_style('ait-bootstrap-css', plugins_url('assets/css/bootstrap.min.css', __FILE__), array(), '5.3.3'); 7 wp_enqueue_style('aitepc-admin-css', plugins_url('assets/css/aitepc-admin.css', __FILE__), array(), '1.0.0'); 7 8 wp_enqueue_script('ait-bootstrap-js', plugins_url('assets/js/bootstrap.bundle.min.js', __FILE__), array('jquery'), '5.3.3', true); 9 10 wp_enqueue_style('dashicons'); 8 11 } 9 12 } -
ait-easy-post-customization/trunk/plugin-files/aitepc-expiry-metabox.php
r3353149 r3358149 1 1 <?php 2 // Prevent direct access to the file 2 /** 3 * AIT Easy Post Customization - Expiry Metabox 4 * 5 * @package AIT Easy Post Customization 6 */ 7 3 8 if (!defined('ABSPATH')) { 4 9 exit; … … 7 12 // Add Expiry Date Metabox to selected post types 8 13 function aitepc_add_expiry_date_metabox_to_selected_post_types() { 9 // Get selected post types from options10 14 $selected_post_types = get_option('aitepc_expiry_date_post_types', array()); 11 15 12 // Register the metabox for each selected post type13 16 foreach ($selected_post_types as $post_type) { 14 17 add_meta_box( 15 'aitepc_expiry_date_metabox', 16 'Expiry Date',17 'aitepc_expiry_date_metabox_callback', 18 $post_type, 19 'side', 20 'default' 18 'aitepc_expiry_date_metabox', 19 __('Expiry Date', 'ait-easy-post-customization'), 20 'aitepc_expiry_date_metabox_callback', 21 $post_type, 22 'side', 23 'default' 21 24 ); 22 25 } … … 28 31 wp_nonce_field('aitepc_save_expiry_date', 'aitepc_expiry_date_nonce'); 29 32 $expiry_date = get_post_meta($post->ID, '_aitepc_expiry_date', true); 30 echo '<label for="aitepc_expiry_date">Expiry Date: </label>'; 31 echo '<input type="date" id="aitepc_expiry_date" name="aitepc_expiry_date" value="' . esc_attr($expiry_date) . '" />'; 33 ?> 34 <div class="mb-3"> 35 <label for="aitepc_expiry_date" class="form-label"><?php _e('Expiry Date:', 'ait-easy-post-customization'); ?></label> 36 <input type="date" id="aitepc_expiry_date" name="aitepc_expiry_date" value="<?php echo esc_attr($expiry_date); ?>" class="form-control" /> 37 </div> 38 <?php 32 39 } 33 40 … … 49 56 $expiry_date = sanitize_text_field(wp_unslash($_POST['aitepc_expiry_date'])); 50 57 update_post_meta($post_id, '_aitepc_expiry_date', $expiry_date); 58 } else { 59 delete_post_meta($post_id, '_aitepc_expiry_date'); 51 60 } 52 61 } -
ait-easy-post-customization/trunk/plugin-files/aitepc-settings.php
r3353149 r3358149 1 1 <?php 2 // Prevent direct access to the file 2 /** 3 * AIT Easy Post Customization - Settings Page 4 * 5 * @package AIT Easy Post Customization 6 */ 7 3 8 if (!defined('ABSPATH')) { 4 9 exit; … … 8 13 function aitepc_expiry_date_menu_page() { 9 14 add_menu_page( 10 'Expiry Date Settings',11 'Expiry Date',15 __('Easy Post Customization Settings', 'ait-easy-post-customization'), 16 __('Easy Post Customization', 'ait-easy-post-customization'), 12 17 'manage_options', 13 18 'aitepc-expiry-date-settings', … … 21 26 // Render the settings page HTML 22 27 function aitepc_expiry_date_settings_page_html() { 23 // Check user capabilities24 28 if (!current_user_can('manage_options')) { 25 return; 26 } 27 28 // Save options when the form is submitted 29 if (isset($_POST['aitepc_expiry_date_post_types'])) { 30 // Verify the nonce 31 if (!isset($_POST['aitepc_expiry_date_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['aitepc_expiry_date_nonce'])), 'aitepc_expiry_date_nonce_action')) { 32 echo '<div class="alert alert-danger" role="alert">Security check failed. Please try again.</div>'; 33 return; 34 } 35 36 // Sanitize and validate input 37 $post_types = isset($_POST['aitepc_expiry_date_post_types']) ? array_map('sanitize_text_field', wp_unslash($_POST['aitepc_expiry_date_post_types'])) : []; 38 39 // Update the option with the validated post types 40 update_option('aitepc_expiry_date_post_types', $post_types); 41 echo '<div class="alert alert-success" role="alert">Settings saved successfully!</div>'; 42 } 43 44 // Retrieve current settings 45 $selected_post_types = get_option('aitepc_expiry_date_post_types', array()); 46 47 // Get all built-in public post types 29 wp_die(__('You do not have sufficient permissions to access this page.', 'ait-easy-post-customization')); 30 } 31 32 // Handle custom fields actions 33 aitepc_handle_custom_fields_submission(); 34 35 // Retrieve filtered post types for custom fields form 48 36 $all_post_types = get_post_types(array('public' => true), 'objects'); 49 50 // Exclude 'page' and 'attachment'51 37 $filtered_post_types = array_filter($all_post_types, function($post_type) { 52 38 return !in_array($post_type->name, array('page', 'attachment')); 53 39 }); 54 40 55 // Display the settings form with tabs56 41 ?> 57 <div class="container mt-4"> 58 <h2 class="mb-4">Expiry Date Metabox Settings</h2> 59 <!-- Nav tabs --> 60 <ul class="nav nav-tabs" id="myTab" role="tablist"> 42 <div class="wrap aitepc-wrap"> 43 <h1 class="aitepc-title"><span class="dashicons dashicons-admin-settings me-2"></span><?php _e('Easy Post Customization', 'ait-easy-post-customization'); ?></h1> 44 <ul class="nav nav-tabs aitepc-tabs" id="myTab" role="tablist"> 61 45 <li class="nav-item" role="presentation"> 62 <a class="nav-link active" id="post-types-tab" data-bs-toggle="tab" href="#post-types" role="tab" aria-controls="post-types" aria-selected="true">Assign Metabox</a> 46 <button class="nav-link active" id="post-types-tab" data-bs-toggle="tab" data-bs-target="#post-types" type="button" role="tab" aria-controls="post-types" aria-selected="true"> 47 <span class="dashicons dashicons-post-status me-1"></span><?php _e('Assign Metabox', 'ait-easy-post-customization'); ?> 48 </button> 63 49 </li> 64 50 <li class="nav-item" role="presentation"> 65 <a class="nav-link" id="coming-soon-tab" data-bs-toggle="tab" href="#coming-soon" role="tab" aria-controls="coming-soon" aria-selected="false">Coming Soon</a> 51 <button class="nav-link" id="custom-fields-tab" data-bs-toggle="tab" data-bs-target="#custom-fields" type="button" role="tab" aria-controls="custom-fields" aria-selected="false"> 52 <span class="dashicons dashicons-edit me-1"></span><?php _e('Custom Fields', 'ait-easy-post-customization'); ?> 53 </button> 54 </li> 55 <li class="nav-item" role="presentation"> 56 <button class="nav-link" id="coming-soon-tab" data-bs-toggle="tab" data-bs-target="#coming-soon" type="button" role="tab" aria-controls="coming-soon" aria-selected="false"> 57 <span class="dashicons dashicons-clock me-1"></span><?php _e('Coming Soon', 'ait-easy-post-customization'); ?> 58 </button> 66 59 </li> 67 60 </ul> 68 61 69 <!-- Tab content --> 70 <div class="tab-content mt-3" id="myTabContent"> 71 <!-- First tab for assigning post types --> 62 <div class="tab-content mt-4" id="myTabContent"> 72 63 <div class="tab-pane fade show active" id="post-types" role="tabpanel" aria-labelledby="post-types-tab"> 73 <form method="post" class="border p-4 rounded shadow-sm bg-light"> 74 <h4 class="mb-3">Select Post Types for Expiry Date Metabox</h4> 75 76 <!-- Nonce field for security --> 77 <?php wp_nonce_field('aitepc_expiry_date_nonce_action', 'aitepc_expiry_date_nonce'); ?> 78 64 <div class="card aitepc-card"> 65 <div class="card-body"> 66 <h2 class="card-title"><?php _e('Assign Expiry Date Metabox', 'ait-easy-post-customization'); ?></h2> 67 <p class="card-text text-muted"><?php _e('Select the post types where the expiry date metabox should appear.', 'ait-easy-post-customization'); ?></p> 68 <form method="post" action="options.php"> 69 <?php 70 settings_fields('aitepc_expiry_group'); 71 do_settings_sections('aitepc-expiry-date-settings'); 72 submit_button(__('Save Changes', 'ait-easy-post-customization'), 'primary aitepc-btn', 'submit', true); 73 ?> 74 </form> 75 </div> 76 </div> 77 </div> 78 79 <div class="tab-pane fade" id="custom-fields" role="tabpanel" aria-labelledby="custom-fields-tab"> 80 <?php aitepc_display_custom_fields_form($filtered_post_types); ?> 81 </div> 82 83 <div class="tab-pane fade" id="coming-soon" role="tabpanel" aria-labelledby="coming-soon-tab"> 84 <div class="card aitepc-card text-center"> 85 <div class="card-body"> 86 <h2 class="card-title"><?php _e('Coming Soon', 'ait-easy-post-customization'); ?></h2> 87 <p class="card-text"><?php _e('Exciting new features are under development. Stay tuned for updates!', 'ait-easy-post-customization'); ?></p> 88 <p><?php _e('Have ideas or feedback? We’d love to hear from you.', 'ait-easy-post-customization'); ?></p> 89 <p><?php _e('Contact:', 'ait-easy-post-customization'); ?> <a href="mailto:[email protected]" class="text-decoration-none">[email protected]</a></p> 90 <div class="alert alert-info"><?php _e('Thank you for your patience and support!', 'ait-easy-post-customization'); ?></div> 91 </div> 92 </div> 93 </div> 94 </div> 95 </div> 96 <?php 97 } 98 99 // Function to handle custom fields form submissions 100 function aitepc_handle_custom_fields_submission() { 101 if (!isset($_POST['aitepc_custom_fields_action'])) { 102 return; 103 } 104 105 if (!isset($_POST['aitepc_custom_fields_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['aitepc_custom_fields_nonce'])), 'aitepc_custom_fields_action')) { 106 echo '<div class="alert alert-danger" role="alert">' . __('Security check failed. Please try again.', 'ait-easy-post-customization') . '</div>'; 107 return; 108 } 109 110 $custom_fields = get_option('aitepc_custom_fields', array()); 111 $action = sanitize_text_field(wp_unslash($_POST['aitepc_custom_fields_action'])); 112 113 if ($action === 'add' || $action === 'edit') { 114 $key = sanitize_key(wp_unslash($_POST['field_key'])); 115 $label = sanitize_text_field(wp_unslash($_POST['field_label'])); 116 $type = sanitize_text_field(wp_unslash($_POST['field_type'])); 117 $post_types = isset($_POST['field_post_types']) ? array_map('sanitize_text_field', wp_unslash($_POST['field_post_types'])) : []; 118 119 if (empty($key) || empty($label) || empty($type)) { 120 echo '<div class="alert alert-danger" role="alert">' . __('All fields are required.', 'ait-easy-post-customization') . '</div>'; 121 return; 122 } 123 124 if ($action === 'add' && array_key_exists($key, $custom_fields)) { 125 echo '<div class="alert alert-danger" role="alert">' . __('Field key already exists.', 'ait-easy-post-customization') . '</div>'; 126 return; 127 } 128 129 $custom_fields[$key] = array( 130 'label' => $label, 131 'type' => $type, 132 'post_types' => $post_types 133 ); 134 135 update_option('aitepc_custom_fields', $custom_fields); 136 echo '<div class="alert alert-success" role="alert">' . __('Custom field ', 'ait-easy-post-customization') . ($action === 'add' ? __('added', 'ait-easy-post-customization') : __('updated', 'ait-easy-post-customization')) . __(' successfully!', 'ait-easy-post-customization') . '</div>'; 137 } elseif ($action === 'delete') { 138 $key = sanitize_key(wp_unslash($_POST['field_key'])); 139 if (array_key_exists($key, $custom_fields)) { 140 unset($custom_fields[$key]); 141 update_option('aitepc_custom_fields', $custom_fields); 142 echo '<div class="alert alert-success" role="alert">' . __('Custom field deleted successfully!', 'ait-easy-post-customization') . '</div>'; 143 } 144 } 145 } 146 147 // Function to display custom fields management 148 function aitepc_display_custom_fields_form($filtered_post_types) { 149 $custom_fields = get_option('aitepc_custom_fields', array()); 150 $edit_key = isset($_GET['edit']) ? sanitize_key($_GET['edit']) : ''; 151 $edit_field = $edit_key && isset($custom_fields[$edit_key]) ? $custom_fields[$edit_key] : null; 152 ?> 153 <div class="card aitepc-card"> 154 <div class="card-body"> 155 <h2 class="card-title mb-4"><?php echo $edit_field ? __('Edit Custom Field', 'ait-easy-post-customization') : __('Add New Custom Field', 'ait-easy-post-customization'); ?></h2> 156 <form method="post" class="needs-validation" novalidate> 157 <?php wp_nonce_field('aitepc_custom_fields_action', 'aitepc_custom_fields_nonce'); ?> 158 <input type="hidden" name="aitepc_custom_fields_action" value="<?php echo $edit_field ? 'edit' : 'add'; ?>"> 159 <?php if ($edit_field) : ?> 160 <input type="hidden" name="field_key" value="<?php echo esc_attr($edit_key); ?>"> 161 <?php endif; ?> 162 <div class="mb-3"> 163 <label for="field_label" class="form-label"><?php _e('Field Label', 'ait-easy-post-customization'); ?> <span class="text-danger">*</span></label> 164 <input type="text" class="form-control" id="field_label" name="field_label" value="<?php echo $edit_field ? esc_attr($edit_field['label']) : ''; ?>" required> 165 <div class="invalid-feedback"><?php _e('Please enter a field label.', 'ait-easy-post-customization'); ?></div> 166 </div> 167 <div class="mb-3"> 168 <label for="field_key" class="form-label"><?php _e('Field Key (slug)', 'ait-easy-post-customization'); ?> <span class="text-danger">*</span></label> 169 <input type="text" class="form-control" id="field_key" name="field_key" value="<?php echo $edit_field ? esc_attr($edit_key) : ''; ?>" <?php echo $edit_field ? 'readonly' : 'required'; ?> pattern="[a-z0-9_]+"> 170 <small class="form-text text-muted"><?php _e('Use lowercase letters, numbers, and underscores only. Cannot be changed after creation.', 'ait-easy-post-customization'); ?></small> 171 <div class="invalid-feedback"><?php _e('Please enter a valid field key (lowercase letters, numbers, underscores only).', 'ait-easy-post-customization'); ?></div> 172 </div> 173 <div class="mb-3"> 174 <label for="field_type" class="form-label"><?php _e('Field Type', 'ait-easy-post-customization'); ?> <span class="text-danger">*</span></label> 175 <select class="form-select" id="field_type" name="field_type" required> 176 <option value="text" <?php selected($edit_field ? $edit_field['type'] : '', 'text'); ?>><?php _e('Text', 'ait-easy-post-customization'); ?></option> 177 <option value="textarea" <?php selected($edit_field ? $edit_field['type'] : '', 'textarea'); ?>><?php _e('Textarea', 'ait-easy-post-customization'); ?></option> 178 <option value="date" <?php selected($edit_field ? $edit_field['type'] : '', 'date'); ?>><?php _e('Date', 'ait-easy-post-customization'); ?></option> 179 </select> 180 <div class="invalid-feedback"><?php _e('Please select a field type.', 'ait-easy-post-customization'); ?></div> 181 </div> 182 <div class="mb-4"> 183 <h5 class="mb-3"><?php _e('Select Post Types', 'ait-easy-post-customization'); ?></h5> 79 184 <div class="row"> 80 185 <?php foreach ($filtered_post_types as $post_type) : 81 $checked = in_array($post_type->name, $selected_post_types) ? 'checked="checked"' : ''; ?>186 $checked = $edit_field && in_array($post_type->name, $edit_field['post_types']) ? 'checked="checked"' : ''; ?> 82 187 <div class="col-md-4 mb-3 d-flex align-items-center"> 83 <input type="checkbox" class="form-check-input me-2" name=" aitepc_expiry_date_post_types[]" value="<?php echo esc_attr($post_type->name); ?>" <?php echo esc_attr($checked); ?> id="aitepc_<?php echo esc_attr($post_type->name); ?>">84 <label class="form-check-label" for=" aitepc_<?php echo esc_attr($post_type->name); ?>">188 <input type="checkbox" class="form-check-input me-2" name="field_post_types[]" value="<?php echo esc_attr($post_type->name); ?>" <?php echo esc_attr($checked); ?> id="field_<?php echo esc_attr($post_type->name); ?>"> 189 <label class="form-check-label" for="field_<?php echo esc_attr($post_type->name); ?>"> 85 190 <?php echo esc_html($post_type->label); ?> 86 191 </label> … … 88 193 <?php endforeach; ?> 89 194 </div> 90 <button type="submit" class="btn btn-primary mt-3">Save Changes</button> 91 </form> 92 </div> 93 94 <!-- Second tab for coming soon --> 95 <div class="tab-pane fade" id="coming-soon" role="tabpanel" aria-labelledby="coming-soon-tab"> 96 <div class="text-center"> 97 <h4>Coming Soon</h4> 98 <p>This feature is currently under development. Stay tuned for updates!</p> 99 <p>We’d love to hear your feedback! Let us know what features you’d like to see in the upcoming release.</p> 100 <p>Contact: <a href="mailto:[email protected]">[email protected]</a></p> 101 <div class="alert alert-info" role="alert"> 102 We appreciate your patience and support! 103 </div> 104 </div> 105 </div> 195 </div> 196 <div class="d-flex gap-2"> 197 <button type="submit" class="btn btn-primary aitepc-btn"><?php echo $edit_field ? __('Update Field', 'ait-easy-post-customization') : __('Add Field', 'ait-easy-post-customization'); ?></button> 198 <?php if ($edit_field) : ?> 199 <a href="<?php echo esc_url(admin_url('admin.php?page=aitepc-expiry-date-settings')); ?>" class="btn btn-outline-secondary"><?php _e('Cancel', 'ait-easy-post-customization'); ?></a> 200 <?php endif; ?> 201 </div> 202 </form> 106 203 </div> 107 204 </div> 205 206 <div class="card aitepc-card mt-4"> 207 <div class="card-body"> 208 <h2 class="card-title mb-4"><?php _e('Existing Custom Fields', 'ait-easy-post-customization'); ?></h2> 209 <?php if (!empty($custom_fields)) : ?> 210 <div class="table-responsive"> 211 <table class="table table-hover aitepc-table"> 212 <thead> 213 <tr> 214 <th><?php _e('Label', 'ait-easy-post-customization'); ?></th> 215 <th><?php _e('Key', 'ait-easy-post-customization'); ?></th> 216 <th><?php _e('Type', 'ait-easy-post-customization'); ?></th> 217 <th><?php _e('Post Types', 'ait-easy-post-customization'); ?></th> 218 <th><?php _e('Actions', 'ait-easy-post-customization'); ?></th> 219 </tr> 220 </thead> 221 <tbody> 222 <?php foreach ($custom_fields as $key => $field) : ?> 223 <tr> 224 <td><?php echo esc_html($field['label']); ?></td> 225 <td><?php echo esc_html($key); ?></td> 226 <td><?php echo esc_html($field['type']); ?></td> 227 <td><?php echo esc_html(implode(', ', $field['post_types'])); ?></td> 228 <td> 229 <a href="<?php echo esc_url(add_query_arg('edit', $key, admin_url('admin.php?page=aitepc-expiry-date-settings'))); ?>" class="btn btn-sm btn-primary aitepc-btn me-1"><?php _e('Edit', 'ait-easy-post-customization'); ?></a> 230 <form method="post" style="display:inline;"> 231 <?php wp_nonce_field('aitepc_custom_fields_action', 'aitepc_custom_fields_nonce'); ?> 232 <input type="hidden" name="aitepc_custom_fields_action" value="delete"> 233 <input type="hidden" name="field_key" value="<?php echo esc_attr($key); ?>"> 234 <button type="submit" class="btn btn-sm btn-danger aitepc-btn" onclick="return confirm('<?php esc_attr_e('Are you sure you want to delete this field?', 'ait-easy-post-customization'); ?>');"><?php _e('Delete', 'ait-easy-post-customization'); ?></button> 235 </form> 236 </td> 237 </tr> 238 <?php endforeach; ?> 239 </tbody> 240 </table> 241 </div> 242 <?php else : ?> 243 <p class="text-muted"><?php _e('No custom fields added yet.', 'ait-easy-post-customization'); ?></p> 244 <?php endif; ?> 245 </div> 246 </div> 247 <script> 248 // Bootstrap form validation 249 (function () { 250 'use strict'; 251 const forms = document.querySelectorAll('.needs-validation'); 252 Array.from(forms).forEach(form => { 253 form.addEventListener('submit', event => { 254 if (!form.checkValidity()) { 255 event.preventDefault(); 256 event.stopPropagation(); 257 } 258 form.classList.add('was-validated'); 259 }, false); 260 }); 261 })(); 262 </script> 108 263 <?php 109 264 } 110 ?> -
ait-easy-post-customization/trunk/readme.txt
r3353149 r3358149 1 1 === AIT Easy Post Customization === 2 2 Contributors: Muhammad Kamran 3 Tags: post expiration, post expiry, expiry date, schedule post, auto expire4 Requires at least: 5.0 5 Tested up to: 6. 66 Requires PHP: 7. 27 Stable tag: 1.0. 18 License: GPLv2 or later 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html 3 Tags: post expiration, post expiry, expiry date, schedule post, auto expire, custom fields 4 Requires at least: 5.0 5 Tested up to: 6.8 6 Requires PHP: 7.4 7 Stable tag: 1.0.2 8 License: GPLv2 or later 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html 10 10 11 11 Easily set expiry dates for posts and custom post types, automatically unpublishing content when it becomes outdated. 12 12 13 == AIT Easy Post Customization ==13 == Description == 14 14 15 15 AIT Easy Post Customization is a powerful yet easy-to-use plugin designed to give users control over the lifespan of their posts and custom post types by allowing you to set an expiry date. If you're managing time-sensitive content, like event announcements, special promotions, or limited-time offers, this plugin automates the process of unpublishing or archiving content, making content management hassle-free. 16 What problem does this plugin solve? 16 17 **What problem does this plugin solve?** 17 18 18 19 Manually tracking and updating expired content can be tedious and prone to errors, especially if you have a large volume of posts. Without this plugin, expired content can remain live, leading to outdated or irrelevant information on your website. AIT Easy Post Customization solves this problem by allowing you to set expiry dates on posts, ensuring that content is automatically hidden or removed after the specified date. This keeps your site fresh and up-to-date without manual intervention. 19 How does it improve the user experience?20 20 21 1. Automatic Expiry Handling: Set an expiration date for any post or custom post type, and let the plugin automatically unpublish it when the time comes. This eliminates the need for manual updates and improves content accuracy. 22 2. Custom Post Type Support: Whether you're using standard WordPress posts or custom post types (like events, job listings, products, etc.), you can easily add expiry functionality to all of them. 23 3. User-Friendly Interface: The plugin integrates seamlessly with the WordPress editor, making it easy for users of all skill levels to manage expiry dates. The expiry date can be set directly from the post editing screen. 21 **How does it improve the user experience?** 24 22 25 Key Features: 23 1. **Automatic Expiry Handling**: Set an expiration date for any post or custom post type, and let the plugin automatically unpublish it when the time comes. This eliminates the need for manual updates and improves content accuracy. 24 2. **Custom Post Type Support**: Whether you're using standard WordPress posts or custom post types (like events, job listings, products, etc.), you can easily add expiry functionality to all of them. 25 3. **User-Friendly Interface**: The plugin integrates seamlessly with the WordPress editor, making it easy for users of all skill levels to manage expiry dates. The expiry date can be set directly from the post editing screen. 26 4. **Custom Fields Management**: Add custom fields (text, textarea, date) to posts for enhanced content customization. 27 5. **Modern Admin Interface**: A sleek, responsive admin panel with sticky tabs, form validation, and intuitive navigation. 26 28 27 1. Post Expiration: Set expiration dates for posts and custom post types, and automatically unpublish them when the time is up. 28 2. Customizable Actions: Choose what happens to the post after it expires (e.g., move to draft, delete, or do nothing). 29 3. Supports All Post Types: Works with standard WordPress posts and custom post types, allowing flexible use across various content types. 30 4. Seamless Integration: Integrated into the post editor, making it easy to manage for both beginners and advanced users. 31 5. Scheduled Content Management: Perfect for managing content like event updates, limited-time offers, and seasonal announcements. 29 **Key Features**: 32 30 33 Notable Features: 31 1. **Post Expiration**: Set expiration dates for posts and custom post types, automatically moving them to draft status when expired. 32 2. **Custom Fields**: Add and manage custom fields (text, textarea, date) for selected post types. 33 3. **Supports All Post Types**: Works with standard WordPress posts and custom post types, allowing flexible use across various content types. 34 4. **Seamless Integration**: Integrated into the post editor with a modern, user-friendly interface. 35 5. **Scheduled Content Management**: Perfect for managing content like event updates, limited-time offers, and seasonal announcements. 36 6. **Localization Ready**: Fully translation-ready for multilingual websites. 34 37 35 1. Custom Widgets: Optionally display a widget that shows upcoming or recently expired content to site admins for better tracking. 36 2. Developer-Friendly Hooks and Filters: Developers can further customize the plugin’s behavior using built-in hooks and filters. 37 3. Localization Ready: Fully translation-ready, making it suitable for websites in any language. 38 **Notable Features**: 39 40 1. **Custom Widgets**: Optionally display a widget that shows upcoming or recently expired content to site admins for better tracking (coming soon). 41 2. **Developer-Friendly Hooks and Filters**: Developers can customize the plugin’s behavior using built-in hooks and filters. 42 3. **Modern Design**: Features a responsive admin interface with sticky tabs, Dashicons, and gradient effects for a premium experience. 38 43 39 44 With AIT Easy Post Customization, you’ll never have to worry about outdated or irrelevant content cluttering your site again. Keep your content fresh, current, and timely with minimal effort. … … 43 48 Follow these steps to install and activate AIT Easy Post Customization: 44 49 45 Upload Method:46 1. Upload the plugin files to the /wp-content/plugins/ait-easy-post-customization/directory.50 **Upload Method**: 51 1. Upload the plugin files to the `/wp-content/plugins/ait-easy-post-customization/` directory. 47 52 48 OR 53 **OR** 49 54 50 Direct Install via WordPress:51 1. Go to the WordPress dashboard, navigate to Plugins -> Add New, and search for AIT Easy Post Customization. Click Install Now.55 **Direct Install via WordPress**: 56 1. Go to the WordPress dashboard, navigate to Plugins -> Add New, and search for AIT Easy Post Customization. Click Install Now. 52 57 53 Activate the Plugin:54 1. Once installed, go to the Plugins screen in WordPress and click Activate next to AIT Easy Post Customization.58 **Activate the Plugin**: 59 1. Once installed, go to the Plugins screen in WordPress and click Activate next to AIT Easy Post Customization. 55 60 56 Configure the Plugin:57 1. After activation, a new menu item called AIT Expiry Date will appear in the WordPress sidebar. Click on it to access the plugin settings and configure the expiry dateoptions as needed.61 **Configure the Plugin**: 62 1. After activation, a new menu item called **Easy Post Customization** will appear in the WordPress sidebar. Click on it to access the plugin settings and configure the expiry date and custom field options as needed. 58 63 59 64 == Frequently Asked Questions == 60 65 61 = 1. What is AIT Easy Post Customization?=62 AIT Easy Post Customization is a WordPress plugin that allows users to easily set expiry dates for posts and custom post types, helping to manage content visibility and relevance effectively.66 === 1. What is AIT Easy Post Customization? === 67 AIT Easy Post Customization is a WordPress plugin that allows users to set expiry dates for posts and custom post types and manage custom fields, ensuring content remains relevant and up-to-date. 63 68 64 69 === 2. How do I install the plugin? === 65 70 You can install the plugin by uploading the plugin files to the `/wp-content/plugins/ait-easy-post-customization/` directory or by installing it directly through the WordPress plugins screen. After installation, activate the plugin through the 'Plugins' screen in WordPress. 66 71 67 === 3. Where can I configure the expiry datesettings? ===68 After activating the plugin, you can configure the expiry date settings by navigating to the **AIT Expiry Date** link in the WordPress sidebar. Here, you can manage expiry settings for your posts and custom post types.72 === 3. Where can I configure the settings? === 73 After activating the plugin, you can configure settings by navigating to the **Easy Post Customization** link in the WordPress sidebar. Here, you can manage expiry dates and custom fields for your posts and custom post types. 69 74 70 75 === 4. Can I set an expiry date for custom post types? === … … 72 77 73 78 === 5. What happens to a post when it expires? === 74 Once a post expires, it will be automatically hidden from public view. You can also configure settings to notify users or take specific actions when a post expires.79 Once a post expires, it will be automatically moved to draft status, hiding it from public view. Future updates may include options for other actions like deletion. 75 80 76 81 === 6. Is there any support available if I encounter issues? === … … 83 88 Yes, AIT Easy Post Customization is completely free to use and is licensed under the GPLv2 or later. 84 89 90 == Screenshots == 91 1. Admin settings page with modern tabbed interface and sticky navigation. 92 2. Expiry date metabox on a post edit screen. 93 3. Custom fields metabox on a post edit screen. 94 85 95 == Changelog == 96 = 1.0.2 - 2025-09-09 = 97 * Changed admin menu name from "AIT Expiry Date" to "Easy Post Customization" for better branding. 98 * Introduced modern admin interface with custom CSS (`aitepc-admin.css`), sticky tabs, Dashicons, and gradient effects. 99 * Added client-side form validation for custom fields with inline feedback for improved usability. 100 * Enhanced custom fields table with sticky headers and hover effects for better navigation. 101 * Optimized CSS with variables for maintainability and improved responsive design. 102 * Improved security with proper sanitization, nonce verification, and output escaping. 103 * Ensured compatibility with WordPress 6.6 and PHP 7.2+. 86 104 87 = 1.0.1 =105 = 1.0.1 - 2025-09-01 = 88 106 * Initial release of AIT Easy Post Customization. 89 107 * Added functionality to set expiry dates for posts and custom post types. 90 108 * Integrated user-friendly interface for managing expiry settings. 109 * Added custom fields management for text, textarea, and date fields. 110 111 == Upgrade Notice == 112 = 1.0.2 = 113 This update introduces a modern admin interface with sticky tabs, form validation, and enhanced usability. The menu name is now "Easy Post Customization" for consistency. Update for a more user-friendly experience. 91 114 92 115 == License & Copyright == 93 94 This plugin is licensed under the GPLv2 or later. 116 This plugin is licensed under the GPLv2 or later. 95 117 You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. 96 118 … … 98 120 99 121 == Contact == 100 101 122 For inquiries, project discussions, or collaboration opportunities, feel free to reach out to me: 102 123 103 - Email: [[email protected]](mailto:[email protected]) 104 - LinkedIn: [Muhammad Kamran](https://linkedin.com/in/muhammad-kamran-64a33a166) 124 - Email: [[email protected]](mailto:[email protected]) 125 - LinkedIn: [Muhammad Kamran](https://linkedin.com/in/muhammad-kamran-64a33a166) 105 126 106 127 I look forward to connecting with you! 107
Note: See TracChangeset
for help on using the changeset viewer.