Changeset 3478245
- Timestamp:
- 03/09/2026 03:02:26 PM (4 weeks ago)
- Location:
- a1-tools/trunk
- Files:
-
- 3 edited
-
a1-tools.php (modified) (2 diffs)
-
includes/class-a1-tools-media-management.php (modified) (9 diffs)
-
readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
a1-tools/trunk/a1-tools.php
r3478238 r3478245 4 4 * Plugin URI: https://tools.a-1chimney.com 5 5 * Description: Connects your WordPress site to the A1 Tools platform for centralized management of contact information, social media links, and business details. 6 * Version: 1. 9.96 * Version: 1.10.0 7 7 * Requires at least: 5.0 8 8 * Requires PHP: 7.4 … … 21 21 22 22 // Plugin constants. 23 define( 'A1TOOLS_VERSION', '1. 9.9' );23 define( 'A1TOOLS_VERSION', '1.10.0' ); 24 24 define( 'A1TOOLS_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); 25 25 define( 'A1TOOLS_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); -
a1-tools/trunk/includes/class-a1-tools-media-management.php
r3478238 r3478245 42 42 add_action( 'wp_ajax_a1tools_find_duplicates', array( $this, 'ajax_find_duplicates' ) ); 43 43 add_action( 'wp_ajax_a1tools_scan_unused', array( $this, 'ajax_scan_unused' ) ); 44 add_action( 'wp_ajax_a1tools_delete_media', array( $this, 'ajax_delete_media' ) ); 44 45 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) ); 45 46 } … … 70 71 'nonceDuplicates' => wp_create_nonce( 'a1tools_find_duplicates' ), 71 72 'nonceUnused' => wp_create_nonce( 'a1tools_scan_unused' ), 73 'nonceDelete' => wp_create_nonce( 'a1tools_delete_media' ), 72 74 ) ); 73 75 … … 589 591 590 592 // ========================================================================= 593 // AJAX: Delete Media 594 // ========================================================================= 595 596 public function ajax_delete_media() { 597 check_ajax_referer( 'a1tools_delete_media', 'nonce' ); 598 if ( ! current_user_can( 'delete_posts' ) ) { 599 wp_send_json_error( array( 'message' => 'Permission denied.' ) ); 600 } 601 602 $id = absint( $_POST['attachment_id'] ?? 0 ); 603 if ( ! $id ) { 604 wp_send_json_error( array( 'message' => 'Invalid attachment ID.' ) ); 605 } 606 607 $post = get_post( $id ); 608 if ( ! $post || 'attachment' !== $post->post_type ) { 609 wp_send_json_error( array( 'message' => 'Attachment not found.' ) ); 610 } 611 612 $deleted = wp_delete_attachment( $id, true ); 613 if ( ! $deleted ) { 614 wp_send_json_error( array( 'message' => 'Could not delete the file.' ) ); 615 } 616 617 wp_send_json_success( array( 'message' => 'File deleted.', 'id' => $id ) ); 618 } 619 620 // ========================================================================= 591 621 // Rename Logic (preserved from v1) 592 622 // ========================================================================= … … 906 936 .a1mm-field-input-wrap { flex:1; display:flex; align-items:center; gap:4px; flex-wrap:wrap; min-width:0; } 907 937 908 /* Rename Row */ 909 .a1mm-rename-row { display:flex; align-items:center; gap:4px; flex-wrap:wrap; } 910 .a1mm-new-name { flex:1; min-width:180px; max-width:420px; padding:5px 8px; font-size:13px; font-family:monospace; } 911 .a1mm-ext { font-size:13px; color:#666; font-family:monospace; margin-right:6px; } 912 .a1mm-refs-label { font-size:12px; color:#555; margin-right:6px; cursor:pointer; display:inline-flex; align-items:center; gap:3px; } 938 /* Ext label */ 939 .a1mm-ext { font-size:13px; color:#666; font-family:monospace; flex-shrink:0; } 940 941 /* Meta Inputs — full width */ 942 .a1mm-meta-input { flex:1; min-width:0; padding:5px 8px; font-size:13px; } 943 .a1mm-new-name { font-family:monospace; } 944 textarea.a1mm-meta-input { resize:vertical; font-family:inherit; } 945 946 /* Actions Row */ 947 .a1mm-actions-row { gap:8px !important; } 948 .a1mm-refs-label { font-size:12px; color:#555; cursor:pointer; display:inline-flex; align-items:center; gap:3px; margin-right:4px; } 913 949 .a1mm-item-msg { font-size:12px; margin-left:4px; } 914 950 .a1mm-item-msg.success { color:#00a32a; } 915 951 .a1mm-item-msg.error { color:#d63638; } 916 .a1mm-undo-link { font-size:12px; color:#2271b1; cursor:pointer; margin-left: 6px; text-decoration:underline; }952 .a1mm-undo-link { font-size:12px; color:#2271b1; cursor:pointer; margin-left:4px; text-decoration:underline; } 917 953 .a1mm-undo-link:hover { color:#135e96; } 918 919 /* Meta Inputs */ 920 .a1mm-meta-input { flex:1; min-width:180px; max-width:420px; padding:4px 8px; font-size:13px; } 921 textarea.a1mm-meta-input { resize:vertical; font-family:inherit; } 922 .a1mm-meta-msg { font-size:12px; margin-left:8px; } 954 .a1mm-delete-btn { color:#b32d2e !important; border-color:#b32d2e !important; } 955 .a1mm-delete-btn:hover { background:#b32d2e !important; color:#fff !important; } 956 .a1mm-meta-msg { font-size:12px; margin-left:4px; } 923 957 .a1mm-meta-msg.success { color:#00a32a; } 924 958 .a1mm-meta-msg.error { color:#d63638; } … … 1047 1081 + '<div class="a1mm-field-row">' 1048 1082 + '<span class="a1mm-field-label">File Name:</span>' 1049 + '<div class="a1mm-field-input-wrap a1mm-rename-row">' 1050 + '<input type="text" class="a1mm-new-name" value="'+escA(noExt)+'" />' 1051 + '<span class="a1mm-ext">.'+esc(ext)+'</span>' 1052 + '<label class="a1mm-refs-label"><input type="checkbox" class="a1mm-refs-cb" checked /> Update refs</label>' 1053 + '<button type="button" class="button button-primary a1mm-rename-btn">Rename</button>' 1054 + '<span class="a1mm-item-msg"></span>' 1055 + '</div>' 1083 + '<div class="a1mm-field-input-wrap"><input type="text" class="a1mm-meta-input a1mm-new-name" value="'+escA(noExt)+'" /><span class="a1mm-ext">.'+esc(ext)+'</span></div>' 1056 1084 + '</div>' 1057 1085 + '<div class="a1mm-field-row">' 1058 1086 + '<span class="a1mm-field-label">Alt Text:</span>' 1059 + '<div class="a1mm-field-input-wrap"><input type="text" class="a1mm-meta-input a1mm-alt-input" data-field="alt_text"value="'+escA(it.alt_text)+'" placeholder="Describe this image for SEO..." /></div>'1087 + '<div class="a1mm-field-input-wrap"><input type="text" class="a1mm-meta-input a1mm-alt-input" value="'+escA(it.alt_text)+'" placeholder="Describe this image for SEO..." /></div>' 1060 1088 + '</div>' 1061 1089 + '<div class="a1mm-field-row">' 1062 1090 + '<span class="a1mm-field-label">Title:</span>' 1063 + '<div class="a1mm-field-input-wrap"><input type="text" class="a1mm-meta-input a1mm-title-input" data-field="title"value="'+escA(it.title)+'" /></div>'1091 + '<div class="a1mm-field-input-wrap"><input type="text" class="a1mm-meta-input a1mm-title-input" value="'+escA(it.title)+'" /></div>' 1064 1092 + '</div>' 1065 1093 + '<div class="a1mm-field-row">' 1066 1094 + '<span class="a1mm-field-label">Caption:</span>' 1067 + '<div class="a1mm-field-input-wrap"><input type="text" class="a1mm-meta-input a1mm-caption-input" data-field="caption"value="'+escA(it.caption)+'" placeholder="Short caption..." /></div>'1095 + '<div class="a1mm-field-input-wrap"><input type="text" class="a1mm-meta-input a1mm-caption-input" value="'+escA(it.caption)+'" placeholder="Short caption..." /></div>' 1068 1096 + '</div>' 1069 1097 + '<div class="a1mm-field-row">' 1070 1098 + '<span class="a1mm-field-label">Description:</span>' 1071 + '<div class="a1mm-field-input-wrap"><textarea class="a1mm-meta-input a1mm-desc-input" data-field="description"rows="2" placeholder="Longer description...">'+esc(it.description)+'</textarea></div>'1099 + '<div class="a1mm-field-input-wrap"><textarea class="a1mm-meta-input a1mm-desc-input" rows="2" placeholder="Longer description...">'+esc(it.description)+'</textarea></div>' 1072 1100 + '</div>' 1073 1101 + '<div class="a1mm-field-row">' 1074 1102 + '<span class="a1mm-field-label"></span>' 1075 + '<div class="a1mm-field-input-wrap"><button type="button" class="button a1mm-save-meta-btn">Save Details</button><span class="a1mm-meta-msg"></span></div>' 1103 + '<div class="a1mm-field-input-wrap a1mm-actions-row">' 1104 + '<label class="a1mm-refs-label"><input type="checkbox" class="a1mm-refs-cb" checked /> Update refs</label>' 1105 + '<button type="button" class="button button-primary a1mm-save-meta-btn">Save</button>' 1106 + '<button type="button" class="button a1mm-delete-btn">Delete</button>' 1107 + '<span class="a1mm-item-msg"></span>' 1108 + '</div>' 1076 1109 + '</div>'; 1077 1110 … … 1113 1146 }); 1114 1147 1115 // ---- Rename ---- 1116 function doRename($it) { 1148 // ---- Save (rename if changed + meta) ---- 1149 $(document).on('click', '.a1mm-save-meta-btn', function() { 1150 var $it = $(this).closest('.a1mm-item'); 1117 1151 var id = $it.data('id'); 1118 var nn = $it.find('.a1mm-new-name').val().trim(); 1152 var $b = $(this); 1153 var $m = $it.find('.a1mm-item-msg'); 1154 var newName = $it.find('.a1mm-new-name').val().trim(); 1155 var curFile = $it.find('.a1mm-current code').text(); 1156 var curName = curFile.replace(/\.[^.]+$/,''); 1119 1157 var ur = $it.find('.a1mm-refs-cb').is(':checked') ? 1 : 0; 1120 var $b = $it.find('.a1mm-rename-btn'); 1121 var $m = $it.find('.a1mm-item-msg'); 1122 if (!nn) { $m.text('Name cannot be empty.').attr('class','a1mm-item-msg error'); return; } 1123 $b.prop('disabled',true).text('Renaming...'); 1158 $b.prop('disabled',true).text('Saving...'); 1124 1159 $m.text('').attr('class','a1mm-item-msg'); 1125 1126 $.post(a1mm.ajax, { 1127 action:'a1tools_rename_media', nonce:a1mm.nonceRename, 1128 attachment_id:id, new_filename:nn, update_refs:ur 1129 }, function(r) { 1130 $b.prop('disabled',false).text('Rename'); 1131 if (r.success) { 1132 var d=r.data; 1133 var mt = 'Renamed to ' + d.new_filename; 1134 if (ur && d.refs_updated > 0) mt += ' ('+d.refs_updated+' refs updated)'; 1135 $m.text(mt).attr('class','a1mm-item-msg success'); 1136 $it.addClass('a1mm-renamed'); 1137 $it.data('old-name', d.old_filename); 1138 $it.find('.a1mm-current code').text(d.new_filename); 1139 $it.find('.a1mm-new-name').val(d.new_filename.replace(/\.[^.]+$/,'')); 1140 if (d.new_thumbnail_url) $it.find('.a1mm-thumb img').attr('src', d.new_thumbnail_url); 1141 $it.data('url', d.new_url); 1142 // Show undo link 1143 $m.after('<span class="a1mm-undo-link">Undo</span>'); 1144 // Update badges: remove "Needs rename" badge 1145 $it.find('.a1mm-badge-random').remove(); 1146 } else { 1147 $m.text(r.data.message||'Failed.').attr('class','a1mm-item-msg error'); 1148 } 1149 }).fail(function() { 1150 $b.prop('disabled',false).text('Rename'); 1151 $m.text('Network error.').attr('class','a1mm-item-msg error'); 1152 }); 1153 } 1160 $it.find('.a1mm-undo-link').remove(); 1161 1162 function saveMeta(msg) { 1163 $.post(a1mm.ajax, { 1164 action:'a1tools_update_media_meta', nonce:a1mm.nonceMeta, 1165 attachment_id: id, 1166 alt_text: $it.find('.a1mm-alt-input').val(), 1167 title: $it.find('.a1mm-title-input').val(), 1168 caption: $it.find('.a1mm-caption-input').val(), 1169 description: $it.find('.a1mm-desc-input').val() 1170 }, function(r) { 1171 $b.prop('disabled',false).text('Save'); 1172 if (r.success) { 1173 var txt = msg || 'Saved!'; 1174 $m.text(txt).attr('class','a1mm-item-msg success'); 1175 if ($it.find('.a1mm-alt-input').val()) $it.find('.a1mm-badge-no-alt').remove(); 1176 } else { 1177 $m.text(r.data.message||'Meta save failed.').attr('class','a1mm-item-msg error'); 1178 } 1179 }).fail(function() { 1180 $b.prop('disabled',false).text('Save'); 1181 $m.text('Network error.').attr('class','a1mm-item-msg error'); 1182 }); 1183 } 1184 1185 // Check if filename changed — rename first, then save meta. 1186 if (newName && newName !== curName) { 1187 $.post(a1mm.ajax, { 1188 action:'a1tools_rename_media', nonce:a1mm.nonceRename, 1189 attachment_id:id, new_filename:newName, update_refs:ur 1190 }, function(r) { 1191 if (r.success) { 1192 var d = r.data; 1193 $it.data('old-name', d.old_filename); 1194 $it.find('.a1mm-current code').text(d.new_filename); 1195 $it.find('.a1mm-new-name').val(d.new_filename.replace(/\.[^.]+$/,'')); 1196 if (d.new_thumbnail_url) $it.find('.a1mm-thumb img').attr('src', d.new_thumbnail_url); 1197 $it.data('url', d.new_url); 1198 $it.addClass('a1mm-renamed'); 1199 $it.find('.a1mm-badge-random').remove(); 1200 var mt = 'Renamed & saved!'; 1201 if (ur && d.refs_updated > 0) mt = 'Renamed (' + d.refs_updated + ' refs) & saved!'; 1202 saveMeta(mt); 1203 // Show undo after msg 1204 setTimeout(function(){ 1205 $m.after('<span class="a1mm-undo-link">Undo rename</span>'); 1206 }, 100); 1207 } else { 1208 $b.prop('disabled',false).text('Save'); 1209 $m.text(r.data.message||'Rename failed.').attr('class','a1mm-item-msg error'); 1210 } 1211 }).fail(function() { 1212 $b.prop('disabled',false).text('Save'); 1213 $m.text('Network error.').attr('class','a1mm-item-msg error'); 1214 }); 1215 } else { 1216 saveMeta(); 1217 } 1218 }); 1154 1219 1155 1220 // ---- Undo Rename ---- … … 1159 1224 if (!oldName) return; 1160 1225 var noExt = oldName.replace(/\.[^.]+$/,''); 1161 $it.find('.a1mm-new-name').val(noExt); 1162 $it.find('.a1mm-undo-link').remove(); 1163 doRename($it); 1226 var id = $it.data('id'); 1227 var ur = $it.find('.a1mm-refs-cb').is(':checked') ? 1 : 0; 1228 var $m = $it.find('.a1mm-item-msg'); 1229 $(this).remove(); 1230 $m.text('Undoing...').attr('class','a1mm-item-msg'); 1231 $.post(a1mm.ajax, { 1232 action:'a1tools_rename_media', nonce:a1mm.nonceRename, 1233 attachment_id:id, new_filename:noExt, update_refs:ur 1234 }, function(r) { 1235 if (r.success) { 1236 $it.find('.a1mm-current code').text(r.data.new_filename); 1237 $it.find('.a1mm-new-name').val(r.data.new_filename.replace(/\.[^.]+$/,'')); 1238 if (r.data.new_thumbnail_url) $it.find('.a1mm-thumb img').attr('src', r.data.new_thumbnail_url); 1239 $it.data('url', r.data.new_url); 1240 $it.data('old-name',''); 1241 $it.removeClass('a1mm-renamed'); 1242 $m.text('Rename undone.').attr('class','a1mm-item-msg success'); 1243 } else { 1244 $m.text(r.data.message||'Undo failed.').attr('class','a1mm-item-msg error'); 1245 } 1246 }); 1164 1247 }); 1165 1248 1166 // ---- Save Meta (Alt, Title, Caption, Description)----1167 $(document).on('click', '.a1mm- save-meta-btn', function() {1249 // ---- Delete ---- 1250 $(document).on('click', '.a1mm-delete-btn', function() { 1168 1251 var $it = $(this).closest('.a1mm-item'); 1169 1252 var id = $it.data('id'); 1253 var fn = $it.find('.a1mm-current code').text(); 1254 if (!confirm('Permanently delete "' + fn + '"? This cannot be undone.')) return; 1170 1255 var $b = $(this); 1171 var $m = $it.find('.a1mm-meta-msg'); 1172 $b.prop('disabled',true).text('Saving...'); 1256 $b.prop('disabled',true).text('Deleting...'); 1173 1257 1174 1258 $.post(a1mm.ajax, { 1175 action:'a1tools_update_media_meta', nonce:a1mm.nonceMeta, 1176 attachment_id: id, 1177 alt_text: $it.find('.a1mm-alt-input').val(), 1178 title: $it.find('.a1mm-title-input').val(), 1179 caption: $it.find('.a1mm-caption-input').val(), 1180 description: $it.find('.a1mm-desc-input').val() 1259 action:'a1tools_delete_media', nonce:a1mm.nonceDelete, 1260 attachment_id: id 1181 1261 }, function(r) { 1182 $b.prop('disabled',false).text('Save Details');1183 1262 if (r.success) { 1184 $m.text('Saved!').attr('class','a1mm-meta-msg success'); 1185 setTimeout(function(){ $m.text(''); }, 2500); 1186 if ($it.find('.a1mm-alt-input').val()) $it.find('.a1mm-badge-no-alt').remove(); 1263 $it.slideUp(300, function() { $(this).remove(); }); 1187 1264 } else { 1188 $m.text(r.data.message||'Failed.').attr('class','a1mm-meta-msg error'); 1265 $b.prop('disabled',false).text('Delete'); 1266 alert(r.data.message||'Delete failed.'); 1189 1267 } 1190 1268 }).fail(function() { 1191 $b.prop('disabled',false).text(' Save Details');1192 $m.text('Network error.').attr('class','a1mm-meta-msg error');1269 $b.prop('disabled',false).text('Delete'); 1270 alert('Network error.'); 1193 1271 }); 1194 1272 }); … … 1284 1362 1285 1363 // ---- Event Handlers ---- 1286 $(document).on('click', '.a1mm-rename-btn', function() { doRename($(this).closest('.a1mm-item')); });1287 1364 $(document).on('click', '.a1mm-page-btn', function() { 1288 1365 loadMedia(parseInt($(this).data('page'),10)); … … 1292 1369 $('#a1mm-search').on('keypress', function(e) { if(e.which===13) loadMedia(1); }); 1293 1370 $('#a1mm-mime-filter, #a1mm-sort').on('change', function() { loadMedia(1); }); 1294 $(document).on('keypress', '.a1mm-new-name', function(e) { if(e.which===13) doRename($(this).closest('.a1mm-item')); });1371 $(document).on('keypress', '.a1mm-new-name', function(e) { if(e.which===13) $(this).closest('.a1mm-item').find('.a1mm-save-meta-btn').click(); }); 1295 1372 $(document).on('keypress', '.a1mm-meta-input', function(e) { 1296 1373 if(e.which===13 && !$(this).is('textarea')) $(this).closest('.a1mm-item').find('.a1mm-save-meta-btn').click(); -
a1-tools/trunk/readme.txt
r3478238 r3478245 4 4 Requires at least: 5.0 5 5 Tested up to: 6.9 6 Stable tag: 1. 9.96 Stable tag: 1.10.0 7 7 Requires PHP: 7.4 8 8 License: GPLv2 or later … … 151 151 152 152 == Changelog == 153 154 = 1.10.0 = 155 * Improved: Unified Save button — renames file (if changed) and saves all meta in one click 156 * New: Delete button to permanently remove media files 157 * Improved: Full-width input fields for better usability 158 * Improved: Update refs checkbox moved to actions row with Save and Delete 153 159 154 160 = 1.9.9 =
Note: See TracChangeset
for help on using the changeset viewer.