Changeset 3760546
- Timestamp:
- 10/22/2026 09:20:01 PM (3 hours ago)
- Location:
- pvr-media-reviews-for-woocommerce
- Files:
-
- 22 edited
- 1 copied
-
tags/1.2.8 (copied) (copied from pvr-media-reviews-for-woocommerce/trunk )
-
tags/1.2.8/frontend/css/pvr-frontend.css (modified) (4 diffs)
-
tags/1.2.8/frontend/js/pvr-frontend.js (modified) (4 diffs)
-
tags/1.2.8/frontend/js/pvr-media-upload.js (modified) (12 diffs)
-
tags/1.2.8/includes/class-pvr-hooks.php (modified) (1 diff)
-
tags/1.2.8/includes/class-pvr-media.php (modified) (2 diffs)
-
tags/1.2.8/includes/class-pvr-review.php (modified) (1 diff)
-
tags/1.2.8/languages/pvr-media-reviews-for-woocommerce-en_US.json (modified) (1 diff)
-
tags/1.2.8/languages/pvr-media-reviews-for-woocommerce-es_ES.json (modified) (1 diff)
-
tags/1.2.8/languages/pvr-media-reviews-for-woocommerce-ru_RU.json (modified) (1 diff)
-
tags/1.2.8/photo-video-reviews-for-woocommerce.php (modified) (2 diffs)
-
tags/1.2.8/readme.txt (modified) (2 diffs)
-
trunk/frontend/css/pvr-frontend.css (modified) (4 diffs)
-
trunk/frontend/js/pvr-frontend.js (modified) (4 diffs)
-
trunk/frontend/js/pvr-media-upload.js (modified) (12 diffs)
-
trunk/includes/class-pvr-hooks.php (modified) (1 diff)
-
trunk/includes/class-pvr-media.php (modified) (2 diffs)
-
trunk/includes/class-pvr-review.php (modified) (1 diff)
-
trunk/languages/pvr-media-reviews-for-woocommerce-en_US.json (modified) (1 diff)
-
trunk/languages/pvr-media-reviews-for-woocommerce-es_ES.json (modified) (1 diff)
-
trunk/languages/pvr-media-reviews-for-woocommerce-ru_RU.json (modified) (1 diff)
-
trunk/photo-video-reviews-for-woocommerce.php (modified) (2 diffs)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
pvr-media-reviews-for-woocommerce/tags/1.2.8/frontend/css/pvr-frontend.css
r3748498 r3760546 317 317 align-items: center; 318 318 justify-content: center; 319 padding: 20px;319 padding: 5px; 320 320 z-index: 100001; 321 321 } … … 1669 1669 overflow: hidden; 1670 1670 text-overflow: ellipsis; 1671 white-space: nowrap; 1671 /* nowrap stretched the identity row past the viewport on mobile — 1672 long product names must wrap instead of forcing min-content width. */ 1673 overflow-wrap: anywhere; 1672 1674 } 1673 1675 … … 2160 2162 .pvr-form input{ 2161 2163 outline: 0 !important; 2164 } 2165 2166 /* Class-scoped text inputs: themes (Astra et al.) style input[type=...] 2167 with the same specificity as the shared .pvr-form input rule above and 2168 win by loading later — that is how sharp corners and theme borders 2169 replaced the form look on client sites. Same declarations as the 2170 textarea so the fields match. */ 2171 .pvr-form .pvr-form__input, 2172 .pvr-reply-form .pvr-form__input { 2173 border: 1px solid rgba(203, 213, 225, 0.9); 2174 border-radius: 16px; 2175 background: #ffffff; 2176 color: var(--pvr-text-color); 2177 padding: 12px 14px; 2178 font-family: inherit; 2179 font-size: 14px; 2180 line-height: 1.45; 2181 box-shadow: none; 2182 } 2183 2184 .pvr-form .pvr-form__input:focus, 2185 .pvr-reply-form .pvr-form__input:focus { 2186 border-color: var(--pvr-accent-color); 2162 2187 } 2163 2188 … … 2772 2797 margin: 0; 2773 2798 line-height: 1.5; 2799 } 2800 2801 /* Partial upload failure notice: the review IS submitted, so this stays 2802 inside the thank-you screen (never an error screen) and offers a retry. */ 2803 .pvr-thankyou__warning { 2804 width: 100%; 2805 max-width: 340px; 2806 display: flex; 2807 flex-direction: column; 2808 gap: 10px; 2809 padding: 12px 16px; 2810 border: 1px solid rgba(245, 158, 11, 0.45); 2811 background: rgba(245, 158, 11, 0.08); 2812 border-radius: 12px; 2813 box-sizing: border-box; 2814 } 2815 2816 .pvr-thankyou__warning-text { 2817 margin: 0; 2818 font-size: 13px; 2819 line-height: 1.5; 2820 color: var(--pvr-text-color, #1f2937); 2821 } 2822 2823 .pvr-thankyou__warning-actions { 2824 display: flex; 2825 justify-content: center; 2826 } 2827 2828 .pvr-thankyou__retry { 2829 padding: 8px 22px; 2830 border-radius: 999px; 2831 border: none; 2832 background: var(--pvr-accent-color, #2563eb); 2833 color: #fff; 2834 font-size: 13px; 2835 font-weight: 600; 2836 cursor: pointer; 2837 transition: opacity 0.15s ease; 2838 } 2839 2840 .pvr-thankyou__retry:hover { 2841 opacity: 0.88; 2842 } 2843 2844 .pvr-thankyou__retry:disabled { 2845 opacity: 0.55; 2846 cursor: default; 2774 2847 } 2775 2848 -
pvr-media-reviews-for-woocommerce/tags/1.2.8/frontend/js/pvr-frontend.js
r3748498 r3760546 3982 3982 3983 3983 // 2. If review saved successfully, start uploading media 3984 var mediaIds = []; 3985 var failedUploads = 0; 3984 3986 if (commentId && window.PVREVUpload && typeof window.PVREVUpload.uploadPending === 'function' && window.PVREVUpload.hasPendingItems()) { 3985 3987 if (submitButton) { … … 3988 3990 3989 3991 // Associate with commentId 3990 var mediaIds = await window.PVREVUpload.uploadPending(commentId); 3992 var uploadResult = await window.PVREVUpload.uploadPending(commentId); 3993 // New contract: { mediaIds, failed, total }. Tolerate the 3994 // legacy plain array for safety. 3995 if (uploadResult && typeof uploadResult === 'object' && !Array.isArray(uploadResult)) { 3996 mediaIds = Array.isArray(uploadResult.mediaIds) ? uploadResult.mediaIds : []; 3997 failedUploads = parseInt(uploadResult.failed, 10) || 0; 3998 } else if (Array.isArray(uploadResult)) { 3999 mediaIds = uploadResult; 4000 } 3991 4001 3992 4002 // 3. Notify server that uploads are finished (so it can send emails) … … 4001 4011 4002 4012 if (window.PVREVUpload && typeof window.PVREVUpload.showThankYou === 'function') { 4003 window.PVREVUpload.showThankYou(); 4013 // The review itself IS submitted — never turn this into an 4014 // error screen. Partial upload failures are surfaced inside 4015 // the thank-you screen with a retry. 4016 window.PVREVUpload.showThankYou({ failed: failedUploads }); 4004 4017 } 4005 4018 … … 4038 4051 } 4039 4052 4040 window.setTimeout(closeThankYouModal, 5000); 4053 // The review itself IS submitted — never auto-close the modal 4054 // while some media failed: the user needs the retry. 4055 if (failedUploads === 0) { 4056 window.setTimeout(closeThankYouModal, 5000); 4057 } 4041 4058 } 4042 4059 } catch (error) { -
pvr-media-reviews-for-woocommerce/tags/1.2.8/frontend/js/pvr-media-upload.js
r3732836 r3760546 24 24 var isCancelled = false; 25 25 var watermarkImagePromise = null; 26 // Comment the current upload round belongs to (retry after partial 27 // failures re-runs the round against the same review). 28 var lastCommentId = 0; 29 var retryBusy = false; 26 30 27 31 function getText(key, fallback) { … … 479 483 } 480 484 481 function showThankYou( ) {485 function showThankYou(result) { 482 486 var form = getForm(); 483 487 if (!form) return; 484 488 form.classList.remove('pvr-form--uploading'); 485 489 form.classList.add('pvr-form--thankyou'); 490 var failed = 0; 491 if (result && typeof result === 'object') { 492 failed = parseInt(result.failed, 10) || 0; 493 } 494 renderThankYouNotice(failed); 495 } 496 497 function getThankYouRoot() { 498 return document.getElementById('pvr-thankyou'); 499 } 500 501 function removeThankYouNotice() { 502 var root = getThankYouRoot(); 503 var warn = root ? root.querySelector('.pvr-thankyou__warning') : null; 504 if (warn) { 505 warn.parentNode.removeChild(warn); 506 } 507 } 508 509 function hasUnuploadedItems() { 510 return state.items.some(function(item) { 511 return !item.uploaded; 512 }); 513 } 514 515 /** 516 * Partial-upload notice inside the thank-you screen: the review IS 517 * submitted, so this must never become an error screen — it only 518 * reports the failed files and offers a retry. 519 */ 520 function renderThankYouNotice(failed) { 521 var root = getThankYouRoot(); 522 if (!root) return; 523 removeThankYouNotice(); 524 if (!failed || failed <= 0) { 525 return; 526 } 527 528 var warning = document.createElement('div'); 529 warning.className = 'pvr-thankyou__warning'; 530 531 var text = document.createElement('p'); 532 text.className = 'pvr-thankyou__warning-text'; 533 text.textContent = getText( 534 'thankyou_media_failed', 535 'Your review has been submitted, but some media files failed to upload.' 536 ) + ' (' + failed + ')'; 537 warning.appendChild(text); 538 539 if (lastCommentId > 0 && hasUnuploadedItems()) { 540 var actions = document.createElement('div'); 541 actions.className = 'pvr-thankyou__warning-actions'; 542 543 var retry = document.createElement('button'); 544 retry.type = 'button'; 545 retry.className = 'pvr-btn pvr-thankyou__retry'; 546 retry.id = 'pvr-thankyou-retry'; 547 retry.textContent = getText('thankyou_retry_upload', 'Retry upload'); 548 retry.addEventListener('click', retryFailedUploads); 549 actions.appendChild(retry); 550 551 warning.appendChild(actions); 552 } 553 554 var closeBtn = document.getElementById('pvr-thankyou-close'); 555 if (closeBtn && closeBtn.parentNode === root) { 556 root.insertBefore(warning, closeBtn); 557 } else { 558 root.appendChild(warning); 559 } 560 } 561 562 function retryFailedUploads() { 563 if (retryBusy || !lastCommentId || !hasUnuploadedItems()) { 564 return; 565 } 566 retryBusy = true; 567 var retryBtn = document.getElementById('pvr-thankyou-retry'); 568 if (retryBtn) { 569 retryBtn.disabled = true; 570 retryBtn.classList.add('pvr-is-loading'); 571 } 572 573 uploadPendingItems(lastCommentId).then(function (result) { 574 var failed = (result && typeof result === 'object') ? (parseInt(result.failed, 10) || 0) : 0; 575 if (typeof window.pvrAjax === 'function') { 576 window.pvrAjax('pvrev_finish_uploads', { 577 comment_id: lastCommentId, 578 media_ids: state.mediaIds.join(','), 579 nonce: (window.PVREV && window.PVREV.nonces ? window.PVREV.nonces.submit_review : (window.PVREV.nonce || '')) 580 }).catch(function () {}); 581 } 582 if (failed === 0) { 583 removeThankYouNotice(); 584 if (window.pvrShowSuccess) { 585 window.pvrShowSuccess(getText('thankyou_media_complete', 'All media uploaded successfully.')); 586 } 587 } else { 588 renderThankYouNotice(failed); 589 } 590 }).catch(function () { 591 if (window.pvrShowError) { 592 window.pvrShowError(getText('upload_failed', 'Upload failed')); 593 } 594 }).then(function () { 595 retryBusy = false; 596 if (retryBtn) { 597 retryBtn.disabled = false; 598 retryBtn.classList.remove('pvr-is-loading'); 599 } 600 }); 486 601 } 487 602 … … 1035 1150 var total; 1036 1151 var completed = 0; 1152 var failed = 0; 1037 1153 1038 1154 isCancelled = false; 1155 lastCommentId = parseInt(commentId, 10) || 0; 1039 1156 1040 1157 photos = state.items.filter(function(item) { … … 1051 1168 if (!total) { 1052 1169 syncMediaIdsInput(); 1053 return state.mediaIds.slice();1170 return { mediaIds: state.mediaIds.slice(), failed: 0, total: 0 }; 1054 1171 } 1055 1172 … … 1091 1208 elements.progressText = savedText; 1092 1209 resetFormMode(); 1093 return state.mediaIds.slice();1210 return { mediaIds: state.mediaIds.slice(), failed: 0, total: 0 }; 1094 1211 } 1212 // Previously this failure was swallowed silently: the file 1213 // was skipped without any visible error and the flow still 1214 // ended on the success screen. 1215 failed += 1; 1095 1216 payloads.push(null); 1217 var badItem = queue[q]; 1218 var badLabel = (badItem && badItem.name) || getText('upload_file', 'file'); 1219 var badErr = (err && err.message) ? err.message : getText('upload_failed', 'Upload failed'); 1220 showError(badLabel + ': ' + badErr); 1096 1221 } 1097 1222 } … … 1102 1227 elements.progressText = savedText; 1103 1228 resetFormMode(); 1104 return state.mediaIds.slice();1229 return { mediaIds: state.mediaIds.slice(), failed: 0, total: 0 }; 1105 1230 } 1106 1231 … … 1170 1295 elements.progressText = savedText; 1171 1296 resetFormMode(); 1172 return state.mediaIds.slice();1297 return { mediaIds: state.mediaIds.slice(), failed: 0, total: 0 }; 1173 1298 } 1174 1299 if (!item.uploaded) { … … 1176 1301 } 1177 1302 setItemProgress(item, 0); 1303 failed += 1; 1178 1304 var fileLabel = item.name || getText('upload_file', 'file'); 1179 1305 var errMsg = (error && error.message) ? error.message : getText('upload_failed', 'Upload failed'); … … 1186 1312 if (isCancelled) { 1187 1313 resetFormMode(); 1188 return state.mediaIds.slice();1314 return { mediaIds: state.mediaIds.slice(), failed: 0, total: 0 }; 1189 1315 } 1190 1316 … … 1198 1324 hideProgress(); 1199 1325 } 1200 return state.mediaIds.slice();1326 return { mediaIds: state.mediaIds.slice(), failed: failed, total: total }; 1201 1327 } 1202 1328 … … 1447 1573 reset: function() { 1448 1574 resetState(); 1575 removeThankYouNotice(); 1576 lastCommentId = 0; 1449 1577 if (elements.preview) { 1450 1578 elements.preview.innerHTML = ''; … … 1469 1597 }, 1470 1598 1471 showThankYou: function( ) {1472 showThankYou( );1599 showThankYou: function(result) { 1600 showThankYou(result); 1473 1601 }, 1474 1602 -
pvr-media-reviews-for-woocommerce/tags/1.2.8/includes/class-pvr-hooks.php
r3748498 r3760546 1472 1472 'picker_no_results' => __( 'No products found', 'pvr-media-reviews-for-woocommerce' ), 1473 1473 'picker_select_product' => __( 'Please select a product first', 'pvr-media-reviews-for-woocommerce' ), 1474 'thankyou_media_failed' => __( 'Your review has been submitted, but some media files failed to upload. You can retry.', 'pvr-media-reviews-for-woocommerce' ), 1475 'thankyou_retry_upload' => __( 'Retry upload', 'pvr-media-reviews-for-woocommerce' ), 1476 'thankyou_media_complete' => __( 'All media uploaded successfully.', 'pvr-media-reviews-for-woocommerce' ), 1474 1477 ], 1475 1478 ]; -
pvr-media-reviews-for-woocommerce/tags/1.2.8/includes/class-pvr-media.php
r3750600 r3760546 459 459 // binding: a guest comment may receive media only from the 460 460 // address that submitted it, so sequential comment IDs from 461 // other visitors cannot be targeted. 461 // other visitors cannot be targeted. Same resolver as the 462 // submit side — REMOTE_ADDR alone breaks behind a reverse 463 // proxy / Cloudflare, where it is the proxy's address. 462 464 $comment_ip = (string) $comment->comment_author_IP; 463 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Same normalization wp_new_comment applies to REMOTE_ADDR. 464 $current_ip = preg_replace( '/[^0-9a-fA-F:., ]/', '', (string) wp_unslash( $_SERVER['REMOTE_ADDR'] ?? '' ) ); 465 $current_ip = PVREV_Hooks::get_client_ip(); 465 466 if ( '' === $comment_ip || ! hash_equals( $comment_ip, $current_ip ) ) { 466 467 $comment_id = 0; … … 513 514 } 514 515 516 // Variant paths come back from ensure_photo_variants() as paths 517 // relative to uploads; str_replace( basedir, baseurl, ... ) here 518 // was a no-op on them and esc_url() turned the bare relative path 519 // into "http://pvr-reviews/...". get_media_url() resolves both 520 // relative and absolute paths correctly. 515 521 $thumbnail_url = null; 516 522 if ( ! empty( $thumbnail_path ) ) { 517 $thumbnail_url = s tr_replace( trailingslashit( $upload['basedir'] ), trailingslashit( $upload['baseurl'] ),$thumbnail_path );523 $thumbnail_url = self::get_media_url( $thumbnail_path ); 518 524 } 519 525 $large_url = null; 520 526 if ( ! empty( $large_path ) ) { 521 $large_url = s tr_replace( trailingslashit( $upload['basedir'] ), trailingslashit( $upload['baseurl'] ),$large_path );527 $large_url = self::get_media_url( $large_path ); 522 528 } 523 529 $popup_url = null; 524 530 if ( ! empty( $popup_path ) ) { 525 $popup_url = s tr_replace( trailingslashit( $upload['basedir'] ), trailingslashit( $upload['baseurl'] ),$popup_path );531 $popup_url = self::get_media_url( $popup_path ); 526 532 } 527 533 -
pvr-media-reviews-for-woocommerce/tags/1.2.8/includes/class-pvr-review.php
r3737674 r3760546 327 327 'comment_author_email' => $author_email, 328 328 'comment_author_url' => '', 329 // wp_insert_comment() does not fill the IP (unlike 330 // wp_new_comment()); guest media attach in PVREV_Media 331 // compares this value with the upload request's IP. 332 'comment_author_IP' => PVREV_Hooks::get_client_ip(), 329 333 'comment_content' => $content, 330 334 'comment_type' => 'review', -
pvr-media-reviews-for-woocommerce/tags/1.2.8/languages/pvr-media-reviews-for-woocommerce-en_US.json
r3759180 r3760546 1394 1394 "Replies skipped (parent review missing)": "Replies skipped (parent review missing)", 1395 1395 "Reply queue limit reached — reply row skipped.": "Reply queue limit reached — reply row skipped.", 1396 "Review ID (source file)": "Review ID (source file)" 1396 "Review ID (source file)": "Review ID (source file)", 1397 "Your review has been submitted, but some media files failed to upload. You can retry.": "Your review has been submitted, but some media files failed to upload. You can retry.", 1398 "Retry upload": "Retry upload", 1399 "All media uploaded successfully.": "All media uploaded successfully." 1397 1400 } -
pvr-media-reviews-for-woocommerce/tags/1.2.8/languages/pvr-media-reviews-for-woocommerce-es_ES.json
r3759180 r3760546 1376 1376 "Replies skipped (parent review missing)": "Respuestas omitidas (falta la reseña principal)", 1377 1377 "Reply queue limit reached — reply row skipped.": "Se alcanzó el límite de la cola de respuestas — fila de respuesta omitida.", 1378 "Review ID (source file)": "ID de reseña (archivo de origen)" 1378 "Review ID (source file)": "ID de reseña (archivo de origen)", 1379 "Your review has been submitted, but some media files failed to upload. You can retry.": "Tu reseña se ha enviado, pero algunos archivos multimedia no se pudieron cargar. Puedes reintentarlo.", 1380 "Retry upload": "Reintentar subida", 1381 "All media uploaded successfully.": "Todos los archivos multimedia se subieron correctamente." 1379 1382 } -
pvr-media-reviews-for-woocommerce/tags/1.2.8/languages/pvr-media-reviews-for-woocommerce-ru_RU.json
r3759180 r3760546 1390 1390 "Replies skipped (parent review missing)": "Пропущено ответов (нет родительского отзыва)", 1391 1391 "Reply queue limit reached — reply row skipped.": "Достигнут лимит очереди ответов — строка ответа пропущена.", 1392 "Review ID (source file)": "ID отзыва (в файле-источнике)" 1392 "Review ID (source file)": "ID отзыва (в файле-источнике)", 1393 "Your review has been submitted, but some media files failed to upload. You can retry.": "Ваш отзыв отправлен, но часть медиафайлов не удалось загрузить. Можно повторить попытку.", 1394 "Retry upload": "Повторить загрузку", 1395 "All media uploaded successfully.": "Все медиафайлы успешно загружены." 1393 1396 } -
pvr-media-reviews-for-woocommerce/tags/1.2.8/photo-video-reviews-for-woocommerce.php
r3759180 r3760546 4 4 * Plugin URI: https://wordpress.org/plugins/pvr-media-reviews-for-woocommerce/ 5 5 * Description: Photo and video reviews for WooCommerce with client-side compression. 6 * Version: 1.2. 76 * Version: 1.2.8 7 7 * Author: Lebedgor 8 8 * Author URI: https://demo.pv-reviews.site/ … … 18 18 defined( 'ABSPATH' ) || exit; 19 19 20 define( 'PVREV_VERSION', '1.2. 7' );20 define( 'PVREV_VERSION', '1.2.8' ); 21 21 define( 'PVREV_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); 22 22 define( 'PVREV_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); -
pvr-media-reviews-for-woocommerce/tags/1.2.8/readme.txt
r3759180 r3760546 5 5 Tested up to: 7.0 6 6 Requires PHP: 7.1 7 Stable tag: 1.2. 77 Stable tag: 1.2.8 8 8 WC requires at least: 7.0 9 9 WC tested up to: 9.0 … … 175 175 == Changelog == 176 176 177 = 1.2.8 = 178 * Fix: photo and video uploads failed to attach to reviews — the review saved and the customer saw the success screen, but the admin found no media: wp_insert_comment() does not fill comment_author_IP, so the anti-enumeration guest attach check (added in 1.2.0) silently re-bound every upload to comment 0. The submit now stores the client IP (proxy/Cloudflare-aware) and the attach check resolves the request IP through the same resolver 179 * Improvement: failed media uploads no longer end on a silent success screen — the thank-you screen shows which files failed, offers a Retry upload that re-runs only the failed files against the same review, and the 5-second auto-close only runs when everything is uploaded; prepare/compression errors now surface a toast instead of skipping the file invisibly 180 * Fix: photo thumbnail/large webp URLs in the upload response were built as "http://pvr-reviews/..." when the variant path came back relative (esc_url on a bare path); URLs are now resolved through PVREV_Media::get_media_url() 181 * Fix: the Reply button in the review details modal did nothing on pages rendered without the reviews list (media gallery block, hub page, My Reviews, standalone form) — the shared reply form modal is now printed once per request for every surface 182 * New: the review details modal shows a product mini-card (thumbnail + name) at the top of its sidebar, above the author — desktop aside and mobile slide both follow review flip-through, so mixed-product surfaces (media gallery, all-media strip, hub) always show which product the review belongs to 183 * Fix: the review form could stretch past the viewport on mobile — long product names no longer force min-content width (nowrap removed), and text inputs keep the form styling (rounded corners, border color) on themes that load their own input styles later 184 * Updated plugin banners 185 177 186 = 1.2.7 = 178 187 * New: import preset for CusRev (Customer Reviews for WooCommerce) — a file format selector in the import wizard recognizes their export columns automatically (review_title, review_content, review_score, display_name, media), so a CusRev CSV maps without any manual column setup -
pvr-media-reviews-for-woocommerce/trunk/frontend/css/pvr-frontend.css
r3748498 r3760546 317 317 align-items: center; 318 318 justify-content: center; 319 padding: 20px;319 padding: 5px; 320 320 z-index: 100001; 321 321 } … … 1669 1669 overflow: hidden; 1670 1670 text-overflow: ellipsis; 1671 white-space: nowrap; 1671 /* nowrap stretched the identity row past the viewport on mobile — 1672 long product names must wrap instead of forcing min-content width. */ 1673 overflow-wrap: anywhere; 1672 1674 } 1673 1675 … … 2160 2162 .pvr-form input{ 2161 2163 outline: 0 !important; 2164 } 2165 2166 /* Class-scoped text inputs: themes (Astra et al.) style input[type=...] 2167 with the same specificity as the shared .pvr-form input rule above and 2168 win by loading later — that is how sharp corners and theme borders 2169 replaced the form look on client sites. Same declarations as the 2170 textarea so the fields match. */ 2171 .pvr-form .pvr-form__input, 2172 .pvr-reply-form .pvr-form__input { 2173 border: 1px solid rgba(203, 213, 225, 0.9); 2174 border-radius: 16px; 2175 background: #ffffff; 2176 color: var(--pvr-text-color); 2177 padding: 12px 14px; 2178 font-family: inherit; 2179 font-size: 14px; 2180 line-height: 1.45; 2181 box-shadow: none; 2182 } 2183 2184 .pvr-form .pvr-form__input:focus, 2185 .pvr-reply-form .pvr-form__input:focus { 2186 border-color: var(--pvr-accent-color); 2162 2187 } 2163 2188 … … 2772 2797 margin: 0; 2773 2798 line-height: 1.5; 2799 } 2800 2801 /* Partial upload failure notice: the review IS submitted, so this stays 2802 inside the thank-you screen (never an error screen) and offers a retry. */ 2803 .pvr-thankyou__warning { 2804 width: 100%; 2805 max-width: 340px; 2806 display: flex; 2807 flex-direction: column; 2808 gap: 10px; 2809 padding: 12px 16px; 2810 border: 1px solid rgba(245, 158, 11, 0.45); 2811 background: rgba(245, 158, 11, 0.08); 2812 border-radius: 12px; 2813 box-sizing: border-box; 2814 } 2815 2816 .pvr-thankyou__warning-text { 2817 margin: 0; 2818 font-size: 13px; 2819 line-height: 1.5; 2820 color: var(--pvr-text-color, #1f2937); 2821 } 2822 2823 .pvr-thankyou__warning-actions { 2824 display: flex; 2825 justify-content: center; 2826 } 2827 2828 .pvr-thankyou__retry { 2829 padding: 8px 22px; 2830 border-radius: 999px; 2831 border: none; 2832 background: var(--pvr-accent-color, #2563eb); 2833 color: #fff; 2834 font-size: 13px; 2835 font-weight: 600; 2836 cursor: pointer; 2837 transition: opacity 0.15s ease; 2838 } 2839 2840 .pvr-thankyou__retry:hover { 2841 opacity: 0.88; 2842 } 2843 2844 .pvr-thankyou__retry:disabled { 2845 opacity: 0.55; 2846 cursor: default; 2774 2847 } 2775 2848 -
pvr-media-reviews-for-woocommerce/trunk/frontend/js/pvr-frontend.js
r3748498 r3760546 3982 3982 3983 3983 // 2. If review saved successfully, start uploading media 3984 var mediaIds = []; 3985 var failedUploads = 0; 3984 3986 if (commentId && window.PVREVUpload && typeof window.PVREVUpload.uploadPending === 'function' && window.PVREVUpload.hasPendingItems()) { 3985 3987 if (submitButton) { … … 3988 3990 3989 3991 // Associate with commentId 3990 var mediaIds = await window.PVREVUpload.uploadPending(commentId); 3992 var uploadResult = await window.PVREVUpload.uploadPending(commentId); 3993 // New contract: { mediaIds, failed, total }. Tolerate the 3994 // legacy plain array for safety. 3995 if (uploadResult && typeof uploadResult === 'object' && !Array.isArray(uploadResult)) { 3996 mediaIds = Array.isArray(uploadResult.mediaIds) ? uploadResult.mediaIds : []; 3997 failedUploads = parseInt(uploadResult.failed, 10) || 0; 3998 } else if (Array.isArray(uploadResult)) { 3999 mediaIds = uploadResult; 4000 } 3991 4001 3992 4002 // 3. Notify server that uploads are finished (so it can send emails) … … 4001 4011 4002 4012 if (window.PVREVUpload && typeof window.PVREVUpload.showThankYou === 'function') { 4003 window.PVREVUpload.showThankYou(); 4013 // The review itself IS submitted — never turn this into an 4014 // error screen. Partial upload failures are surfaced inside 4015 // the thank-you screen with a retry. 4016 window.PVREVUpload.showThankYou({ failed: failedUploads }); 4004 4017 } 4005 4018 … … 4038 4051 } 4039 4052 4040 window.setTimeout(closeThankYouModal, 5000); 4053 // The review itself IS submitted — never auto-close the modal 4054 // while some media failed: the user needs the retry. 4055 if (failedUploads === 0) { 4056 window.setTimeout(closeThankYouModal, 5000); 4057 } 4041 4058 } 4042 4059 } catch (error) { -
pvr-media-reviews-for-woocommerce/trunk/frontend/js/pvr-media-upload.js
r3732836 r3760546 24 24 var isCancelled = false; 25 25 var watermarkImagePromise = null; 26 // Comment the current upload round belongs to (retry after partial 27 // failures re-runs the round against the same review). 28 var lastCommentId = 0; 29 var retryBusy = false; 26 30 27 31 function getText(key, fallback) { … … 479 483 } 480 484 481 function showThankYou( ) {485 function showThankYou(result) { 482 486 var form = getForm(); 483 487 if (!form) return; 484 488 form.classList.remove('pvr-form--uploading'); 485 489 form.classList.add('pvr-form--thankyou'); 490 var failed = 0; 491 if (result && typeof result === 'object') { 492 failed = parseInt(result.failed, 10) || 0; 493 } 494 renderThankYouNotice(failed); 495 } 496 497 function getThankYouRoot() { 498 return document.getElementById('pvr-thankyou'); 499 } 500 501 function removeThankYouNotice() { 502 var root = getThankYouRoot(); 503 var warn = root ? root.querySelector('.pvr-thankyou__warning') : null; 504 if (warn) { 505 warn.parentNode.removeChild(warn); 506 } 507 } 508 509 function hasUnuploadedItems() { 510 return state.items.some(function(item) { 511 return !item.uploaded; 512 }); 513 } 514 515 /** 516 * Partial-upload notice inside the thank-you screen: the review IS 517 * submitted, so this must never become an error screen — it only 518 * reports the failed files and offers a retry. 519 */ 520 function renderThankYouNotice(failed) { 521 var root = getThankYouRoot(); 522 if (!root) return; 523 removeThankYouNotice(); 524 if (!failed || failed <= 0) { 525 return; 526 } 527 528 var warning = document.createElement('div'); 529 warning.className = 'pvr-thankyou__warning'; 530 531 var text = document.createElement('p'); 532 text.className = 'pvr-thankyou__warning-text'; 533 text.textContent = getText( 534 'thankyou_media_failed', 535 'Your review has been submitted, but some media files failed to upload.' 536 ) + ' (' + failed + ')'; 537 warning.appendChild(text); 538 539 if (lastCommentId > 0 && hasUnuploadedItems()) { 540 var actions = document.createElement('div'); 541 actions.className = 'pvr-thankyou__warning-actions'; 542 543 var retry = document.createElement('button'); 544 retry.type = 'button'; 545 retry.className = 'pvr-btn pvr-thankyou__retry'; 546 retry.id = 'pvr-thankyou-retry'; 547 retry.textContent = getText('thankyou_retry_upload', 'Retry upload'); 548 retry.addEventListener('click', retryFailedUploads); 549 actions.appendChild(retry); 550 551 warning.appendChild(actions); 552 } 553 554 var closeBtn = document.getElementById('pvr-thankyou-close'); 555 if (closeBtn && closeBtn.parentNode === root) { 556 root.insertBefore(warning, closeBtn); 557 } else { 558 root.appendChild(warning); 559 } 560 } 561 562 function retryFailedUploads() { 563 if (retryBusy || !lastCommentId || !hasUnuploadedItems()) { 564 return; 565 } 566 retryBusy = true; 567 var retryBtn = document.getElementById('pvr-thankyou-retry'); 568 if (retryBtn) { 569 retryBtn.disabled = true; 570 retryBtn.classList.add('pvr-is-loading'); 571 } 572 573 uploadPendingItems(lastCommentId).then(function (result) { 574 var failed = (result && typeof result === 'object') ? (parseInt(result.failed, 10) || 0) : 0; 575 if (typeof window.pvrAjax === 'function') { 576 window.pvrAjax('pvrev_finish_uploads', { 577 comment_id: lastCommentId, 578 media_ids: state.mediaIds.join(','), 579 nonce: (window.PVREV && window.PVREV.nonces ? window.PVREV.nonces.submit_review : (window.PVREV.nonce || '')) 580 }).catch(function () {}); 581 } 582 if (failed === 0) { 583 removeThankYouNotice(); 584 if (window.pvrShowSuccess) { 585 window.pvrShowSuccess(getText('thankyou_media_complete', 'All media uploaded successfully.')); 586 } 587 } else { 588 renderThankYouNotice(failed); 589 } 590 }).catch(function () { 591 if (window.pvrShowError) { 592 window.pvrShowError(getText('upload_failed', 'Upload failed')); 593 } 594 }).then(function () { 595 retryBusy = false; 596 if (retryBtn) { 597 retryBtn.disabled = false; 598 retryBtn.classList.remove('pvr-is-loading'); 599 } 600 }); 486 601 } 487 602 … … 1035 1150 var total; 1036 1151 var completed = 0; 1152 var failed = 0; 1037 1153 1038 1154 isCancelled = false; 1155 lastCommentId = parseInt(commentId, 10) || 0; 1039 1156 1040 1157 photos = state.items.filter(function(item) { … … 1051 1168 if (!total) { 1052 1169 syncMediaIdsInput(); 1053 return state.mediaIds.slice();1170 return { mediaIds: state.mediaIds.slice(), failed: 0, total: 0 }; 1054 1171 } 1055 1172 … … 1091 1208 elements.progressText = savedText; 1092 1209 resetFormMode(); 1093 return state.mediaIds.slice();1210 return { mediaIds: state.mediaIds.slice(), failed: 0, total: 0 }; 1094 1211 } 1212 // Previously this failure was swallowed silently: the file 1213 // was skipped without any visible error and the flow still 1214 // ended on the success screen. 1215 failed += 1; 1095 1216 payloads.push(null); 1217 var badItem = queue[q]; 1218 var badLabel = (badItem && badItem.name) || getText('upload_file', 'file'); 1219 var badErr = (err && err.message) ? err.message : getText('upload_failed', 'Upload failed'); 1220 showError(badLabel + ': ' + badErr); 1096 1221 } 1097 1222 } … … 1102 1227 elements.progressText = savedText; 1103 1228 resetFormMode(); 1104 return state.mediaIds.slice();1229 return { mediaIds: state.mediaIds.slice(), failed: 0, total: 0 }; 1105 1230 } 1106 1231 … … 1170 1295 elements.progressText = savedText; 1171 1296 resetFormMode(); 1172 return state.mediaIds.slice();1297 return { mediaIds: state.mediaIds.slice(), failed: 0, total: 0 }; 1173 1298 } 1174 1299 if (!item.uploaded) { … … 1176 1301 } 1177 1302 setItemProgress(item, 0); 1303 failed += 1; 1178 1304 var fileLabel = item.name || getText('upload_file', 'file'); 1179 1305 var errMsg = (error && error.message) ? error.message : getText('upload_failed', 'Upload failed'); … … 1186 1312 if (isCancelled) { 1187 1313 resetFormMode(); 1188 return state.mediaIds.slice();1314 return { mediaIds: state.mediaIds.slice(), failed: 0, total: 0 }; 1189 1315 } 1190 1316 … … 1198 1324 hideProgress(); 1199 1325 } 1200 return state.mediaIds.slice();1326 return { mediaIds: state.mediaIds.slice(), failed: failed, total: total }; 1201 1327 } 1202 1328 … … 1447 1573 reset: function() { 1448 1574 resetState(); 1575 removeThankYouNotice(); 1576 lastCommentId = 0; 1449 1577 if (elements.preview) { 1450 1578 elements.preview.innerHTML = ''; … … 1469 1597 }, 1470 1598 1471 showThankYou: function( ) {1472 showThankYou( );1599 showThankYou: function(result) { 1600 showThankYou(result); 1473 1601 }, 1474 1602 -
pvr-media-reviews-for-woocommerce/trunk/includes/class-pvr-hooks.php
r3748498 r3760546 1472 1472 'picker_no_results' => __( 'No products found', 'pvr-media-reviews-for-woocommerce' ), 1473 1473 'picker_select_product' => __( 'Please select a product first', 'pvr-media-reviews-for-woocommerce' ), 1474 'thankyou_media_failed' => __( 'Your review has been submitted, but some media files failed to upload. You can retry.', 'pvr-media-reviews-for-woocommerce' ), 1475 'thankyou_retry_upload' => __( 'Retry upload', 'pvr-media-reviews-for-woocommerce' ), 1476 'thankyou_media_complete' => __( 'All media uploaded successfully.', 'pvr-media-reviews-for-woocommerce' ), 1474 1477 ], 1475 1478 ]; -
pvr-media-reviews-for-woocommerce/trunk/includes/class-pvr-media.php
r3750600 r3760546 459 459 // binding: a guest comment may receive media only from the 460 460 // address that submitted it, so sequential comment IDs from 461 // other visitors cannot be targeted. 461 // other visitors cannot be targeted. Same resolver as the 462 // submit side — REMOTE_ADDR alone breaks behind a reverse 463 // proxy / Cloudflare, where it is the proxy's address. 462 464 $comment_ip = (string) $comment->comment_author_IP; 463 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Same normalization wp_new_comment applies to REMOTE_ADDR. 464 $current_ip = preg_replace( '/[^0-9a-fA-F:., ]/', '', (string) wp_unslash( $_SERVER['REMOTE_ADDR'] ?? '' ) ); 465 $current_ip = PVREV_Hooks::get_client_ip(); 465 466 if ( '' === $comment_ip || ! hash_equals( $comment_ip, $current_ip ) ) { 466 467 $comment_id = 0; … … 513 514 } 514 515 516 // Variant paths come back from ensure_photo_variants() as paths 517 // relative to uploads; str_replace( basedir, baseurl, ... ) here 518 // was a no-op on them and esc_url() turned the bare relative path 519 // into "http://pvr-reviews/...". get_media_url() resolves both 520 // relative and absolute paths correctly. 515 521 $thumbnail_url = null; 516 522 if ( ! empty( $thumbnail_path ) ) { 517 $thumbnail_url = s tr_replace( trailingslashit( $upload['basedir'] ), trailingslashit( $upload['baseurl'] ),$thumbnail_path );523 $thumbnail_url = self::get_media_url( $thumbnail_path ); 518 524 } 519 525 $large_url = null; 520 526 if ( ! empty( $large_path ) ) { 521 $large_url = s tr_replace( trailingslashit( $upload['basedir'] ), trailingslashit( $upload['baseurl'] ),$large_path );527 $large_url = self::get_media_url( $large_path ); 522 528 } 523 529 $popup_url = null; 524 530 if ( ! empty( $popup_path ) ) { 525 $popup_url = s tr_replace( trailingslashit( $upload['basedir'] ), trailingslashit( $upload['baseurl'] ),$popup_path );531 $popup_url = self::get_media_url( $popup_path ); 526 532 } 527 533 -
pvr-media-reviews-for-woocommerce/trunk/includes/class-pvr-review.php
r3737674 r3760546 327 327 'comment_author_email' => $author_email, 328 328 'comment_author_url' => '', 329 // wp_insert_comment() does not fill the IP (unlike 330 // wp_new_comment()); guest media attach in PVREV_Media 331 // compares this value with the upload request's IP. 332 'comment_author_IP' => PVREV_Hooks::get_client_ip(), 329 333 'comment_content' => $content, 330 334 'comment_type' => 'review', -
pvr-media-reviews-for-woocommerce/trunk/languages/pvr-media-reviews-for-woocommerce-en_US.json
r3759180 r3760546 1394 1394 "Replies skipped (parent review missing)": "Replies skipped (parent review missing)", 1395 1395 "Reply queue limit reached — reply row skipped.": "Reply queue limit reached — reply row skipped.", 1396 "Review ID (source file)": "Review ID (source file)" 1396 "Review ID (source file)": "Review ID (source file)", 1397 "Your review has been submitted, but some media files failed to upload. You can retry.": "Your review has been submitted, but some media files failed to upload. You can retry.", 1398 "Retry upload": "Retry upload", 1399 "All media uploaded successfully.": "All media uploaded successfully." 1397 1400 } -
pvr-media-reviews-for-woocommerce/trunk/languages/pvr-media-reviews-for-woocommerce-es_ES.json
r3759180 r3760546 1376 1376 "Replies skipped (parent review missing)": "Respuestas omitidas (falta la reseña principal)", 1377 1377 "Reply queue limit reached — reply row skipped.": "Se alcanzó el límite de la cola de respuestas — fila de respuesta omitida.", 1378 "Review ID (source file)": "ID de reseña (archivo de origen)" 1378 "Review ID (source file)": "ID de reseña (archivo de origen)", 1379 "Your review has been submitted, but some media files failed to upload. You can retry.": "Tu reseña se ha enviado, pero algunos archivos multimedia no se pudieron cargar. Puedes reintentarlo.", 1380 "Retry upload": "Reintentar subida", 1381 "All media uploaded successfully.": "Todos los archivos multimedia se subieron correctamente." 1379 1382 } -
pvr-media-reviews-for-woocommerce/trunk/languages/pvr-media-reviews-for-woocommerce-ru_RU.json
r3759180 r3760546 1390 1390 "Replies skipped (parent review missing)": "Пропущено ответов (нет родительского отзыва)", 1391 1391 "Reply queue limit reached — reply row skipped.": "Достигнут лимит очереди ответов — строка ответа пропущена.", 1392 "Review ID (source file)": "ID отзыва (в файле-источнике)" 1392 "Review ID (source file)": "ID отзыва (в файле-источнике)", 1393 "Your review has been submitted, but some media files failed to upload. You can retry.": "Ваш отзыв отправлен, но часть медиафайлов не удалось загрузить. Можно повторить попытку.", 1394 "Retry upload": "Повторить загрузку", 1395 "All media uploaded successfully.": "Все медиафайлы успешно загружены." 1393 1396 } -
pvr-media-reviews-for-woocommerce/trunk/photo-video-reviews-for-woocommerce.php
r3759180 r3760546 4 4 * Plugin URI: https://wordpress.org/plugins/pvr-media-reviews-for-woocommerce/ 5 5 * Description: Photo and video reviews for WooCommerce with client-side compression. 6 * Version: 1.2. 76 * Version: 1.2.8 7 7 * Author: Lebedgor 8 8 * Author URI: https://demo.pv-reviews.site/ … … 18 18 defined( 'ABSPATH' ) || exit; 19 19 20 define( 'PVREV_VERSION', '1.2. 7' );20 define( 'PVREV_VERSION', '1.2.8' ); 21 21 define( 'PVREV_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); 22 22 define( 'PVREV_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); -
pvr-media-reviews-for-woocommerce/trunk/readme.txt
r3759180 r3760546 5 5 Tested up to: 7.0 6 6 Requires PHP: 7.1 7 Stable tag: 1.2. 77 Stable tag: 1.2.8 8 8 WC requires at least: 7.0 9 9 WC tested up to: 9.0 … … 175 175 == Changelog == 176 176 177 = 1.2.8 = 178 * Fix: photo and video uploads failed to attach to reviews — the review saved and the customer saw the success screen, but the admin found no media: wp_insert_comment() does not fill comment_author_IP, so the anti-enumeration guest attach check (added in 1.2.0) silently re-bound every upload to comment 0. The submit now stores the client IP (proxy/Cloudflare-aware) and the attach check resolves the request IP through the same resolver 179 * Improvement: failed media uploads no longer end on a silent success screen — the thank-you screen shows which files failed, offers a Retry upload that re-runs only the failed files against the same review, and the 5-second auto-close only runs when everything is uploaded; prepare/compression errors now surface a toast instead of skipping the file invisibly 180 * Fix: photo thumbnail/large webp URLs in the upload response were built as "http://pvr-reviews/..." when the variant path came back relative (esc_url on a bare path); URLs are now resolved through PVREV_Media::get_media_url() 181 * Fix: the Reply button in the review details modal did nothing on pages rendered without the reviews list (media gallery block, hub page, My Reviews, standalone form) — the shared reply form modal is now printed once per request for every surface 182 * New: the review details modal shows a product mini-card (thumbnail + name) at the top of its sidebar, above the author — desktop aside and mobile slide both follow review flip-through, so mixed-product surfaces (media gallery, all-media strip, hub) always show which product the review belongs to 183 * Fix: the review form could stretch past the viewport on mobile — long product names no longer force min-content width (nowrap removed), and text inputs keep the form styling (rounded corners, border color) on themes that load their own input styles later 184 * Updated plugin banners 185 177 186 = 1.2.7 = 178 187 * New: import preset for CusRev (Customer Reviews for WooCommerce) — a file format selector in the import wizard recognizes their export columns automatically (review_title, review_content, review_score, display_name, media), so a CusRev CSV maps without any manual column setup
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)