Changeset 3435486
- Timestamp:
- 01/08/2026 09:20:09 PM (6 weeks ago)
- Location:
- products-showcase/trunk
- Files:
-
- 1 added
- 6 edited
-
README.md (modified) (3 diffs)
-
assets/admin/admin.css (modified) (2 diffs)
-
assets/admin/admin.js (modified) (1 diff)
-
includes/class-admin-settings.php (modified) (5 diffs)
-
includes/class-shopify-oauth.php (added)
-
products-showcase.php (modified) (4 diffs)
-
readme.txt (modified) (10 diffs)
Legend:
- Unmodified
- Added
- Removed
-
products-showcase/trunk/README.md
r3408553 r3435486 3 3 A powerful WordPress plugin that displays Shopify products and collections in beautiful, responsive carousels using **native Gutenberg blocks**. 4 4 5 5  6 6  7 7  … … 49 49 **Data Transmitted to Shopify**: 50 50 1. **Shopify Store URL** - Your store's domain (e.g., `your-store.myshopify.com`) configured in plugin settings 51 2. **Admin API Access Token** - Your Shopify Admin API authentication token (configured in plugin settings) 52 3. **GraphQL Queries** - Specific queries requesting product and collection data 51 2. **OAuth Credentials** - Client ID and Client Secret are used during the one-time OAuth authorization flow 52 3. **Admin API Access Token** - Obtained automatically via OAuth and used for all subsequent API requests 53 4. **GraphQL Queries** - Specific queries requesting product and collection data 53 54 54 55 **When Data is Transmitted**: … … 135 136 ## ⚙️ Configuration 136 137 137 ### 1. Get Shopify API Credentials138 ### 1. Create a Shopify Custom App 138 139 139 140 1. Log in to your **Shopify Admin** 140 141 2. Navigate to **Settings → Apps and sales channels** 141 142 3. Click **"Develop apps"** 142 4. **Create a new app** or select existing 143 5. Configure **Admin API scopes**: 143 4. Click **"Create an app"** and give it a name (e.g., "WordPress Integration") 144 145 ### 2. Configure API Access 146 147 1. In your app, go to the **"Configuration"** tab 148 2. Under **"Admin API integration"**, click **"Configure"** 149 3. Enable the following scope: 144 150 - ✅ `read_products` (required) 145 6. **Install the app** to your store 146 7. Copy the **Admin API access token** 147 148 ### 2. Configure Plugin Settings 149 150 1. Go to **Settings → Shopify Products** in WordPress admin 151 4. Click **"Save"** 152 5. **Important**: Under **"Allowed redirection URL(s)"**, add the Redirect URL shown in your WordPress plugin settings 153 154 ### 3. Get Your Credentials 155 156 1. Go to the **"API credentials"** tab in your Shopify app 157 2. Copy the **Client ID** 158 3. Copy the **Client secret** 159 160 ### 4. Connect via OAuth (Easy Setup!) 161 162 1. Go to **Shopify Products** in WordPress admin 151 163 2. Enter your **Shopify Store URL** (e.g., `your-store.myshopify.com`) 152 3. Paste your **Admin API Access Token** 153 4. Set **Cache Duration** (default: 1 hour) 154 5. Click **Save Settings** 155 156 The plugin will test the connection and show you a success message if configured correctly. 164 3. Paste your **Client ID** 165 4. Paste your **Client Secret** 166 5. Click **"Connect to Shopify"** 167 6. You'll be redirected to Shopify to authorize the connection 168 7. After authorizing, you'll be redirected back to WordPress - done! ✅ 169 170 The plugin automatically: 171 - Obtains the access token via secure OAuth 172 - Detects the latest supported Shopify API version 173 - Tests the connection and displays your store name 157 174 158 175 ## 📖 Usage -
products-showcase/trunk/assets/admin/admin.css
r3408553 r3435486 344 344 .prodshow-connection-status-card.error .prodshow-status-value { 345 345 color: #dc3545; 346 } 347 348 .prodshow-auto-detected { 349 font-size: 0.85em; 350 color: #28a745; 351 font-weight: normal; 352 } 353 354 .prodshow-fallback-version { 355 font-size: 0.85em; 356 color: #dc3545; 357 font-weight: normal; 358 } 359 360 .prodshow-api-version-display { 361 display: flex; 362 align-items: center; 363 gap: 8px; 364 flex-wrap: wrap; 365 } 366 367 #prodshow-refresh-api-version { 368 padding: 2px 6px; 369 min-height: auto; 370 line-height: 1; 371 } 372 373 #prodshow-refresh-api-version .dashicons { 374 font-size: 14px; 375 width: 14px; 376 height: 14px; 377 } 378 379 .prodshow-spin { 380 animation: prodshow-spin 1s linear infinite; 381 } 382 383 @keyframes prodshow-spin { 384 from { transform: rotate(0deg); } 385 to { transform: rotate(360deg); } 346 386 } 347 387 … … 937 977 display: none; 938 978 } 979 980 /* OAuth Section Styles */ 981 #prodshow-oauth-section .required { 982 color: #dc3545; 983 font-weight: bold; 984 } 985 986 .prodshow-oauth-connected-info { 987 display: flex; 988 align-items: center; 989 justify-content: space-between; 990 padding: 20px; 991 background: #f0f9f0; 992 border-top: 1px solid #e5e5e5; 993 } 994 995 .prodshow-oauth-status { 996 display: flex; 997 align-items: center; 998 gap: 8px; 999 margin: 0; 1000 font-size: 1rem; 1001 font-weight: 600; 1002 color: #28a745; 1003 } 1004 1005 .prodshow-oauth-status .dashicons { 1006 font-size: 24px; 1007 width: 24px; 1008 height: 24px; 1009 } 1010 1011 .prodshow-redirect-url-box { 1012 display: flex; 1013 align-items: center; 1014 gap: 8px; 1015 background: #f9f9f9; 1016 padding: 8px 12px; 1017 border: 1px solid #ddd; 1018 border-radius: 4px; 1019 max-width: 100%; 1020 overflow-x: auto; 1021 } 1022 1023 .prodshow-redirect-url-box code { 1024 background: transparent; 1025 padding: 0; 1026 font-size: 13px; 1027 word-break: break-all; 1028 flex: 1; 1029 } 1030 1031 #prodshow-copy-redirect-url { 1032 flex-shrink: 0; 1033 padding: 4px 8px; 1034 min-height: auto; 1035 line-height: 1; 1036 } 1037 1038 #prodshow-copy-redirect-url .dashicons { 1039 font-size: 16px; 1040 width: 16px; 1041 height: 16px; 1042 } 1043 1044 #prodshow-copy-redirect-url.copied { 1045 background: #28a745; 1046 border-color: #28a745; 1047 color: #fff; 1048 } 1049 1050 .prodshow-oauth-actions { 1051 display: flex; 1052 align-items: center; 1053 gap: 12px; 1054 padding: 24px; 1055 background: #f9f9f9; 1056 border-top: 1px solid #e5e5e5; 1057 } 1058 1059 .prodshow-connect-button { 1060 display: inline-flex !important; 1061 align-items: center; 1062 gap: 10px; 1063 padding: 14px 28px !important; 1064 font-size: 15px !important; 1065 background: #95BF46 !important; 1066 border-color: #95BF46 !important; 1067 } 1068 1069 .prodshow-connect-button::before { 1070 content: none !important; 1071 } 1072 1073 .prodshow-connect-button:hover, 1074 .prodshow-connect-button:focus { 1075 background: #7aa93a !important; 1076 border-color: #7aa93a !important; 1077 } 1078 1079 .prodshow-connect-button:disabled { 1080 opacity: 0.7; 1081 cursor: not-allowed; 1082 } 1083 1084 .prodshow-connect-button svg { 1085 flex-shrink: 0; 1086 } 1087 1088 .prodshow-oauth-spinner { 1089 float: none !important; 1090 margin: 0 !important; 1091 } 1092 1093 /* Responsive adjustments for OAuth */ 1094 @media screen and (max-width: 782px) { 1095 .prodshow-oauth-connected-info { 1096 flex-direction: column; 1097 gap: 16px; 1098 text-align: center; 1099 } 1100 1101 .prodshow-redirect-url-box { 1102 flex-direction: column; 1103 align-items: stretch; 1104 } 1105 1106 .prodshow-redirect-url-box code { 1107 text-align: center; 1108 } 1109 1110 #prodshow-copy-redirect-url { 1111 width: 100%; 1112 } 1113 1114 .prodshow-oauth-actions { 1115 flex-direction: column; 1116 } 1117 1118 .prodshow-connect-button { 1119 width: 100%; 1120 justify-content: center; 1121 } 1122 } -
products-showcase/trunk/assets/admin/admin.js
r3408553 r3435486 285 285 }); 286 286 287 /** 288 * OAuth Connect to Shopify 289 */ 290 $('#prodshow-connect-btn').on('click', function(e) { 291 e.preventDefault(); 292 293 var $btn = $(this); 294 var $spinner = $('.prodshow-oauth-spinner'); 295 var nonce = $('#prodshow-oauth-nonce').val(); 296 297 // Get form values 298 var shopUrl = $('#prodshow_shopify_url').val().trim(); 299 var clientId = $('#prodshow_shopify_client_id').val().trim(); 300 var clientSecret = $('#prodshow_shopify_client_secret').val().trim(); 301 302 // Validate fields 303 if (!shopUrl) { 304 alert('Please enter your Shopify store URL.'); 305 $('#prodshow_shopify_url').focus(); 306 return; 307 } 308 309 if (!clientId) { 310 alert('Please enter your Client ID.'); 311 $('#prodshow_shopify_client_id').focus(); 312 return; 313 } 314 315 if (!clientSecret) { 316 alert('Please enter your Client Secret.'); 317 $('#prodshow_shopify_client_secret').focus(); 318 return; 319 } 320 321 // Validate shop URL format 322 if (!shopUrl.match(/^[a-zA-Z0-9][a-zA-Z0-9\-]*\.myshopify\.com$/)) { 323 alert('Please enter a valid Shopify store URL (e.g., your-store.myshopify.com)'); 324 $('#prodshow_shopify_url').focus(); 325 return; 326 } 327 328 // Show loading state 329 $btn.prop('disabled', true); 330 $spinner.addClass('is-active'); 331 332 // Make AJAX request to initiate OAuth 333 $.ajax({ 334 url: ajaxurl, 335 type: 'POST', 336 data: { 337 action: 'prodshow_initiate_oauth', 338 nonce: nonce, 339 shop_url: shopUrl, 340 client_id: clientId, 341 client_secret: clientSecret 342 }, 343 success: function(response) { 344 if (response.success && response.data.redirect_url) { 345 // Redirect to Shopify for authorization 346 window.location.href = response.data.redirect_url; 347 } else { 348 alert(response.data.message || 'Failed to initiate connection. Please try again.'); 349 $btn.prop('disabled', false); 350 $spinner.removeClass('is-active'); 351 } 352 }, 353 error: function(xhr, status, error) { 354 console.error('OAuth Error:', error); 355 alert('Connection error. Please try again.'); 356 $btn.prop('disabled', false); 357 $spinner.removeClass('is-active'); 358 } 359 }); 360 }); 361 362 /** 363 * Disconnect from Shopify 364 */ 365 $('#prodshow-disconnect-btn').on('click', function(e) { 366 e.preventDefault(); 367 368 if (!confirm('Are you sure you want to disconnect from Shopify? You will need to reconnect to continue using the plugin.')) { 369 return; 370 } 371 372 var $btn = $(this); 373 var nonce = $('#prodshow-oauth-nonce').val() || $('input[name="_wpnonce"]').val(); 374 375 // If no nonce found, create one from the hidden field 376 if (!nonce) { 377 // Try to get it from the page 378 nonce = $('#prodshow-clear-cache-nonce').val(); 379 } 380 381 $btn.prop('disabled', true).text('Disconnecting...'); 382 383 $.ajax({ 384 url: ajaxurl, 385 type: 'POST', 386 data: { 387 action: 'prodshow_disconnect_shopify', 388 nonce: nonce 389 }, 390 success: function(response) { 391 if (response.success) { 392 // Reload the page to show disconnected state 393 window.location.reload(); 394 } else { 395 alert(response.data.message || 'Failed to disconnect. Please try again.'); 396 $btn.prop('disabled', false).text('Disconnect'); 397 } 398 }, 399 error: function() { 400 alert('Connection error. Please try again.'); 401 $btn.prop('disabled', false).text('Disconnect'); 402 } 403 }); 404 }); 405 406 /** 407 * Copy Redirect URL to clipboard 408 */ 409 $('#prodshow-copy-redirect-url').on('click', function(e) { 410 e.preventDefault(); 411 412 var $btn = $(this); 413 var redirectUrl = $('#prodshow-redirect-url').text(); 414 415 // Copy to clipboard 416 if (navigator.clipboard && navigator.clipboard.writeText) { 417 navigator.clipboard.writeText(redirectUrl).then(function() { 418 showCopySuccess($btn); 419 }).catch(function() { 420 fallbackCopy(redirectUrl, $btn); 421 }); 422 } else { 423 fallbackCopy(redirectUrl, $btn); 424 } 425 }); 426 427 function fallbackCopy(text, $btn) { 428 var $temp = $('<textarea>'); 429 $('body').append($temp); 430 $temp.val(text).select(); 431 try { 432 document.execCommand('copy'); 433 showCopySuccess($btn); 434 } catch (err) { 435 alert('Failed to copy. Please copy manually.'); 436 } 437 $temp.remove(); 438 } 439 440 function showCopySuccess($btn) { 441 var $icon = $btn.find('.dashicons'); 442 $icon.removeClass('dashicons-clipboard').addClass('dashicons-yes'); 443 $btn.addClass('copied'); 444 445 setTimeout(function() { 446 $icon.removeClass('dashicons-yes').addClass('dashicons-clipboard'); 447 $btn.removeClass('copied'); 448 }, 2000); 449 } 450 451 /** 452 * Refresh API Version 453 */ 454 $('#prodshow-refresh-api-version').on('click', function(e) { 455 e.preventDefault(); 456 457 var $btn = $(this); 458 var $icon = $btn.find('.dashicons'); 459 var nonce = $('#prodshow-api-version-nonce').val() || $('#prodshow-oauth-nonce').val() || $('#prodshow-clear-cache-nonce').val(); 460 461 // Show loading state 462 $btn.prop('disabled', true); 463 $icon.addClass('prodshow-spin'); 464 465 $.ajax({ 466 url: ajaxurl, 467 type: 'POST', 468 data: { 469 action: 'prodshow_refresh_api_version', 470 nonce: nonce 471 }, 472 success: function(response) { 473 if (response.success) { 474 // Update the displayed version 475 var $display = $('.prodshow-api-version-display'); 476 var newHtml = response.data.version + 477 ' <span class="prodshow-auto-detected">(' + 'auto-detected' + ')</span>' + 478 ' <button type="button" id="prodshow-refresh-api-version" class="button button-small" title="Refresh API Version">' + 479 '<span class="dashicons dashicons-update"></span></button>'; 480 $display.html(newHtml); 481 482 // Show success feedback 483 alert(response.data.message); 484 485 // Reload to update all references 486 window.location.reload(); 487 } else { 488 alert(response.data.message || 'Failed to refresh API version.'); 489 $btn.prop('disabled', false); 490 $icon.removeClass('prodshow-spin'); 491 } 492 }, 493 error: function() { 494 alert('Connection error. Please try again.'); 495 $btn.prop('disabled', false); 496 $icon.removeClass('prodshow-spin'); 497 } 498 }); 499 }); 500 287 501 }); 288 502 -
products-showcase/trunk/includes/class-admin-settings.php
r3408553 r3435486 77 77 ); 78 78 79 // OAuth settings 80 register_setting( 81 'prodshow_settings', 82 'prodshow_shopify_client_id', 83 array( 84 'type' => 'string', 85 'sanitize_callback' => 'sanitize_text_field', 86 'default' => '', 87 'show_in_rest' => false, 88 ) 89 ); 90 91 register_setting( 92 'prodshow_settings', 93 'prodshow_shopify_client_secret', 94 array( 95 'type' => 'string', 96 'sanitize_callback' => 'sanitize_text_field', 97 'default' => '', 98 'show_in_rest' => false, 99 ) 100 ); 101 79 102 register_setting( 80 103 'prodshow_settings', … … 179 202 } 180 203 204 // Show OAuth success/error notices 205 if (isset($_GET['oauth_success'])) { 206 $success = get_transient('prodshow_oauth_success'); 207 if ($success) { 208 delete_transient('prodshow_oauth_success'); 209 echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('🎉 Successfully connected to Shopify! Your store is now linked.', 'products-showcase') . '</p></div>'; 210 } 211 } 212 213 if (isset($_GET['oauth_error'])) { 214 $error = get_transient('prodshow_oauth_error'); 215 if ($error) { 216 delete_transient('prodshow_oauth_error'); 217 echo '<div class="notice notice-error is-dismissible"><p>' . esc_html__('Connection failed: ', 'products-showcase') . esc_html($error) . '</p></div>'; 218 } 219 } 220 181 221 // Test connection. 182 222 $connection_status = $this->test_shopify_connection(); 223 $is_oauth_connected = PRODSHOW_Shopify_OAuth::is_connected_via_oauth(); 224 225 // Auto-detect API version if connected but not yet stored 226 if ($connection_status['success']) { 227 $stored_version = get_option('prodshow_shopify_api_version', ''); 228 if (empty($stored_version)) { 229 // Try to detect and store the API version now 230 PRODSHOW_Shopify_OAuth::refresh_api_version(); 231 } 232 } 183 233 184 234 // Include the header. … … 200 250 <div class="prodshow-section-header"> 201 251 <h2><?php esc_html_e('🚀 Quick Start', 'products-showcase'); ?></h2> 202 <p><?php esc_html_e('Connect your Shopify store and start displaying products:', 'products-showcase'); ?></p>252 <p><?php esc_html_e('Connect your Shopify store in 3 easy steps:', 'products-showcase'); ?></p> 203 253 </div> 204 254 <div class="prodshow-banner-content"> 205 255 <ol class="prodshow-steps"> 206 256 <li> 207 <strong><?php esc_html_e('Create Custom App', 'products-showcase'); ?></strong>208 <p><?php esc_html_e(' Shopify Admin → Settings → Apps → Develop apps → Create an app', 'products-showcase'); ?></p>257 <strong><?php esc_html_e('Create a Custom App in Shopify', 'products-showcase'); ?></strong> 258 <p><?php esc_html_e('Go to Shopify Admin → Settings → Apps and sales channels → Develop apps → Create an app', 'products-showcase'); ?></p> 209 259 </li> 210 260 <li> 211 <strong><?php esc_html_e('Enable API Access', 'products-showcase'); ?></strong> 212 <p><?php esc_html_e('Configure Admin API → Enable "read_products" scope → Install app', 'products-showcase'); ?></p> 261 <strong><?php esc_html_e('Configure API Access', 'products-showcase'); ?></strong> 262 <p> 263 <?php esc_html_e('In your app settings:', 'products-showcase'); ?><br> 264 • <?php esc_html_e('Go to "Configuration" tab', 'products-showcase'); ?><br> 265 • <?php esc_html_e('Under "Admin API integration", click "Configure"', 'products-showcase'); ?><br> 266 • <?php esc_html_e('Enable "read_products" scope and save', 'products-showcase'); ?><br> 267 • <?php 268 echo wp_kses( 269 sprintf( 270 /* translators: %s: redirect URL */ 271 __('Set Allowed redirection URL to: %s', 'products-showcase'), 272 '<code>' . esc_html(PRODSHOW_Shopify_OAuth::get_redirect_uri()) . '</code>' 273 ), 274 array('code' => array()) 275 ); 276 ?> 277 </p> 213 278 </li> 214 279 <li> 215 <strong><?php esc_html_e('Enter Credentials', 'products-showcase'); ?></strong> 216 <p><?php esc_html_e('Copy your store URL and access token, paste below, and save', 'products-showcase'); ?></p> 217 </li> 218 <li> 219 <strong><?php esc_html_e('Add Products to Your Site', 'products-showcase'); ?></strong> 220 <p><?php esc_html_e('Edit any page/post → Click + to add block → Search "Shopify Product Showcase" → Select product or collection', 'products-showcase'); ?></p> 280 <strong><?php esc_html_e('Copy Client ID & Secret', 'products-showcase'); ?></strong> 281 <p><?php esc_html_e('Go to "API credentials" tab → Copy the Client ID and Client secret → Paste them below and click "Connect to Shopify"', 'products-showcase'); ?></p> 221 282 </li> 222 283 </ol> 223 284 <div class="prodshow-banner-links"> 224 <a href="https://shopify.dev/docs/apps/ auth/admin-app-access-tokens" target="_blank" rel="noopener noreferrer" class="prodshow-link-primary">285 <a href="https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens/" target="_blank" rel="noopener noreferrer" class="prodshow-link-primary"> 225 286 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 226 287 <circle cx="12" cy="12" r="10"></circle> … … 264 325 <div class="prodshow-status-item"> 265 326 <span class="prodshow-status-label"><?php esc_html_e('API Version:', 'products-showcase'); ?></span> 266 <span class="prodshow-status-value"><?php echo esc_html(PRODSHOW_SHOPIFY_API_VERSION); ?></span> 327 <span class="prodshow-status-value prodshow-api-version-display"> 328 <?php 329 $current_api_version = prodshow_get_api_version(); 330 $stored_version = get_option('prodshow_shopify_api_version', ''); 331 echo esc_html($current_api_version); 332 if (!empty($stored_version)) { 333 echo ' <span class="prodshow-auto-detected">(' . esc_html__('auto-detected', 'products-showcase') . ')</span>'; 334 } else { 335 echo ' <span class="prodshow-fallback-version">(' . esc_html__('fallback', 'products-showcase') . ')</span>'; 336 } 337 ?> 338 <button type="button" id="prodshow-refresh-api-version" class="button button-small" title="<?php esc_attr_e('Refresh API Version', 'products-showcase'); ?>"> 339 <span class="dashicons dashicons-update"></span> 340 </button> 341 <input type="hidden" id="prodshow-api-version-nonce" value="<?php echo esc_attr(wp_create_nonce('prodshow_oauth_nonce')); ?>"> 342 </span> 267 343 </div> 268 344 </div> … … 300 376 <?php settings_fields('prodshow_settings'); ?> 301 377 302 <!-- Shopify API Configuration Section-->303 <div class="prodshow-section" >378 <!-- Shopify Connection Section (OAuth) --> 379 <div class="prodshow-section" id="prodshow-oauth-section"> 304 380 <div class="prodshow-section-header"> 305 <h2><?php esc_html_e(' Shopify API Configuration', 'products-showcase'); ?></h2>306 <p><?php esc_html_e('Connect your WordPress site to your Shopify store by providing the necessary API credentials.', 'products-showcase'); ?></p>381 <h2><?php esc_html_e('Connect to Shopify', 'products-showcase'); ?></h2> 382 <p><?php esc_html_e('Connect your WordPress site to your Shopify store using secure OAuth authentication. Just enter your app credentials and click connect!', 'products-showcase'); ?></p> 307 383 </div> 308 384 309 <table class="form-table" role="presentation"> 310 <tbody> 311 <tr> 312 <th scope="row"> 313 <label for="prodshow_shopify_url"><?php esc_html_e('Shopify Store URL', 'products-showcase'); ?></label> 314 </th> 315 <td> 316 <input type="text" 317 id="prodshow_shopify_url" 318 name="prodshow_shopify_url" 319 value="<?php echo esc_attr(get_option('prodshow_shopify_url')); ?>" 320 class="regular-text" 321 placeholder="your-store.myshopify.com"> 322 <p class="description"> 323 <?php esc_html_e('Your Shopify store URL (without https://). Example: my-store.myshopify.com', 'products-showcase'); ?> 324 </p> 325 </td> 326 </tr> 327 328 <tr> 329 <th scope="row"> 330 <label for="prodshow_shopify_access_token"><?php esc_html_e('Admin API Access Token', 'products-showcase'); ?></label> 331 </th> 332 <td> 333 <input type="password" 334 id="prodshow_shopify_access_token" 335 name="prodshow_shopify_access_token" 336 value="<?php echo esc_attr(get_option('prodshow_shopify_access_token')); ?>" 337 class="regular-text" 338 autocomplete="off"> 339 <p class="description"> 340 <?php 341 echo wp_kses( 342 __('Generate from <strong>Shopify Admin → Settings → Apps and sales channels → Develop apps</strong>. Required scopes: <code>read_products</code>', 'products-showcase'), 343 array( 344 'strong' => array(), 345 'code' => array(), 346 ) 347 ); 348 ?> 349 <br> 350 <a href="https://shopify.dev/docs/apps/auth/admin-app-access-tokens" target="_blank" rel="noopener noreferrer"> 351 <?php esc_html_e('Learn how to create an access token →', 'products-showcase'); ?> 352 </a> 353 </p> 354 </td> 355 </tr> 356 </tbody> 357 </table> 385 <?php if ($is_oauth_connected && $connection_status['success']): ?> 386 <!-- Already connected via OAuth - show minimal info --> 387 <div class="prodshow-oauth-connected-info"> 388 <p class="prodshow-oauth-status"> 389 <span class="dashicons dashicons-yes-alt" style="color: #28a745;"></span> 390 <?php esc_html_e('Connected via OAuth', 'products-showcase'); ?> 391 </p> 392 <button type="button" id="prodshow-disconnect-btn" class="button button-secondary"> 393 <?php esc_html_e('Disconnect', 'products-showcase'); ?> 394 </button> 395 <input type="hidden" id="prodshow-oauth-nonce" value="<?php echo esc_attr(wp_create_nonce('prodshow_oauth_nonce')); ?>"> 396 <!-- Preserve OAuth credentials when saving other settings --> 397 <input type="hidden" name="prodshow_shopify_url" value="<?php echo esc_attr(get_option('prodshow_shopify_url')); ?>"> 398 <input type="hidden" name="prodshow_shopify_client_id" value="<?php echo esc_attr(get_option('prodshow_shopify_client_id')); ?>"> 399 <input type="hidden" name="prodshow_shopify_client_secret" value="<?php echo esc_attr(get_option('prodshow_shopify_client_secret')); ?>"> 400 <input type="hidden" name="prodshow_shopify_access_token" value="<?php echo esc_attr(get_option('prodshow_shopify_access_token')); ?>"> 401 </div> 402 <?php else: ?> 403 <table class="form-table" role="presentation"> 404 <tbody> 405 <tr> 406 <th scope="row"> 407 <label for="prodshow_shopify_url"><?php esc_html_e('Shopify Store URL', 'products-showcase'); ?> <span class="required">*</span></label> 408 </th> 409 <td> 410 <input type="text" 411 id="prodshow_shopify_url" 412 name="prodshow_shopify_url" 413 value="<?php echo esc_attr(get_option('prodshow_shopify_url')); ?>" 414 class="regular-text" 415 placeholder="your-store.myshopify.com" 416 required> 417 <p class="description"> 418 <?php esc_html_e('Your Shopify store URL (without https://). Example: my-store.myshopify.com', 'products-showcase'); ?> 419 </p> 420 </td> 421 </tr> 422 423 <tr> 424 <th scope="row"> 425 <label for="prodshow_shopify_client_id"><?php esc_html_e('Client ID', 'products-showcase'); ?> <span class="required">*</span></label> 426 </th> 427 <td> 428 <input type="text" 429 id="prodshow_shopify_client_id" 430 name="prodshow_shopify_client_id" 431 value="<?php echo esc_attr(get_option('prodshow_shopify_client_id')); ?>" 432 class="regular-text" 433 placeholder="e.g., 1234567890abcdef..." 434 autocomplete="off" 435 required> 436 <p class="description"> 437 <?php esc_html_e('Found in your Shopify app settings under "API credentials" → "Client ID"', 'products-showcase'); ?> 438 </p> 439 </td> 440 </tr> 441 442 <tr> 443 <th scope="row"> 444 <label for="prodshow_shopify_client_secret"><?php esc_html_e('Client Secret', 'products-showcase'); ?> <span class="required">*</span></label> 445 </th> 446 <td> 447 <input type="password" 448 id="prodshow_shopify_client_secret" 449 name="prodshow_shopify_client_secret" 450 value="<?php echo esc_attr(get_option('prodshow_shopify_client_secret')); ?>" 451 class="regular-text" 452 placeholder="e.g., shpss_..." 453 autocomplete="off" 454 required> 455 <p class="description"> 456 <?php esc_html_e('Found in your Shopify app settings under "API credentials" → "Client secret"', 'products-showcase'); ?> 457 </p> 458 </td> 459 </tr> 460 461 <tr> 462 <th scope="row"> 463 <label><?php esc_html_e('Redirect URL', 'products-showcase'); ?></label> 464 </th> 465 <td> 466 <div class="prodshow-redirect-url-box"> 467 <code id="prodshow-redirect-url"><?php echo esc_html(PRODSHOW_Shopify_OAuth::get_redirect_uri()); ?></code> 468 <button type="button" id="prodshow-copy-redirect-url" class="button button-small" title="<?php esc_attr_e('Copy to clipboard', 'products-showcase'); ?>"> 469 <span class="dashicons dashicons-clipboard"></span> 470 </button> 471 </div> 472 <p class="description"> 473 <?php esc_html_e('Copy this URL and paste it in your Shopify app\'s "Allowed redirection URL(s)" field.', 'products-showcase'); ?> 474 </p> 475 </td> 476 </tr> 477 </tbody> 478 </table> 479 480 <div class="prodshow-oauth-actions"> 481 <button type="button" id="prodshow-connect-btn" class="button button-primary button-large prodshow-connect-button"> 482 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 483 <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path> 484 <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path> 485 </svg> 486 <?php esc_html_e('Connect to Shopify', 'products-showcase'); ?> 487 </button> 488 <span class="prodshow-oauth-spinner spinner"></span> 489 </div> 490 491 <input type="hidden" id="prodshow-oauth-nonce" value="<?php echo esc_attr(wp_create_nonce('prodshow_oauth_nonce')); ?>"> 492 <?php endif; ?> 358 493 </div> 359 494 -
products-showcase/trunk/products-showcase.php
r3408553 r3435486 4 4 * Plugin URI: https://github.com/HosseinKarami/products-showcase 5 5 * Description: Display Shopify products and collections in beautiful carousels using native Gutenberg blocks. Features product filtering, color swatches, and responsive design. 6 * Version: 1. 0.06 * Version: 1.1.0 7 7 * Requires at least: 6.0 8 8 * Requires PHP: 8.1 … … 23 23 24 24 // Plugin constants. 25 define( 'PRODSHOW_VERSION', '1. 0.0' );25 define( 'PRODSHOW_VERSION', '1.1.0' ); 26 26 define( 'PRODSHOW_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); 27 27 define( 'PRODSHOW_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); 28 28 define( 'PRODSHOW_PLUGIN_BASENAME', plugin_basename( __FILE__ ) ); 29 define( 'PRODSHOW_SHOPIFY_API_VERSION', '2025-10' ); // Shopify Admin API version. 29 define( 'PRODSHOW_SHOPIFY_API_VERSION_FALLBACK', '2025-10' ); // Fallback Shopify Admin API version. 30 31 /** 32 * Get the Shopify API version to use 33 * 34 * Returns the dynamically detected version if available, otherwise falls back to default. 35 * 36 * @return string The API version to use (e.g., '2025-10') 37 */ 38 function prodshow_get_api_version() { 39 $stored_version = get_option( 'prodshow_shopify_api_version', '' ); 40 41 if ( ! empty( $stored_version ) ) { 42 return $stored_version; 43 } 44 45 return PRODSHOW_SHOPIFY_API_VERSION_FALLBACK; 46 } 47 48 // For backwards compatibility, define the constant using the dynamic function 49 // Note: This runs early, so it will use the stored value or fallback 50 if ( ! defined( 'PRODSHOW_SHOPIFY_API_VERSION' ) ) { 51 // We can't call prodshow_get_api_version() before plugins_loaded in some cases, 52 // so we check the option directly here 53 $_prodshow_api_version = get_option( 'prodshow_shopify_api_version', '' ); 54 if ( empty( $_prodshow_api_version ) ) { 55 $_prodshow_api_version = PRODSHOW_SHOPIFY_API_VERSION_FALLBACK; 56 } 57 define( 'PRODSHOW_SHOPIFY_API_VERSION', $_prodshow_api_version ); 58 unset( $_prodshow_api_version ); 59 } 30 60 31 61 /** … … 37 67 require_once PRODSHOW_PLUGIN_DIR . 'includes/class-shopify-block.php'; 38 68 require_once PRODSHOW_PLUGIN_DIR . 'includes/class-shopify-graphql-types.php'; 69 require_once PRODSHOW_PLUGIN_DIR . 'includes/class-shopify-oauth.php'; 39 70 require_once PRODSHOW_PLUGIN_DIR . 'includes/class-admin-settings.php'; 40 71 require_once PRODSHOW_PLUGIN_DIR . 'includes/class-enqueue-assets.php'; … … 45 76 new PRODSHOW_Shopify_Block(); 46 77 new PRODSHOW_Shopify_GraphQL_Types(); 78 new PRODSHOW_Shopify_OAuth(); 47 79 new PRODSHOW_Admin_Settings(); 48 80 new PRODSHOW_Enqueue_Assets(); 49 81 new PRODSHOW_REST_API(); 82 83 // Auto-detect API version if connected but no version stored yet 84 add_action( 'admin_init', 'prodshow_maybe_detect_api_version' ); 50 85 } 51 86 add_action( 'plugins_loaded', 'prodshow_init' ); 87 88 /** 89 * Detect and store API version if connected but not yet detected 90 * 91 * This runs once when an existing connection doesn't have a stored API version. 92 */ 93 function prodshow_maybe_detect_api_version() { 94 // Only run once per day to avoid excessive API calls 95 $last_check = get_transient( 'prodshow_api_version_check' ); 96 if ( $last_check ) { 97 return; 98 } 99 100 // Check if we have a connection but no stored API version 101 $shop_url = get_option( 'prodshow_shopify_url', '' ); 102 $access_token = get_option( 'prodshow_shopify_access_token', '' ); 103 $stored_version = get_option( 'prodshow_shopify_api_version', '' ); 104 105 // If we have credentials but no stored version, try to detect it 106 if ( ! empty( $shop_url ) && ! empty( $access_token ) && empty( $stored_version ) ) { 107 $detected_version = PRODSHOW_Shopify_OAuth::get_latest_api_version( $shop_url, $access_token ); 108 109 if ( $detected_version ) { 110 update_option( 'prodshow_shopify_api_version', $detected_version ); 111 } 112 } 113 114 // Set transient to prevent checking again for 24 hours 115 set_transient( 'prodshow_api_version_check', true, DAY_IN_SECONDS ); 116 } 52 117 53 118 /** -
products-showcase/trunk/readme.txt
r3408830 r3435486 1 1 === Products Showcase – Shopify Integration === 2 2 Contributors: hosseinkarami 3 Donate link: https://buymeacoffee.com/hosseinkarami?utm_source=wordpress&utm_medium=readme&utm_campaign=products-showcase&utm_content=readme-donate4 3 Tags: shopify, ecommerce, products, gutenberg, blocks 5 4 Requires at least: 6.0 6 5 Tested up to: 6.9 7 Stable tag: 1. 0.06 Stable tag: 1.1.0 8 7 Requires PHP: 8.1 9 8 License: GPLv2 or later … … 22 21 23 22 To build from source: 24 ` 25 npm install26 npm run build27 ` 23 ``` 24 npm install 25 npm run build 26 ``` 28 27 29 28 For development with hot reloading: 30 ` 31 npm start32 ` 29 ``` 30 npm start 31 ``` 33 32 34 33 == Description == … … 82 81 **Data Transmitted to Shopify**: 83 82 1. **Shopify Store URL** - Your store's domain (e.g., `your-store.myshopify.com`) configured in plugin settings 84 2. **Admin API Access Token** - Your Shopify Admin API authentication token (configured in plugin settings) 85 3. **GraphQL Queries** - Specific queries requesting product and collection data 83 2. **OAuth Credentials** - Client ID and Client Secret are used during the one-time OAuth authorization flow 84 3. **Admin API Access Token** - Obtained automatically via OAuth and used for all subsequent API requests 85 4. **GraphQL Queries** - Specific queries requesting product and collection data 86 86 87 87 **When Data is Transmitted**: … … 146 146 5. Activate the plugin in WordPress 147 147 148 = Getting Your Shopify API Credentials=148 = Creating Your Shopify App = 149 149 150 150 1. Log in to your Shopify Admin 151 151 2. Go to Settings → Apps and sales channels 152 152 3. Click "Develop apps" 153 4. Create a new app or select an existing one 154 5. Configure Admin API scopes (required: `read_products`) 155 6. Install the app to your store 156 7. Copy the Admin API access token 157 8. Paste it into the plugin settings at Settings → Shopify Products 153 4. Click "Create an app" and name it (e.g., "WordPress Integration") 154 5. Go to the "Configuration" tab 155 6. Under "Admin API integration", click "Configure" 156 7. Enable the `read_products` scope and save 157 8. Under "Allowed redirection URL(s)", add the Redirect URL shown in your WordPress plugin settings 158 9. Go to the "API credentials" tab 159 10. Copy the **Client ID** and **Client secret** 158 160 159 161 == Configuration == 160 162 161 = Plugin Settings=162 163 1. Navigate to **S ettings → Shopify Products** in WordPress admin163 = Easy OAuth Setup = 164 165 1. Navigate to **Shopify Products** in WordPress admin 164 166 2. Enter your **Shopify Store URL** (e.g., `your-store.myshopify.com`) 165 3. Paste your **Admin API Access Token** 166 4. Set **Cache Duration** (default: 1 hour, recommended to reduce API calls) 167 5. Click **Save Settings** 168 169 The plugin will automatically test the connection and display a success message if configured correctly. 167 3. Paste your **Client ID** from Shopify 168 4. Paste your **Client Secret** from Shopify 169 5. Click **"Connect to Shopify"** 170 6. You'll be redirected to Shopify to authorize the connection 171 7. After authorizing, you're automatically redirected back - done! 172 173 The plugin automatically obtains the access token via secure OAuth and detects the latest Shopify API version. 170 174 171 175 == Usage == … … 218 222 219 223 Target these CSS classes for custom styling: 220 ` 221 /* Container */ 222 .wp-block-products-showcase-products { } 223 .prodshow-shopify-block { } 224 .prodshow-container { } 225 226 /* Header */ 227 .prodshow-title { } 228 .prodshow-description { } 229 .prodshow-cta-button { } 230 231 /* Carousel */ 232 .prodshow-carousel { } 233 .prodshow-carousel-viewport { } 234 .prodshow-carousel-container { } 235 .prodshow-carousel-btn { } 236 237 /* Product Cards */ 238 .prodshow-product-card { } 239 .prodshow-product-image { } 240 .prodshow-product-title { } 241 .prodshow-product-price { } 242 .prodshow-product-swatches { } 243 .prodshow-swatch { } 244 245 /* Single Product Layout */ 246 .prodshow-single-product { } 247 .prodshow-single-info { } 248 ` 224 225 ```css 226 /* Container */ 227 .wp-block-products-showcase-products { } 228 .prodshow-shopify-block { } 229 .prodshow-container { } 230 231 /* Header */ 232 .prodshow-title { } 233 .prodshow-description { } 234 .prodshow-cta-button { } 235 236 /* Carousel */ 237 .prodshow-carousel { } 238 .prodshow-carousel-viewport { } 239 .prodshow-carousel-container { } 240 .prodshow-carousel-btn { } 241 242 /* Product Cards */ 243 .prodshow-product-card { } 244 .prodshow-product-image { } 245 .prodshow-product-title { } 246 .prodshow-product-price { } 247 .prodshow-product-swatches { } 248 .prodshow-swatch { } 249 250 /* Single Product Layout */ 251 .prodshow-single-product { } 252 .prodshow-single-info { } 253 ``` 249 254 250 255 = Template Overrides = 251 256 252 257 Copy templates to your theme for customization: 253 ` 254 your-theme/ 255 products-showcase/ 256 block-template.php 257 product-card.php 258 ` 258 259 ``` 260 your-theme/ 261 products-showcase/ 262 block-template.php 263 product-card.php 264 ``` 259 265 260 266 The plugin will automatically use your theme templates if they exist. … … 275 281 276 282 Modify cache duration: 277 ` 278 add_filter('prodshow_cache_duration', function($duration) {279 return 2 * HOUR_IN_SECONDS;280 });281 ` 283 ```php 284 add_filter('prodshow_cache_duration', function($duration) { 285 return 2 * HOUR_IN_SECONDS; 286 }); 287 ``` 282 288 283 289 Customize product data before display: 284 ` 285 add_filter('prodshow_product_data', function($product) {286 // Modify product data287 return $product;288 }, 10, 1);289 ` 290 ```php 291 add_filter('prodshow_product_data', function($product) { 292 // Modify product data 293 return $product; 294 }, 10, 1); 295 ``` 290 296 291 297 Add custom product filtering: 292 ` 293 add_filter('prodshow_filter_products', function($products) {294 // Filter products array295 return $products;296 }, 10, 1);297 ` 298 ```php 299 add_filter('prodshow_filter_products', function($products) { 300 // Filter products array 301 return $products; 302 }, 10, 1); 303 ``` 298 304 299 305 = Programmatic Usage = 300 ` 301 // Get Shopify API instance 302 $shopify_api = new PRODSHOW_Shopify_API(); 303 304 // Search products 305 $products = $shopify_api->search_products('shirt'); 306 307 // Search collections 308 $collections = $shopify_api->search_collections('summer'); 309 310 // Fetch product data 311 $product = $shopify_api->fetch_product_data('gid://shopify/Product/123456'); 312 313 // Fetch collection products 314 $products = $shopify_api->fetch_collection_products('gid://shopify/Collection/789', 12); 315 ` 306 307 ```php 308 // Get Shopify API instance 309 $shopify_api = new PRODSHOW_Shopify_API(); 310 311 // Search products 312 $products = $shopify_api->search_products('shirt'); 313 314 // Search collections 315 $collections = $shopify_api->search_collections('summer'); 316 317 // Fetch product data 318 $product = $shopify_api->fetch_product_data('gid://shopify/Product/123456'); 319 320 // Fetch collection products 321 $products = $shopify_api->fetch_collection_products('gid://shopify/Collection/789', 12); 322 ``` 316 323 317 324 = Build Scripts = 318 ` 319 # Development - watch for changes 320 npm start 321 322 # Production build 323 npm run build 324 325 # Linting 326 npm run lint:js # Lint JavaScript 327 npm run lint:css # Lint styles 328 npm run lint:pkg-json # Lint package.json 329 330 # Formatting 331 npm run format # Auto-fix code style 332 333 # Create plugin zip 334 npm run plugin-zip 335 ` 325 326 ```bash 327 # Development - watch for changes 328 npm start 329 330 # Production build 331 npm run build 332 333 # Linting 334 npm run lint:js # Lint JavaScript 335 npm run lint:css # Lint styles 336 npm run lint:pkg-json # Lint package.json 337 338 # Formatting 339 npm run format # Auto-fix code style 340 341 # Create plugin zip 342 npm run plugin-zip 343 ``` 336 344 337 345 == Frequently Asked Questions == … … 367 375 = What happens if my Shopify API credentials change? = 368 376 369 Simply update the credentials in Settings → Shopify Products. The plugin will automatically use the new credentials for all future API requests.377 Click the "Disconnect" button in Shopify Products settings, then reconnect using the OAuth flow with your new credentials. The plugin will automatically obtain a new access token. 370 378 371 379 = Can I filter out certain products? = … … 401 409 1. Ensure Node.js 18+ is installed 402 410 2. Clean install: 403 ` 404 rm -rf node_modules package-lock.json 405 npm install 406 npm run build 407 ` 408 411 ``` 412 rm -rf node_modules package-lock.json 413 npm install 414 npm run build 415 ``` 409 416 3. Check @wordpress/scripts version compatibility 410 417 … … 419 426 420 427 == Changelog == 428 429 = 1.1.0 = 430 * **NEW: OAuth 2.0 Authentication** - Easy one-click connection to Shopify using secure OAuth flow 431 * **NEW: Auto API Version Detection** - Automatically detects and uses the latest Shopify API version 432 * **NEW: Simplified Setup** - No more manual access token copying - just enter Client ID & Secret and click Connect 433 * **NEW: Disconnect/Reconnect** - Easy way to change Shopify credentials 434 * **NEW: Refresh API Version** - Button to manually refresh the detected API version 435 * Improved admin UI with better connection status display 436 * Enhanced security with OAuth state validation 421 437 422 438 = 1.0.0 = … … 437 453 == Upgrade Notice == 438 454 455 = 1.1.0 = 456 Major update! Now featuring OAuth 2.0 authentication - no more manual token copying. Just enter your Client ID & Secret and click Connect. Existing connections will continue to work. 457 439 458 = 1.0.0 = 440 459 Initial release of Products Showcase – Shopify Integration. Display your Shopify products beautifully on WordPress!
Note: See TracChangeset
for help on using the changeset viewer.