Changeset 3460128
- Timestamp:
- 02/12/2026 03:52:16 PM (7 weeks ago)
- Location:
- formscrm
- Files:
-
- 17 added
- 2 deleted
- 50 edited
- 1 copied
-
assets/screenshot-6.png (added)
-
assets/screenshot-7.png (added)
-
assets/screenshot-8.png (added)
-
tags/4.3.0 (copied) (copied from formscrm/trunk)
-
tags/4.3.0/blueprint.json (deleted)
-
tags/4.3.0/formscrm.php (modified) (3 diffs)
-
tags/4.3.0/includes/admin/class-admin-options.php (modified) (4 diffs)
-
tags/4.3.0/includes/admin/class-error-log-page.php (added)
-
tags/4.3.0/includes/admin/class-error-log.php (added)
-
tags/4.3.0/includes/admin/js (added)
-
tags/4.3.0/includes/admin/js/error-log.js (added)
-
tags/4.3.0/includes/assets/elementor-editor.js (modified) (2 diffs)
-
tags/4.3.0/includes/assets/formscrm-admin.css (modified) (3 diffs)
-
tags/4.3.0/includes/assets/icons/icon-bell.svg (modified) (1 diff)
-
tags/4.3.0/includes/assets/icons/icon-check-simple.svg (modified) (1 diff)
-
tags/4.3.0/includes/assets/icons/icon-checkmark.svg (modified) (1 diff)
-
tags/4.3.0/includes/assets/icons/icon-document.svg (modified) (1 diff)
-
tags/4.3.0/includes/assets/icons/icon-email.svg (modified) (1 diff)
-
tags/4.3.0/includes/assets/icons/icon-plus.svg (modified) (1 diff)
-
tags/4.3.0/includes/assets/icons/icon-slack.svg (modified) (1 diff)
-
tags/4.3.0/includes/assets/icons/icon-support.svg (modified) (1 diff)
-
tags/4.3.0/includes/assets/icons/icon-users.svg (modified) (1 diff)
-
tags/4.3.0/includes/assets/js (added)
-
tags/4.3.0/includes/assets/js/cf7-autosubmit.js (added)
-
tags/4.3.0/includes/crm-library/class-crmlib-brevo.php (modified) (1 diff)
-
tags/4.3.0/includes/crm-library/class-crmlib-clientify.php (modified) (2 diffs)
-
tags/4.3.0/includes/formscrm-library/class-contactform7.php (modified) (9 diffs)
-
tags/4.3.0/includes/formscrm-library/class-elementor.php (modified) (4 diffs)
-
tags/4.3.0/includes/formscrm-library/class-gravityforms-markdown-export.php (added)
-
tags/4.3.0/includes/formscrm-library/class-gravityforms-widget.php (modified) (4 diffs)
-
tags/4.3.0/includes/formscrm-library/class-gravityforms.php (modified) (6 diffs)
-
tags/4.3.0/includes/formscrm-library/class-woocommerce.php (modified) (2 diffs)
-
tags/4.3.0/includes/formscrm-library/class-wpforms.php (modified) (2 diffs)
-
tags/4.3.0/includes/formscrm-library/elementor-ajax.php (modified) (3 diffs)
-
tags/4.3.0/includes/formscrm-library/helpers-functions.php (modified) (4 diffs)
-
tags/4.3.0/includes/formscrm-library/loader.php (modified) (1 diff)
-
tags/4.3.0/readme.txt (modified) (4 diffs)
-
trunk/blueprint.json (deleted)
-
trunk/formscrm.php (modified) (3 diffs)
-
trunk/includes/admin/class-admin-options.php (modified) (4 diffs)
-
trunk/includes/admin/class-error-log-page.php (added)
-
trunk/includes/admin/class-error-log.php (added)
-
trunk/includes/admin/js (added)
-
trunk/includes/admin/js/error-log.js (added)
-
trunk/includes/assets/elementor-editor.js (modified) (2 diffs)
-
trunk/includes/assets/formscrm-admin.css (modified) (3 diffs)
-
trunk/includes/assets/icons/icon-bell.svg (modified) (1 diff)
-
trunk/includes/assets/icons/icon-check-simple.svg (modified) (1 diff)
-
trunk/includes/assets/icons/icon-checkmark.svg (modified) (1 diff)
-
trunk/includes/assets/icons/icon-document.svg (modified) (1 diff)
-
trunk/includes/assets/icons/icon-email.svg (modified) (1 diff)
-
trunk/includes/assets/icons/icon-plus.svg (modified) (1 diff)
-
trunk/includes/assets/icons/icon-slack.svg (modified) (1 diff)
-
trunk/includes/assets/icons/icon-support.svg (modified) (1 diff)
-
trunk/includes/assets/icons/icon-users.svg (modified) (1 diff)
-
trunk/includes/assets/js (added)
-
trunk/includes/assets/js/cf7-autosubmit.js (added)
-
trunk/includes/crm-library/class-crmlib-brevo.php (modified) (1 diff)
-
trunk/includes/crm-library/class-crmlib-clientify.php (modified) (2 diffs)
-
trunk/includes/formscrm-library/class-contactform7.php (modified) (9 diffs)
-
trunk/includes/formscrm-library/class-elementor.php (modified) (4 diffs)
-
trunk/includes/formscrm-library/class-gravityforms-markdown-export.php (added)
-
trunk/includes/formscrm-library/class-gravityforms-widget.php (modified) (4 diffs)
-
trunk/includes/formscrm-library/class-gravityforms.php (modified) (6 diffs)
-
trunk/includes/formscrm-library/class-woocommerce.php (modified) (2 diffs)
-
trunk/includes/formscrm-library/class-wpforms.php (modified) (2 diffs)
-
trunk/includes/formscrm-library/elementor-ajax.php (modified) (3 diffs)
-
trunk/includes/formscrm-library/helpers-functions.php (modified) (4 diffs)
-
trunk/includes/formscrm-library/loader.php (modified) (1 diff)
-
trunk/readme.txt (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
formscrm/tags/4.3.0/formscrm.php
r3425471 r3460128 4 4 * Plugin URI : https://close.technology/wordpress-plugins/formscrm/ 5 5 * Description: Connects Forms with CRM, ERP and Email Marketing. 6 * Version: 4. 2.16 * Version: 4.3.0 7 7 * Author: CloseTechnology 8 8 * Author URI: https://close.technology … … 24 24 defined( 'ABSPATH' ) || die( 'No script kiddies please!' ); 25 25 26 define( 'FORMSCRM_VERSION', '4. 2.1' );26 define( 'FORMSCRM_VERSION', '4.3.0' ); 27 27 define( 'FORMSCRM_PLUGIN', __FILE__ ); 28 28 define( 'FORMSCRM_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); … … 92 92 // Include files. 93 93 require_once FORMSCRM_PLUGIN_PATH . '/includes/admin/class-admin-options.php'; 94 require_once FORMSCRM_PLUGIN_PATH . '/includes/admin/class-error-log.php'; 95 require_once FORMSCRM_PLUGIN_PATH . '/includes/admin/class-error-log-page.php'; 94 96 require_once FORMSCRM_PLUGIN_PATH . '/includes/formscrm-library/loader.php'; -
formscrm/tags/4.3.0/includes/admin/class-admin-options.php
r3424189 r3460128 37 37 add_action( 'admin_menu', array( $this, 'add_plugin_page' ) ); 38 38 add_action( 'formscrm_settings', array( $this, 'settings_page' ) ); 39 add_action( 'formscrm_notifications', array( $this, 'notifications_page' ) ); 39 40 add_action( 'admin_init', array( $this, 'register_settings' ) ); 40 41 } … … 142 143 'action' => 'formscrm_settings', 143 144 ), 145 array( 146 'tab' => 'notifications', 147 'label' => esc_html__( 'Notifications', 'formscrm' ), 148 'action' => 'formscrm_notifications', 149 ), 150 array( 151 'tab' => 'error-log', 152 'label' => esc_html__( 'Error Log', 'formscrm' ), 153 'action' => 'formscrm_error_log_content', 154 ), 144 155 ) 145 156 ); … … 210 221 211 222 /** 212 * Renders the settings page. 213 * 214 * Displays the FormsCRM settings form with Slack integration options. 215 * 216 * @return void 217 */ 218 public function settings_page() { 219 $source_shop_url = 'es' === strtok( get_locale(), '_' ) ? 'https://close.technology/' : 'https://close.technology/en/'; 220 $utm_source = '?utm_source=WordPress+Settings&utm_medium=plugin&utm_campaign=link'; 223 * Renders the notifications page. 224 * 225 * Displays error notification settings including Slack and Email options. 226 * 227 * @return void 228 */ 229 public function notifications_page() { 221 230 $slack_webhook_url = get_option( 'formscrm_slack_webhook_url', '' ); 222 231 $error_notification_email = get_option( 'formscrm_error_notification_email', '' ); 223 232 ?> 224 233 225 <!-- Notifications Section -->234 <!-- Error Notifications Section --> 226 235 <div class="fcrm-section"> 227 236 <div class="fcrm-section-header"> … … 306 315 </div> 307 316 </div> 317 <?php 318 } 319 320 /** 321 * Renders the settings page. 322 * 323 * Displays supported forms and CRM integrations. 324 * 325 * @return void 326 */ 327 public function settings_page() { 328 $source_shop_url = 'es' === strtok( get_locale(), '_' ) ? 'https://close.technology/' : 'https://close.technology/en/'; 329 $utm_source = '?utm_source=WordPress+Settings&utm_medium=plugin&utm_campaign=link'; 330 ?> 308 331 309 332 <!-- Forms Supported Section --> -
formscrm/tags/4.3.0/includes/assets/elementor-editor.js
r3290078 r3460128 30 30 }; 31 31 32 $form.html('Loading...'); 32 // Show loading state. 33 $('#formscrm-connection-status').html( 34 '<div style="padding: 12px; background: #f9f9f9; border-left: 4px solid #0073aa; border-radius: 4px;">' + 35 '<div style="display: flex; align-items: center; gap: 8px;">' + 36 '<strong style="color: #23282d;">' + 'API Connection Status:' + '</strong> ' + 37 '<span style="display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 3px; background: #0073aa; color: white; font-size: 12px; font-weight: bold;">' + 38 '<span style="margin-right: 5px;">⟳</span>' + 'Connecting...' + 39 '</span>' + 40 '</div></div>' 41 ); 42 $form.html('<p style="text-align: center; padding: 20px; color: #666;">Loading...</p>'); 33 43 34 44 $.ajax({ … … 39 49 success: function(response) { 40 50 console.log('Response:', response); 41 $form.html(response.data); 51 52 if (response.success) { 53 // Update connection status. 54 if (response.data.status_html) { 55 $('#formscrm-connection-status').html(response.data.status_html); 56 } 42 57 43 if ( !$('#fc_crm_module').val() ) { 44 // select first 45 let firstSelect = $form.find('select').first(); 46 let firstOption = firstSelect.find('option').first(); 47 firstSelect.val(firstOption.val()); 58 // Update form content with modules and fields. 59 $form.html(response.data.form_html); 60 61 if ( !$('#fc_crm_module').val() ) { 62 // select first 63 let firstSelect = $form.find('select').first(); 64 let firstOption = firstSelect.find('option').first(); 65 firstSelect.val(firstOption.val()); 66 } 67 68 $('.elementor-map-table[data-module="'+$('#fc_crm_module').val()+'"]').addClass('active'); 69 } else { 70 // Handle error response. 71 // Update connection status with error HTML if available. 72 if (response.data && response.data.status_html) { 73 $('#formscrm-connection-status').html(response.data.status_html); 74 } else { 75 let errorMessage = response.data && response.data.message ? response.data.message : (response.data || 'Connection failed'); 76 77 // Show error in status indicator. 78 $('#formscrm-connection-status').html( 79 '<div style="padding: 12px; background: #ffebee; border-left: 4px solid #dc3232; border-radius: 4px;">' + 80 '<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">' + 81 '<strong style="color: #23282d;">API Connection Status:</strong> ' + 82 '<span style="display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 3px; background: #dc3232; color: white; font-size: 12px; font-weight: bold;">' + 83 '<span style="margin-right: 5px;">✕</span>Error' + 84 '</span>' + 85 '</div>' + 86 '<p style="margin: 0; color: #dc3232; font-size: 12px;"><strong>Error:</strong> ' + errorMessage + '</p>' + 87 '</div>' 88 ); 89 } 90 $form.html(''); 48 91 } 49 50 $('.elementor-map-table[data-module="'+$('#fc_crm_module').val()+'"]').addClass('active');51 92 }, 52 93 error: function(xhr, status, error) { 53 94 console.error('AJAX Error:', error); 95 // Show error in status indicator. 96 $('#formscrm-connection-status').html( 97 '<div style="padding: 12px; background: #ffebee; border-left: 4px solid #dc3232; border-radius: 4px;">' + 98 '<div style="display: flex; align-items: center; gap: 8px;">' + 99 '<strong style="color: #23282d;">API Connection Status:</strong> ' + 100 '<span style="display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 3px; background: #dc3232; color: white; font-size: 12px; font-weight: bold;">' + 101 '<span style="margin-right: 5px;">✕</span>Error' + 102 '</span>' + 103 '</div>' + 104 '<p style="margin: 8px 0 0 0; color: #dc3232; font-size: 12px;"><strong>Error:</strong> Network error - ' + error + '</p>' + 105 '</div>' 106 ); 107 $form.html(''); 54 108 } 55 109 }); -
formscrm/tags/4.3.0/includes/assets/formscrm-admin.css
r3424189 r3460128 114 114 /* Tabs Navigation */ 115 115 .fcrm-tabs-wrapper { 116 margin-bottom: 2rem;116 margin-bottom: 1rem; 117 117 background: white; 118 118 border-radius: 1rem; … … 413 413 align-items: center; 414 414 justify-content: center; 415 padding: 0. 75rem 1.5rem;415 padding: 0.40rem 1rem; 416 416 font-size: 1rem; 417 417 font-weight: 600; … … 555 555 height: 1.5rem; 556 556 } 557 558 /* Error Log Specific Styles */ 559 .fcrm-error-log-table-wrapper { 560 overflow-x: auto; 561 margin-top: 1.5rem; 562 } 563 564 .fcrm-table { 565 width: 100%; 566 border-collapse: collapse; 567 background: white; 568 } 569 570 .fcrm-table thead { 571 background: var(--fcrm-gray-50); 572 } 573 574 .fcrm-table th { 575 padding: 0.75rem 1rem; 576 text-align: left; 577 border-bottom: 2px solid var(--fcrm-gray-200); 578 font-weight: 600; 579 color: var(--fcrm-gray-700); 580 font-size: 0.875rem; 581 white-space: nowrap; 582 } 583 584 .fcrm-table td { 585 padding: 0.75rem 1rem; 586 border-bottom: 1px solid var(--fcrm-gray-200); 587 font-size: 0.875rem; 588 color: var(--fcrm-gray-700); 589 } 590 591 .fcrm-table tbody tr:hover { 592 background: var(--fcrm-gray-50); 593 } 594 595 .fcrm-button-small { 596 padding: 0.375rem 0.75rem; 597 font-size: 0.8125rem; 598 } 599 600 .fcrm-button-danger { 601 background: var(--fcrm-error); 602 color: white; 603 } 604 605 .fcrm-button-danger:hover { 606 background: #dc2626; 607 transform: translateY(-2px); 608 box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4); 609 } 610 611 .fcrm-status { 612 display: inline-block; 613 padding: 0.25rem 0.625rem; 614 border-radius: 0.375rem; 615 font-size: 0.75rem; 616 font-weight: 600; 617 text-transform: uppercase; 618 letter-spacing: 0.025em; 619 } 620 621 .fcrm-status-error { 622 background: #fef2f2; 623 color: #991b1b; 624 } 625 626 .fcrm-status-success { 627 background: #f0fdf4; 628 color: #15803d; 629 } 630 631 .fcrm-log-details { 632 background: var(--fcrm-gray-50); 633 } 634 635 .fcrm-log-details td { 636 padding: 1.5rem; 637 } 638 639 .fcrm-pagination { 640 display: flex; 641 justify-content: center; 642 gap: 0.5rem; 643 margin-top: 1.5rem; 644 flex-wrap: wrap; 645 } 646 647 .fcrm-notice-info { 648 background: #eff6ff; 649 border-left: 4px solid var(--fcrm-blue); 650 } 651 652 .fcrm-notice-info .fcrm-notice-text { 653 color: #1e40af; 654 } 655 656 /* Error Log Filters */ 657 .fcrm-error-log-filters { 658 margin-bottom: 1.25rem; 659 display: flex; 660 gap: 1rem; 661 align-items: center; 662 justify-content: space-between; 663 flex-wrap: wrap; 664 } 665 666 .fcrm-error-log-filters-form { 667 width: 84%; 668 display: flex; 669 gap: 0.625rem; 670 align-items: center; 671 flex-wrap: wrap; 672 } 673 674 .wp-core-ui select.fcrm-filter-select { 675 max-width: 200px; 676 flex-shrink: 0; 677 } 678 679 /* Stats Summary */ 680 .fcrm-stats-summary { 681 margin-bottom: 1.25rem; 682 padding: 0.9375rem; 683 background: var(--fcrm-gray-100); 684 border-radius: 0.3125rem; 685 } 686 687 /* Table Actions Column */ 688 .fcrm-table-actions { 689 text-align: center; 690 width: 280px; 691 min-width: 280px; 692 } 693 694 .fcrm-table-actions .fcrm-button { 695 margin-right: 0.3125rem; 696 } 697 698 .fcrm-table-actions .fcrm-button:last-child { 699 margin-right: 0; 700 } 701 702 /* Form Subtitle */ 703 .fcrm-form-subtitle { 704 color: var(--fcrm-gray-600); 705 } 706 707 /* Error Message Column */ 708 .fcrm-error-message { 709 max-width: 300px; 710 overflow: hidden; 711 text-overflow: ellipsis; 712 } 713 714 /* Details Row */ 715 .fcrm-log-details { 716 display: none; 717 } 718 719 .fcrm-details-cell { 720 padding: 1.25rem; 721 background: var(--fcrm-gray-50); 722 } 723 724 .fcrm-details-grid { 725 display: grid; 726 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 727 gap: 1.25rem; 728 } 729 730 .fcrm-details-section h4 { 731 margin-top: 0; 732 } 733 734 .fcrm-details-box { 735 background: white; 736 padding: 0.9375rem; 737 border-radius: 0.3125rem; 738 } 739 740 .fcrm-details-box-scroll { 741 max-height: 300px; 742 overflow-y: auto; 743 } 744 745 /* Lead Data Table */ 746 .fcrm-lead-data-table { 747 width: 100%; 748 } 749 750 .fcrm-lead-data-name { 751 padding: 0.3125rem; 752 font-weight: 600; 753 } 754 755 .fcrm-lead-data-value { 756 padding: 0.3125rem; 757 } 758 759 /* Code Blocks */ 760 .fcrm-code { 761 word-break: break-all; 762 font-size: 0.6875rem; 763 } 764 765 .fcrm-code-block { 766 display: block; 767 max-height: 150px; 768 overflow-y: auto; 769 background: var(--fcrm-gray-100); 770 padding: 0.625rem; 771 border-radius: 0.1875rem; 772 } 773 774 /* Full Width Details */ 775 .fcrm-details-full { 776 grid-column: 1 / -1; 777 } 778 779 /* Error Box */ 780 .fcrm-error-box { 781 background: #ffebee; 782 padding: 0.9375rem; 783 border-radius: 0.3125rem; 784 border-left: 4px solid #d32f2f; 785 } 786 787 @media (max-width: 768px) { 788 .fcrm-error-log-filters { 789 flex-direction: column; 790 align-items: stretch !important; 791 } 792 793 .fcrm-error-log-filters-form { 794 flex-direction: column; 795 width: 80%; 796 } 797 798 .fcrm-filter-select { 799 max-width: 100% !important; 800 } 801 802 .fcrm-table { 803 font-size: 0.75rem; 804 } 805 806 .fcrm-table th, 807 .fcrm-table td { 808 padding: 0.5rem; 809 } 810 811 .fcrm-table-actions { 812 width: auto; 813 min-width: auto; 814 } 815 816 .fcrm-table-actions .fcrm-button { 817 display: block; 818 margin-bottom: 0.3125rem; 819 margin-right: 0; 820 } 821 822 .fcrm-details-grid { 823 grid-template-columns: 1fr; 824 } 825 } 826 827 /* ================================================================= 828 Gravity Forms - Connected Feeds Column 829 ================================================================= */ 830 831 /* Column Styling */ 832 .gform-table .formscrm_feeds, 833 .wp-list-table .formscrm_feeds { 834 min-width: 200px; 835 max-width: 350px; 836 vertical-align: top; 837 } 838 839 .wp-list-table td.formscrm_feeds { 840 padding: 10px; 841 } 842 843 /* Status Badges - Using GravityForms native classes */ 844 /* .gform-status-indicator.gform-status--active for connected state */ 845 /* .gform-status-indicator.gform-status--inactive for disconnected/error state */ 846 847 /* Feeds Wrapper */ 848 .formscrm_feeds { 849 850 .gform-status--active { 851 border-radius: .75rem; 852 gap: .25rem; 853 padding-block: 0.0625rem; 854 padding-inline: 0.375rem 0.5rem; 855 font-size: 12px; 856 } 857 } 858 859 /* Feeds List */ 860 .formscrm-feeds-list { 861 display: flex; 862 flex-direction: column; 863 gap: 4px; 864 margin-top: 8px; 865 padding-top: 8px; 866 border-top: 1px solid #e0e0e0; 867 } 868 869 .formscrm-feed-item { 870 display: flex; 871 align-items: center; 872 gap: 6px; 873 line-height: 1.4; 874 } 875 876 .formscrm-feed-name { 877 font-weight: 500; 878 color: #000; 879 } 880 881 .formscrm-feed-crm { 882 color: #666; 883 font-size: 0.85em; 884 font-style: italic; 885 } 886 887 .formscrm-feed-total { 888 margin-top: 4px; 889 padding-top: 4px; 890 border-top: 1px solid #ddd; 891 color: #666; 892 font-size: 0.85em; 893 } 894 895 /* Mobile responsive */ 896 @media screen and (max-width: 782px) { 897 .gform-table .formscrm_feeds, 898 .wp-list-table .formscrm_feeds { 899 min-width: 150px; 900 } 901 902 .formscrm-status-badge { 903 padding: 4px 8px; 904 font-size: 0.85em; 905 } 906 907 .formscrm-feed-item { 908 font-size: 0.9em; 909 } 910 } -
formscrm/tags/4.3.0/includes/assets/icons/icon-bell.svg
r3424189 r3460128 2 2 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"></path> 3 3 </svg> 4 -
formscrm/tags/4.3.0/includes/assets/icons/icon-check-simple.svg
r3424189 r3460128 2 2 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path> 3 3 </svg> 4 -
formscrm/tags/4.3.0/includes/assets/icons/icon-checkmark.svg
r3424189 r3460128 2 2 <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/> 3 3 </svg> 4 -
formscrm/tags/4.3.0/includes/assets/icons/icon-document.svg
r3424189 r3460128 2 2 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path> 3 3 </svg> 4 -
formscrm/tags/4.3.0/includes/assets/icons/icon-email.svg
r3424189 r3460128 2 2 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path> 3 3 </svg> 4 -
formscrm/tags/4.3.0/includes/assets/icons/icon-plus.svg
r3424189 r3460128 2 2 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path> 3 3 </svg> 4 -
formscrm/tags/4.3.0/includes/assets/icons/icon-slack.svg
r3424189 r3460128 2 2 <path d="M6 8a2 2 0 1 0-4 0 2 2 0 0 0 4 0zm8 0a2 2 0 1 0-4 0 2 2 0 0 0 4 0zm8 0a2 2 0 1 0-4 0 2 2 0 0 0 4 0zM6 16a2 2 0 1 0-4 0 2 2 0 0 0 4 0zm8 0a2 2 0 1 0-4 0 2 2 0 0 0 4 0zm8 0a2 2 0 1 0-4 0 2 2 0 0 0 4 0z"/> 3 3 </svg> 4 -
formscrm/tags/4.3.0/includes/assets/icons/icon-support.svg
r3424189 r3460128 2 2 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192l-3.536 3.536M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-5 0a4 4 0 11-8 0 4 4 0 018 0z"></path> 3 3 </svg> 4 -
formscrm/tags/4.3.0/includes/assets/icons/icon-users.svg
r3424189 r3460128 2 2 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path> 3 3 </svg> 4 -
formscrm/tags/4.3.0/includes/crm-library/class-crmlib-brevo.php
r3415133 r3460128 238 238 $list_id = isset( $settings['fc_crm_module'] ) ? (int) $settings['fc_crm_module'] : ''; 239 239 240 // List of standard Brevo contact fields that should be at root level. 241 // All other fields are automatically treated as custom attributes. 242 // See https://developers.brevo.com/reference/createcontact. 243 $standard_fields = array( 244 'email', 245 'ext_id', 246 'emailBlacklisted', 247 'smsBlacklisted', 248 'listIds', 249 'unlinkListIds', 250 'updateEnabled', 251 'smtpBlacklistSender', 252 ); 253 240 254 $subscriber = array(); 241 255 $subscriber['listIds'] = array( $list_id ); 242 256 foreach ( $merge_vars as $element ) { 243 if ( false === strpos( $element['name'], '|' ) ) { 244 $subscriber[ $element['name'] ] = $element['value']; 257 $field_name = $element['name']; 258 $field_value = $element['value']; 259 260 // Check if field contains pipe separator (attributes|FIELDNAME). 261 if ( false === strpos( $field_name, '|' ) ) { 262 // No pipe - check if it's a standard field. 263 if ( in_array( $field_name, $standard_fields, true ) ) { 264 // Standard field - add to root level. 265 $subscriber[ $field_name ] = $field_value; 266 } else { 267 // Custom attribute - add to attributes object. 268 $subscriber['attributes'][ $field_name ] = $field_value; 269 } 245 270 } else { 246 $key = str_replace( 'attributes|', '', $element['name'] ); 247 $subscriber['attributes'][ $key ] = $element['value']; 271 // Pipe found - extract attribute name and add to attributes. 272 $key = str_replace( 'attributes|', '', $field_name ); 273 $subscriber['attributes'][ $key ] = $field_value; 248 274 } 249 275 } -
formscrm/tags/4.3.0/includes/crm-library/class-crmlib-clientify.php
r3424189 r3460128 3 3 * Clientify connect library 4 4 * 5 * Has functions to login, list fields and create lead º5 * Has functions to login, list fields and create lead 6 6 * 7 7 * @author closemarketing … … 874 874 } elseif ( 'gdpr_accept' === $element['name'] || 'disclaimer' === $element['name'] ) { 875 875 $contact[ $element['name'] ] = empty( $element['value'] ) ? false : true; 876 } elseif ( 'birthday' === $element['name'] ) { 877 // Normalize birthday date format to YYYY-MM-DD. 878 $normalized_date = formscrm_normalize_date_format( $element['value'] ); 879 if ( false !== $normalized_date ) { 880 $contact[ $element['name'] ] = $normalized_date; 881 } 876 882 } else { 877 883 $contact[ $element['name'] ] = $element['value']; -
formscrm/tags/4.3.0/includes/formscrm-library/class-contactform7.php
r3415133 r3460128 34 34 add_action( 'wpcf7_after_save', array( $this, 'crm_save_options' ) ); 35 35 add_action( 'wpcf7_before_send_mail', array( $this, 'crm_process_entry' ) ); 36 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_autosubmit_assets' ) ); 36 37 } 37 38 … … 62 63 $cf7_crm_defaults = array(); 63 64 $cf7_crm = get_option( 'cf7_crm_' . $args->id(), $cf7_crm_defaults ); 65 $settings_module = isset( $cf7_crm['fc_crm_module'] ) ? $cf7_crm['fc_crm_module'] : ''; 64 66 ?> 65 67 <div class="metabox-holder"> 66 68 <div class="cme-main-fields"> 67 69 <p> 68 <select name="wpcf7-crm[fc_crm_type]" class="medium" onchange="jQuery(this).parents('form').submit();" id="fc_crm_type"> 70 <label for="fc_crm_type"><?php esc_html_e( 'CRM Type:', 'formscrm' ); ?></label><br /> 71 <select name="wpcf7-crm[fc_crm_type]" class="medium formscrm-autosubmit" id="fc_crm_type" data-formscrm-autosubmit="true"> 69 72 <?php 70 73 foreach ( formscrm_get_choices() as $choice ) { … … 77 80 ?> 78 81 </select> 82 <span class="formscrm-saving-indicator" style="display:none; margin-left:10px; color:#46b450;"> 83 <span class="dashicons dashicons-update-alt" style="animation: rotation 1s infinite linear;"></span> 84 <?php esc_html_e( 'Saving...', 'formscrm' ); ?> 85 </span> 79 86 </p> 80 87 <?php if ( isset( $cf7_crm['fc_crm_type'] ) && $cf7_crm['fc_crm_type'] ) { ?> … … 126 133 ?> 127 134 <p> 128 <select name="wpcf7-crm[fc_crm_module]" class="medium" onchange="jQuery(this).parents('form').submit();" id="fc_crm_module"> 135 <label for="fc_crm_module"><?php esc_html_e( 'CRM Module:', 'formscrm' ); ?></label><br /> 136 <select name="wpcf7-crm[fc_crm_module]" class="medium formscrm-autosubmit" id="fc_crm_module" data-formscrm-autosubmit="true"> 129 137 <?php 130 $ settings_module = isset( $cf7_crm['fc_crm_module'] ) ? $cf7_crm['fc_crm_module'] : '';131 foreach ( $ this->crmlib->list_modules( $cf7_crm )as $module ) {138 $modules = $this->crmlib->list_modules( $cf7_crm ); 139 foreach ( $modules as $module ) { 132 140 $value = ''; 133 141 if ( ! empty( $module['value'] ) ) { … … 145 153 echo '>' . esc_html( $module['label'] ) . '</option>'; 146 154 } 155 if ( empty( $settings_module ) || ! in_array( $settings_module, array_column( $modules, 'value' ), true ) ) { 156 $default_value = ! empty( $modules[0]['value'] ) ? $modules[0]['value'] : ''; 157 $settings_module = $default_value; 158 $cf7_crm['fc_crm_module'] = $default_value; 159 } 147 160 ?> 148 161 </select> 162 <span class="formscrm-saving-indicator" style="display:none; margin-left:10px; color:#46b450;"> 163 <span class="dashicons dashicons-update-alt" style="animation: rotation 1s infinite linear;"></span> 164 <?php esc_html_e( 'Saving...', 'formscrm' ); ?> 165 </span> 149 166 </p> 150 167 <p> … … 155 172 </div> 156 173 <?php 174 // Show API connection status. 175 if ( ! empty( $cf7_crm['fc_crm_type'] ) ) { 176 formscrm_render_connection_status( $cf7_crm, 'html' ); 177 } 178 157 179 if ( ! empty( $this->crmlib ) ) { 158 180 $login_crm = $this->crmlib->login( $cf7_crm ); 159 181 if ( is_array( $login_crm ) && isset( $login_crm['status'] ) && 'error' === $login_crm['status'] ) { 160 echo '<p>' . esc_html__( 'We could not login to the CRM', 'formscrm' ) . ' ' . esc_html( $login_crm['message'] ) . '</p>';161 182 return; 162 183 } 163 184 164 185 if ( false === $login_crm ) { 165 echo '<p>' . esc_html__( 'We could not login to the CRM', 'formscrm' ) . '</p>';166 186 return; 167 187 } 168 188 } 169 189 170 if ( isset( $cf7_crm['fc_crm_module'] ) && $cf7_crm['fc_crm_module']) {171 $crm_fields = $this->crmlib->list_fields( $cf7_crm, $ cf7_crm['fc_crm_module']);190 if ( $settings_module ) { 191 $crm_fields = $this->crmlib->list_fields( $cf7_crm, $settings_module ); 172 192 $cf7_form = WPCF7_ContactForm::get_instance( $args->id() ); 173 193 $form_fields = ! empty( $cf7_form ) ? $cf7_form->scan_form_tags() : array(); … … 275 295 276 296 $form_info = array( 277 'form_type' => 'Contact Form 7', 278 'form_id' => $contact_form->id(), 279 'form_name' => $contact_form->title(), 297 'form_type' => 'contactform7', 298 'form_type_title' => 'Contact Form 7', 299 'form_id' => $contact_form->id(), 300 'form_name' => $contact_form->title(), 280 301 ); 281 302 … … 310 331 } 311 332 333 // Process dynamic values (shortcodes). 334 $value = $this->fill_dynamic_value( $value, $submitted_data ); 335 312 336 $merge_vars[] = array( 313 337 'name' => $crm_key, … … 318 342 return $merge_vars; 319 343 } 344 345 /** 346 * Enqueue auto-submit assets for CF7 settings 347 * 348 * @param string $hook Hook suffix for the current admin page. 349 * @return void 350 */ 351 public function enqueue_autosubmit_assets( $hook ) { 352 // Only load on CF7 edit pages. 353 if ( 'toplevel_page_wpcf7' !== $hook ) { 354 return; 355 } 356 357 // Enqueue CSS (reusing admin styles for consistency). 358 wp_enqueue_style( 359 'formscrm-admin', 360 FORMSCRM_PLUGIN_URL . 'includes/assets/formscrm-admin.css', 361 array(), 362 FORMSCRM_VERSION, 363 'all' 364 ); 365 366 // Enqueue JavaScript. 367 wp_enqueue_script( 368 'formscrm-cf7-autosubmit', 369 FORMSCRM_PLUGIN_URL . 'includes/assets/js/cf7-autosubmit.js', 370 array(), 371 FORMSCRM_VERSION, 372 true 373 ); 374 } 375 /** 376 * Fills dynamic value with shortcode support. 377 * 378 * Supports {id:field_name} syntax to reference other form field values. 379 * Example: "Customer: {id:your-name} - {id:your-email}" 380 * 381 * @param string $field_value Field value that may contain shortcodes. 382 * @param array $submitted_data All submitted form data. 383 * @return string Processed field value with shortcodes replaced. 384 */ 385 private function fill_dynamic_value( $field_value, $submitted_data ) { 386 if ( ! str_contains( $field_value, '{id:' ) ) { 387 return $field_value; 388 } 389 390 // Generate dynamic value. 391 $matches = array(); 392 preg_match_all( '/{([^}]*)}/', $field_value, $matches ); 393 if ( empty( $matches[1] ) ) { 394 return $field_value; 395 } 396 397 foreach ( $matches[1] as $match ) { 398 $field_options = explode( ':', $match ); 399 if ( ! isset( $field_options[1] ) || 'id' !== $field_options[0] ) { 400 continue; 401 } 402 403 $field_name = $field_options[1]; 404 if ( ! isset( $submitted_data[ $field_name ] ) ) { 405 continue; 406 } 407 408 // Get the value from submitted data. 409 $entry_value = $submitted_data[ $field_name ]; 410 411 // Handle array values (checkboxes, etc.). 412 if ( is_array( $entry_value ) ) { 413 $entry_value = implode( ', ', $entry_value ); 414 } 415 416 // Replace the shortcode with the actual value. 417 $field_value = str_replace( '{' . $match . '}', $entry_value, $field_value ); 418 } 419 420 return $field_value; 421 } 320 422 } 321 423 -
formscrm/tags/4.3.0/includes/formscrm-library/class-elementor.php
r3415133 r3460128 16 16 } 17 17 18 use ElementorPro\Modules\Forms\Submissions\Database\Query;19 20 18 /** 21 19 * Action Class … … 183 181 ); 184 182 183 // API Connection Status indicator. 184 $widget->add_control( 185 'fc_connection_status_info', 186 array( 187 'type' => \Elementor\Controls_Manager::RAW_HTML, 188 'raw' => '<div class="formscrm-elementor-status-container" id="formscrm-connection-status">' . 189 '<div style="padding: 12px; background: #f9f9f9; border-left: 4px solid #0073aa; border-radius: 4px; margin: 10px 0;">' . 190 '<div style="display: flex; align-items: center; gap: 8px;">' . 191 '<strong style="color: #23282d;">' . esc_html__( 'API Connection Status:', 'formscrm' ) . '</strong> ' . 192 '<span style="display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 3px; background: #999; color: white; font-size: 12px; font-weight: bold;">' . 193 '<span style="margin-right: 5px;">○</span>' . esc_html__( 'Not verified', 'formscrm' ) . 194 '</span>' . 195 '</div>' . 196 '<p style="margin: 8px 0 0 0; color: #666; font-size: 12px;">' . esc_html__( 'Click "Connect" button below to verify your CRM credentials', 'formscrm' ) . '</p>' . 197 '</div></div>', 198 'content_classes' => 'formscrm-connection-status-wrapper', 199 'separator' => 'before', 200 ) 201 ); 202 185 203 $widget->add_control( 186 204 'connect_crm', … … 188 206 'label' => esc_html__( 'Connect CRM', 'formscrm' ), 189 207 'type' => \Elementor\Controls_Manager::BUTTON, 190 'separator' => 'before',191 208 'button_type' => 'info', 192 209 'text' => esc_html__( 'Connect', 'formscrm' ), … … 281 298 282 299 $form_info = array( 283 'form_type' => 'Elementor', 284 'form_id' => isset( $settings['form_id'] ) ? $settings['form_id'] : ( isset( $settings['id'] ) ? $settings['id'] : '' ), 285 'form_name' => isset( $settings['form_name'] ) ? $settings['form_name'] : '', 300 'form_type' => 'elementor', 301 'form_type_title' => 'Elementor', 302 'form_id' => isset( $settings['form_id'] ) ? $settings['form_id'] : ( isset( $settings['id'] ) ? $settings['id'] : '' ), 303 'form_name' => isset( $settings['form_name'] ) ? $settings['form_name'] : '', 286 304 ); 287 305 -
formscrm/tags/4.3.0/includes/formscrm-library/class-gravityforms-widget.php
r3415133 r3460128 22 22 public function __construct() { 23 23 add_filter( 'gform_entry_detail_meta_boxes', array( $this, 'widget_resend_entries' ), 10, 3 ); 24 add_action( 'gform_post_add_feed', array( $this, 'clear_feeds_cache' ), 10, 2 ); 25 add_action( 'gform_post_update_feed', array( $this, 'clear_feeds_cache' ), 10, 2 ); 26 add_action( 'gform_post_delete_feed', array( $this, 'clear_feeds_cache' ), 10, 2 ); 27 } 28 29 /** 30 * Get feeds with caching and error handling to improve performance. 31 * 32 * @param int $form_id Form ID. 33 * @return array Array of feeds. 34 */ 35 private function get_feeds_cached( $form_id ) { 36 $cache_key = 'formscrm_feeds_' . $form_id; 37 $feeds = get_transient( $cache_key ); 38 39 if ( false === $feeds ) { 40 try { 41 // Increase max execution time temporarily for this operation. 42 $original_time_limit = ini_get( 'max_execution_time' ); 43 if ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) ) { 44 set_time_limit( 60 ); 45 } 46 47 $feeds = GFCRM::get_instance()->get_feeds( null, $form_id, 'formscrm', true ); 48 49 // Restore original time limit. 50 if ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) ) { 51 set_time_limit( (int) $original_time_limit ); 52 } 53 54 // Only cache if we got valid data. 55 if ( is_array( $feeds ) ) { 56 set_transient( $cache_key, $feeds, 5 * MINUTE_IN_SECONDS ); 57 } else { 58 $feeds = array(); 59 } 60 } catch ( Exception $e ) { 61 $feeds = array(); 62 } 63 } 64 65 return is_array( $feeds ) ? $feeds : array(); 66 } 67 68 /** 69 * Clear feeds cache for a form. 70 * 71 * @param int $feed_id Feed ID. 72 * @param array $form_id Form ID. 73 * @return void 74 */ 75 public function clear_feeds_cache( $feed_id, $form_id ) { 76 if ( ! empty( $form_id ) ) { 77 $cache_key = 'formscrm_feeds_' . $form_id; 78 delete_transient( $cache_key ); 79 } 24 80 } 25 81 … … 34 90 public function widget_resend_entries( $meta_boxes, $entry, $form ) { 35 91 $meta_boxes['formscrm'] = array( 36 'title' => esc_html__( ' Resend Entry to CRM', 'formscrm' ),92 'title' => esc_html__( 'FormsCRM: Resend', 'formscrm' ), 37 93 'callback' => array( $this, 'resend_metabox' ), 38 94 'context' => 'side', … … 42 98 return $meta_boxes; 43 99 } 100 44 101 /** 45 102 * The callback used to echo the content to the meta box. … … 48 105 */ 49 106 public function resend_metabox( $args ) { 50 $html = ''; 51 $action = 'formscrm_process_feeds'; 52 $form = ! empty( $args['form'] ) ? $args['form'] : array(); 53 $form_id = isset( $form['id'] ) ? (int) $form['id'] : 0; 54 $entry = ! empty( $args['entry'] ) ? $args['entry'] : array(); 55 56 $feeds = GFCRM::get_instance()->get_feeds( null, $form_id, 'formscrm', true ); 57 58 if ( rgpost( 'action' ) === $action ) { 59 check_admin_referer( 'gforms_save_entry', 'gforms_save_entry' ); 60 $html .= '<p><strong>' . esc_html__( 'Feeds processed:', 'formscrm' ) . '</strong></p>'; 61 $html .= '<ul>'; 62 107 $html = ''; 108 $action = 'formscrm_process_feeds'; 109 $form = ! empty( $args['form'] ) ? $args['form'] : array(); 110 $form_id = isset( $form['id'] ) ? (int) $form['id'] : 0; 111 $entry = ! empty( $args['entry'] ) ? $args['entry'] : array(); 112 $entry_id = isset( $entry['id'] ) ? (int) $entry['id'] : 0; 113 114 // Use cached version with error handling. 115 $feeds = $this->get_feeds_cached( $form_id ); 116 117 // Check if action was triggered. 118 $resend_action = isset( $_POST['formscrm_action'] ) ? sanitize_text_field( wp_unslash( $_POST['formscrm_action'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified below. 119 120 if ( $action === $resend_action ) { 121 // Verify nonce for security. 122 if ( ! isset( $_POST['formscrm_resend_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['formscrm_resend_nonce'] ) ), 'formscrm_resend_entry_' . $entry_id ) ) { 123 $html .= '<p style="color:red;">' . esc_html__( 'Security check failed. Please try again.', 'formscrm' ) . '</p>'; 124 } else { 125 // Get selected feed(s). 126 $selected_feeds = isset( $_POST['formscrm_selected_feeds'] ) && is_array( $_POST['formscrm_selected_feeds'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['formscrm_selected_feeds'] ) ) : array(); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified above. 127 $process_all = in_array( 'all', $selected_feeds, true ); 128 129 $html .= '<p><strong>' . esc_html__( 'Feeds processed:', 'formscrm' ) . '</strong></p>'; 130 $html .= '<ul>'; 131 132 foreach ( $feeds as $feed ) { 133 if ( ! $feed['is_active'] || $form_id !== (int) $feed['form_id'] ) { 134 continue; 135 } 136 137 // Process feed if "all" is selected or if this specific feed is selected. 138 if ( $process_all || in_array( (string) $feed['id'], $selected_feeds, true ) ) { 139 GFCRM::get_instance()->process_feed( $feed, $entry, $form ); 140 $html .= '<li>'; 141 $html .= sprintf( 142 // translators: %s is the name of the feed. 143 __( 'Feed: %s', 'formscrm' ), 144 isset( $feed['meta']['feedName'] ) ? $feed['meta']['feedName'] : $feed['id'], 145 ); 146 $html .= '</li>'; 147 } 148 } 149 $html .= '</ul>'; 150 } 151 } 152 153 // Always show the form with available feeds. 154 $html .= '<p>' . esc_html__( 'This will resend the entry to the CRM.', 'formscrm' ) . '</p>'; 155 $html .= '<p>' . esc_html__( 'Actual feeds actived:', 'formscrm' ) . '</p>'; 156 $html .= '<ul>'; 157 158 $active_feeds = 0; 159 foreach ( $feeds as $feed ) { 160 if ( ! $feed['is_active'] || $form_id !== (int) $feed['form_id'] ) { 161 continue; 162 } 163 ++$active_feeds; 164 $html .= '<li>'; 165 $html .= sprintf( 166 // translators: %s is the name of the feed. 167 __( 'Feed: %s', 'formscrm' ), 168 isset( $feed['meta']['feedName'] ) ? $feed['meta']['feedName'] : $feed['id'], 169 ); 170 $html .= '</li>'; 171 } 172 $html .= '</ul>'; 173 174 if ( $active_feeds > 0 ) { 175 $html .= '<br/>'; 176 $html .= '<form method="post" style="display:inline;">'; 177 $html .= wp_nonce_field( 'formscrm_resend_entry_' . $entry_id, 'formscrm_resend_nonce', true, false ); 178 $html .= '<input type="hidden" name="formscrm_action" value="' . esc_attr( $action ) . '" />'; 179 $html .= '<label for="formscrm_feed_select">' . esc_html__( 'Select Feeds to Resend', 'formscrm' ) . ':</label> '; 180 $html .= '<select id="formscrm_feed_select" name="formscrm_selected_feeds[]" style="min-width:200px;">'; 181 $html .= '<option value="all">' . esc_html__( 'All feeds', 'formscrm' ) . '</option>'; 63 182 foreach ( $feeds as $feed ) { 64 183 if ( ! $feed['is_active'] || $form_id !== (int) $feed['form_id'] ) { 65 184 continue; 66 185 } 67 GFCRM::get_instance()->process_feed( $feed, $entry, $form ); 68 $html .= '<li>'; 69 $html .= sprintf( 70 // translators: %s is the name of the feed. 71 __( 'Feed: %s', 'formscrm' ), 72 isset( $feed['meta']['feedName'] ) ? $feed['meta']['feedName'] : $feed['id'], 186 $feed_name = isset( $feed['meta']['feedName'] ) ? $feed['meta']['feedName'] : $feed['id']; 187 $html .= sprintf( 188 '<option value="%s">%s</option>', 189 esc_attr( $feed['id'] ), 190 esc_html( $feed_name ) 73 191 ); 74 $html .= '</li>'; 75 } 76 $html .= '</ul>'; 192 } 193 $html .= '</select><br/><br/>'; 194 $html .= sprintf( 195 '<input type="submit" value="%s" class="button button-primary" />', 196 esc_attr__( 'Resend Entry', 'formscrm' ) 197 ); 198 $html .= '</form>'; 77 199 } else { 78 $html .= '<p>' . esc_html__( 'This will resend the entry to the CRM.', 'formscrm' ) . '</p>'; 79 $html .= '<p>' . esc_html__( 'Actual feeds actived:', 'formscrm' ) . '</p>'; 80 $html .= '<ul>'; 81 82 foreach ( $feeds as $feed ) { 83 if ( ! $feed['is_active'] || $form_id !== (int) $feed['form_id'] ) { 84 continue; 85 } 86 $html .= '<li>'; 87 $html .= sprintf( 88 // translators: %s is the name of the feed. 89 __( 'Feed: %s', 'formscrm' ), 90 isset( $feed['meta']['feedName'] ) ? $feed['meta']['feedName'] : $feed['id'], 91 ); 92 $html .= '</li>'; 93 } 94 $html .= '</ul>'; 95 $html .= '</br>'; 96 // Add the 'Process Feeds' button. 97 $html .= sprintf( 98 '<input type="submit" value="%s" class="button" onclick="jQuery(\'#action\').val(\'%s\');" />', 99 __( 'Resend Entry', 'formscrm' ), 100 $action 101 ); 102 } 103 echo wp_kses_post( $html ); 200 $html .= '<p><em>' . esc_html__( 'No active feeds found for this form.', 'formscrm' ) . '</em></p>'; 201 } 202 203 echo wp_kses( 204 $html, 205 array( 206 'p' => array( 'style' => array() ), 207 'strong' => array(), 208 'ul' => array(), 209 'li' => array(), 210 'br' => array(), 211 'form' => array( 212 'method' => array(), 213 'style' => array(), 214 ), 215 'input' => array( 216 'type' => array(), 217 'name' => array(), 218 'value' => array(), 219 'class' => array(), 220 'id' => array(), 221 ), 222 'select' => array( 223 'name' => array(), 224 'id' => array(), 225 'multiple' => array(), 226 'style' => array(), 227 ), 228 'option' => array( 229 'value' => array(), 230 ), 231 'label' => array( 232 'for' => array(), 233 ), 234 'em' => array(), 235 ) 236 ); 104 237 } 105 238 } -
formscrm/tags/4.3.0/includes/formscrm-library/class-gravityforms.php
r3415133 r3460128 145 145 146 146 $this->ensure_upgrade(); 147 148 // Add custom columns to forms list. 149 if ( is_admin() ) { 150 add_filter( 'gform_form_list_columns', array( $this, 'add_feeds_column' ), 10 ); 151 add_action( 'gform_form_list_column_formscrm_feeds', array( $this, 'display_feeds_column' ), 10, 1 ); 152 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_forms_list_styles' ) ); 153 } 154 } 155 156 /** 157 * Enqueue styles for forms list page 158 * 159 * @param string $hook Current admin page hook. 160 * @return void 161 */ 162 public function enqueue_forms_list_styles( $hook ) { 163 // Only load on Gravity Forms pages. 164 if ( 'toplevel_page_gf_edit_forms' === $hook || strpos( $hook, 'gf_' ) !== false ) { 165 wp_enqueue_style( 166 'formscrm-forms-list', 167 FORMSCRM_PLUGIN_URL . 'includes/assets/formscrm-admin.css', 168 array(), 169 FORMSCRM_VERSION 170 ); 171 } 147 172 } 148 173 … … 260 285 $fields = $this->get_crm_fields( true, array(), 'settings' ); 261 286 287 // API Connection Status. 288 $fields = array_merge( 289 $fields, 290 array( 291 array( 292 'label' => __( 'API Connection Status', 'formscrm' ), 293 'type' => 'connection_status', 294 'name' => 'fc_crm_connection_status', 295 ), 296 ), 297 ); 298 262 299 // Expert Mode. 263 300 $fields = array_merge( … … 309 346 310 347 return $api_key_field . '</br>' . $caption; 348 } 349 350 /** 351 * Settings Connection Status field. 352 * 353 * Renders the API connection status indicator for plugin settings. 354 * 355 * @param array $field Field configuration. 356 * @param bool $display Whether to display or return the HTML. 357 * @return string HTML output. 358 */ 359 public function settings_connection_status( $field, $display = true ) { 360 $settings = $this->get_plugin_settings(); 361 $help_text = __( 'Save settings and reload the page to test the connection.', 'formscrm' ); 362 $html = formscrm_get_connection_status_html( $settings, 'badge', $help_text ); 363 364 if ( $display ) { 365 formscrm_render_connection_status( $settings, 'badge', $help_text ); 366 } 367 368 return $html; 369 } 370 371 /** 372 * Settings Feed Connection Status field. 373 * 374 * Renders the API connection status indicator for feed settings. 375 * 376 * @param array $field Field configuration. 377 * @param bool $display Whether to display or return the HTML. 378 * @return string HTML output. 379 */ 380 public function settings_feed_connection_status( $field, $display = true ) { 381 $settings = $this->get_api_settings_custom(); 382 $status_data = formscrm_check_connection_status( $settings ); 383 $help_text = ''; 384 385 // Show help text only for error states. 386 if ( 'disconnected' === $status_data['status'] || 'error' === $status_data['status'] ) { 387 $help_text = __( 'Please check your CRM credentials in the FormsCRM settings.', 'formscrm' ); 388 } 389 390 $html = formscrm_build_status_html( $status_data, 'badge', $help_text ); 391 392 if ( $display ) { 393 formscrm_render_connection_status( $settings, 'badge', $help_text ); 394 } 395 396 return $html; 311 397 } 312 398 … … 414 500 $login_crm = $this->login_api_crm(); 415 501 502 // Add connection status field. 503 $crm_feed_fields[] = array( 504 'name' => 'fc_feed_connection_status', 505 'label' => __( 'API Connection Status', 'formscrm' ), 506 'type' => 'feed_connection_status', 507 ); 508 416 509 if ( is_array( $login_crm ) && isset( $login_crm['status'] ) && 'error' === $login_crm['status'] ) { 417 $crm_feed_fields[] = array(418 'name' => 'fc_login_result',419 'label' => __( 'We could not login to the CRM', 'formscrm' ) . ' ' . $login_crm['message'],420 'type' => 'hidden',421 );422 510 return $crm_feed_fields; 423 511 } 424 512 425 513 if ( false === $login_crm ) { 426 $crm_feed_fields[] = array( 427 'name' => 'fc_login_result', 428 'label' => __( 'We could not login to the CRM', 'formscrm' ), 429 'type' => 'hidden', 430 ); 514 // Connection status field already added above, no additional fields needed. 515 return $crm_feed_fields; 431 516 } else { 432 517 $module = $this->get_actual_feed_value( 'fc_crm_module', $feed_settings ); … … 576 661 update_option( 'fc_crm_upgrade', 1 ); 577 662 return true; 663 } 664 665 /** 666 * Add feeds column to forms list 667 * 668 * @param array $columns Existing columns. 669 * @return array Modified columns. 670 */ 671 public function add_feeds_column( $columns ) { 672 $columns['formscrm_feeds'] = esc_html__( 'Connected Feeds', 'formscrm' ); 673 return $columns; 674 } 675 676 /** 677 * Display feeds column content 678 * 679 * @param array $form Form object. 680 * @return void 681 */ 682 public function display_feeds_column( $form ) { 683 // Get form ID from array or object. 684 $form_id = 0; 685 686 if ( is_array( $form ) ) { 687 $form_id = isset( $form['id'] ) ? absint( $form['id'] ) : 0; 688 } elseif ( is_object( $form ) ) { 689 $form_id = isset( $form->id ) ? absint( $form->id ) : 0; 690 } 691 692 // If no form ID, show disconnected. 693 if ( ! $form_id ) { 694 echo '<span class="gform-status-indicator gform-status--inactive">● ' . esc_html__( 'Disconnected', 'formscrm' ) . '</span>'; 695 return; 696 } 697 698 try { 699 // Get feeds for this form. 700 $feeds = $this->get_feeds( $form_id ); 701 702 // No feeds - show Disconnected. 703 if ( empty( $feeds ) || ! is_array( $feeds ) ) { 704 return; 705 } 706 707 // Has feeds - show Connected. 708 $feed_count = count( $feeds ); 709 710 echo '<div class="formscrm-feeds-wrapper">'; 711 echo '<span class="gform-status-indicator gform-status--active">' . esc_html__( 'Connected', 'formscrm' ) . '</span>'; 712 713 // Show feed details. 714 echo '<div class="formscrm-feeds-list">'; 715 716 foreach ( $feeds as $feed ) { 717 if ( ! is_array( $feed ) || empty( $feed['meta'] ) ) { 718 continue; 719 } 720 721 $feed_name = isset( $feed['meta']['feedName'] ) ? $feed['meta']['feedName'] : __( 'Unnamed Feed', 'formscrm' ); 722 $crm_type = ''; 723 724 // Get CRM type. 725 if ( ! empty( $feed['meta']['fc_crm_custom_type'] ) && 'no' !== $feed['meta']['fc_crm_custom_type'] ) { 726 $crm_type = $feed['meta']['fc_crm_custom_type']; 727 } else { 728 $settings = $this->get_plugin_settings(); 729 if ( ! empty( $settings['fc_crm_type'] ) ) { 730 $crm_type = $settings['fc_crm_type']; 731 } 732 } 733 734 $is_active = ! empty( $feed['is_active'] ); 735 $status = $is_active ? '✓' : '✗'; 736 $color = $is_active ? '#46b450' : '#dc3232'; 737 $title = $is_active ? __( 'Active', 'formscrm' ) : __( 'Inactive', 'formscrm' ); 738 739 echo '<div class="formscrm-feed-item">'; 740 printf( 741 '<span style="color: %s; font-weight: bold;" title="%s">%s</span> ', 742 esc_attr( $color ), 743 esc_attr( $title ), 744 esc_html( $status ) 745 ); 746 echo '<span class="formscrm-feed-name">' . esc_html( $feed_name ) . '</span>'; 747 748 if ( ! empty( $crm_type ) ) { 749 echo ' <span class="formscrm-feed-crm">(' . esc_html( ucfirst( $crm_type ) ) . ')</span>'; 750 } 751 echo '</div>'; 752 } 753 754 // Show total if more than 1. 755 if ( $feed_count > 1 ) { 756 echo '<div class="formscrm-feed-total">'; 757 printf( 758 /* translators: %d: number of feeds */ 759 esc_html__( 'Total: %d feeds', 'formscrm' ), 760 absint( $feed_count ) 761 ); 762 echo '</div>'; 763 } 764 765 echo '</div>'; // .formscrm-feeds-list 766 echo '</div>'; // .formscrm-feeds-wrapper 767 } catch ( Exception $e ) { 768 echo '<span class="gform-status-indicator gform-status--inactive">● ' . esc_html__( 'Error', 'formscrm' ) . '</span>'; 769 } 578 770 } 579 771 … … 681 873 682 874 $form_info = array( 683 'form_type' => 'Gravity Forms', 684 'form_id' => isset( $form['id'] ) ? $form['id'] : '', 685 'form_name' => isset( $form['title'] ) ? $form['title'] : '', 686 'entry_id' => isset( $entry['id'] ) ? $entry['id'] : '', 875 'form_type' => 'gravityforms', 876 'form_type_title' => 'Gravity Forms', 877 'form_id' => isset( $form['id'] ) ? $form['id'] : '', 878 'form_name' => isset( $form['title'] ) ? $form['title'] : '', 879 'entry_id' => isset( $entry['id'] ) ? $entry['id'] : '', 687 880 ); 688 881 -
formscrm/tags/4.3.0/includes/formscrm-library/class-woocommerce.php
r3415133 r3460128 194 194 'id' => 'wc_settings_formscrm_section_end', 195 195 ); 196 197 // Show API connection status. 198 if ( ! empty( $wc_formscrm['fc_crm_type'] ) ) { 199 formscrm_render_connection_status( $wc_formscrm, 'notice' ); 200 } 201 196 202 if ( ! empty( $this->crmlib ) && ! empty( $wc_formscrm ) ) { 197 203 $login_crm = $this->crmlib->login( $wc_formscrm ); 198 204 if ( is_array( $login_crm ) && isset( $login_crm['status'] ) && 'error' === $login_crm['status'] ) { 199 echo '<div class="notice notice-error"><p>' . esc_html__( 'We could not login to the CRM', 'formscrm' ) . ' ' . esc_html( $login_crm['message'] ) . '</p></div>';200 205 return $settings_crm; 201 206 } 202 207 203 208 if ( false === $login_crm ) { 204 echo '<div class="notice notice-error"><p>' . esc_html__( 'We could not login to the CRM', 'formscrm' ) . '</p></div>';205 209 return $settings_crm; 206 210 } … … 261 265 if ( 'error' === $response_result['status'] ) { 262 266 $form_info = array( 263 'form_type' => 'WooCommerce', 264 'form_id' => 'checkout', 265 'form_name' => 'WooCommerce Checkout', 266 'entry_id' => $order_id, 267 'form_type' => 'woocommerce', 268 'form_type_title' => 'WooCommerce', 269 'form_id' => 'checkout', 270 'form_name' => 'WooCommerce Checkout', 271 'entry_id' => $order_id, 267 272 ); 268 273 -
formscrm/tags/4.3.0/includes/formscrm-library/class-wpforms.php
r3415133 r3460128 174 174 if ( 'error' === $api_status ) { 175 175 $form_info = array( 176 'form_type' => 'WPForms', 177 'form_id' => $form_id, 178 'form_name' => isset( $form_data['settings']['form_title'] ) ? $form_data['settings']['form_title'] : '', 179 'entry_id' => $entry_id, 176 'form_type' => 'wpforms', 177 'form_type_title' => 'WPForms', 178 'form_id' => $form_id, 179 'form_name' => isset( $form_data['settings']['form_title'] ) ? $form_data['settings']['form_title'] : '', 180 'entry_id' => $entry_id, 180 181 ); 181 182 formscrm_alert_error( $settings['fc_crm_type'], 'Error ' . $api_message, $merge_vars, '', '', $form_info ); … … 517 518 */ 518 519 public function output_options( $connection_id = '', $connection = array() ) { 519 520 // Double opt in and a welcome email are defined in the List options on FormsCRM. 521 // They can't be controlled via the API. 522 return ''; 520 $account_id = ! empty( $connection['account_id'] ) ? $connection['account_id'] : ''; 521 $html = ''; 522 523 if ( ! empty( $account_id ) ) { 524 $settings = $this->api_connect( $account_id ); 525 if ( is_array( $settings ) && ! empty( $settings['fc_crm_type'] ) ) { 526 // Get connection status and display it prominently. 527 $status_html = formscrm_get_connection_status_html( $settings, 'badge' ); 528 529 // Wrap in a visible container with proper styling. 530 $html = '<div class="wpforms-provider-connection-status" style="margin: 15px 0; padding: 12px; background: #f9f9f9; border-radius: 4px; border-left: 4px solid #0073aa;">'; 531 $html .= '<div style="display: flex; align-items: center; justify-content: space-between;">'; 532 $html .= '<div>'; 533 $html .= '<strong style="display: block; margin-bottom: 5px; color: #23282d;">' . esc_html__( 'CRM Connection:', 'formscrm' ) . '</strong>'; 534 $html .= $status_html; 535 $html .= '</div>'; 536 537 // Add CRM type info. 538 if ( ! empty( $settings['fc_crm_type'] ) ) { 539 $html .= '<div style="text-align: right;">'; 540 $html .= '<span style="display: block; font-size: 11px; color: #666; text-transform: uppercase; letter-spacing: 0.5px;">' . esc_html__( 'CRM Type', 'formscrm' ) . '</span>'; 541 $html .= '<strong style="font-size: 14px; color: #0073aa;">' . esc_html( ucfirst( $settings['fc_crm_type'] ) ) . '</strong>'; 542 $html .= '</div>'; 543 } 544 545 $html .= '</div>'; 546 $html .= '</div>'; 547 } 548 } 549 550 return $html; 523 551 } 524 552 -
formscrm/tags/4.3.0/includes/formscrm-library/elementor-ajax.php
r3415133 r3460128 51 51 // 1. Check connection to CRM 52 52 $crmtype = isset( $_POST['crmSettings']['fc_crm_type'] ) ? sanitize_text_field( wp_unslash( $_POST['crmSettings']['fc_crm_type'] ) ) : ''; 53 $crmlib = null;54 53 55 54 if ( empty( $crmtype ) ) { … … 57 56 } 58 57 59 $crmname = strtolower( $crmtype ); 60 $crmclassname = str_replace( ' ', '', $crmname ); 61 $crmclassname = 'CRMLIB_' . strtoupper( $crmclassname ); 62 $crmname = str_replace( ' ', '_', $crmname ); 63 64 $array_path = formscrm_get_crmlib_path(); 65 if ( isset( $array_path[ $crmname ] ) ) { 66 include_once $array_path[ $crmname ]; 67 } 68 69 formscrm_debug_message( $array_path[ $crmname ] ); 70 71 if ( ! class_exists( $crmclassname ) ) { 72 wp_send_json_error( __( 'Class not found', 'formscrm' ) ); 73 } 74 75 $crmlib = new $crmclassname(); 58 // Load CRM library class using helper function. 59 $crmlib = formscrm_get_api_class( $crmtype ); 60 61 if ( ! $crmlib ) { 62 wp_send_json_error( __( 'Could not load CRM library', 'formscrm' ) ); 63 } 76 64 77 65 $crm_settings_raw = isset( $_POST['crmSettings'] ) ? wp_unslash( $_POST['crmSettings'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized in formscrm_elementor_process_settings(). 78 66 $post_data = formscrm_elementor_process_settings( $crm_settings_raw ); 67 68 // Check connection status. 69 $status_data = formscrm_check_connection_status( $post_data ); 70 71 // Store connection status HTML separately. 72 $status_html = formscrm_get_connection_status_html( $post_data, 'elementor' ); 73 74 // If connection failed, return error with status HTML. 75 if ( 'connected' !== $status_data['status'] ) { 76 $error_msg = __( 'Could not connect to CRM', 'formscrm' ); 77 if ( ! empty( $status_data['error_message'] ) ) { 78 $error_msg .= ': ' . $status_data['error_message']; 79 } 80 81 // Return error with status HTML for JavaScript to handle. 82 wp_send_json_error( 83 array( 84 'message' => $error_msg, 85 'status_html' => $status_html, 86 ) 87 ); 88 } 79 89 80 90 // 2. Show modules dropdown … … 196 206 } 197 207 198 wp_send_json_success( ob_get_clean() ); 208 $form_html = ob_get_clean(); 209 210 // Return success with both form HTML and status HTML. 211 wp_send_json_success( 212 array( 213 'form_html' => $form_html, 214 'status_html' => $status_html, 215 ) 216 ); 199 217 } -
formscrm/tags/4.3.0/includes/formscrm-library/helpers-functions.php
r3425471 r3460128 16 16 * 17 17 * @param string $crm_type Type of CRM. 18 * @return object| void18 * @return object|null 19 19 */ 20 20 function formscrm_get_api_class( $crm_type ) { 21 $crmname = strtolower( $crm_type ); 21 // Normalize CRM type. 22 $crmname = strtolower( trim( $crm_type ) ); 22 23 $crmclassname = str_replace( ' ', '', $crmname ); 23 $crmclassname = 'CRMLIB_' . strtoupper( $crmclassname );24 $crmclassname = 'CRMLIB_' . ucfirst( $crmclassname ); 24 25 $crmname = str_replace( ' ', '_', $crmname ); 25 26 27 formscrm_debug_message( 'Attempting to load CRM: ' . $crmname ); 28 26 29 $array_path = formscrm_get_crmlib_path(); 27 30 31 // Log available CRM paths for debugging. 32 formscrm_debug_message( 'Available CRM paths: ' . wp_json_encode( array_keys( $array_path ) ) ); 33 28 34 if ( isset( $array_path[ $crmname ] ) ) { 29 include_once $array_path[ $crmname ]; 30 formscrm_debug_message( $array_path[ $crmname ] ); 31 } 32 35 $file_path = $array_path[ $crmname ]; 36 37 // Verify file exists before including. 38 if ( ! file_exists( $file_path ) ) { 39 formscrm_debug_message( 'ERROR: CRM library file not found: ' . $file_path ); 40 return null; 41 } 42 43 include_once $file_path; 44 formscrm_debug_message( 'Included CRM library: ' . $file_path ); 45 } else { 46 formscrm_debug_message( 'ERROR: CRM path not registered for: ' . $crmname ); 47 return null; 48 } 49 50 // Verify class exists after including file. 33 51 if ( class_exists( $crmclassname ) ) { 52 formscrm_debug_message( 'Successfully created instance of: ' . $crmclassname ); 34 53 return new $crmclassname(); 35 54 } 55 56 formscrm_debug_message( 'ERROR: CRM class not found: ' . $crmclassname ); 57 return null; 58 } 59 } 60 61 if ( ! function_exists( 'formscrm_get_crm_settings' ) ) { 62 /** 63 * Get CRM settings from WordPress options 64 * 65 * @param string $form_type Type of form (gravity, woocommerce, etc). 66 * @return array Settings array. 67 */ 68 function formscrm_get_crm_settings( $form_type = '' ) { 69 $settings = array(); 70 71 // Try to get settings based on form type. 72 if ( 'gravity' === $form_type || 'gravityforms' === $form_type ) { 73 $settings = get_option( 'gravityformsaddon_formscrm_settings', array() ); 74 } elseif ( 'woocommerce' === $form_type ) { 75 $settings = get_option( 'wc_formscrm', array() ); 76 } else { 77 // Default to Gravity Forms settings as fallback. 78 $settings = get_option( 'gravityformsaddon_formscrm_settings', array() ); 79 80 // Fallback to WooCommerce settings when Gravity Forms settings are empty. 81 if ( empty( $settings ) ) { 82 $settings = get_option( 'wc_formscrm', array() ); 83 } 84 } 85 86 formscrm_debug_message( 'Retrieved CRM settings for form type: ' . $form_type ); 87 88 return $settings; 36 89 } 37 90 } … … 106 159 */ 107 160 function formscrm_alert_error( $crm, $error, $data, $url = '', $json = '', $form_info = array() ) { 161 // Log error to database. 162 global $formscrm_error_log; 163 if ( isset( $formscrm_error_log ) && method_exists( $formscrm_error_log, 'insert_log' ) ) { 164 $formscrm_error_log->insert_log( $crm, $error, $data, $url, $json, $form_info ); 165 } 166 108 167 // Get custom email or fallback to admin email. 109 168 $custom_email = get_option( 'formscrm_error_notification_email', '' ); … … 436 495 } 437 496 497 if ( ! function_exists( 'formscrm_normalize_date_format' ) ) { 498 /** 499 * Normalizes date to YYYY-MM-DD format required by APIs like Clientify. 500 * 501 * Supported input formats: 502 * - dd/mm/yyyy (European format) 503 * - dd-mm-yyyy (European format with dashes) 504 * - dd.mm.yyyy (European format with dots) 505 * - yyyy-mm-dd (ISO format - already correct) 506 * - yyyy/mm/dd (ISO format with slashes) 507 * - mm/dd/yyyy (US format) 508 * - mm-dd-yyyy (US format with dashes) 509 * - Unix timestamps 510 * 511 * @param string $date_value The date value to normalize. 512 * @return string|false Normalized date in YYYY-MM-DD format or false if invalid. 513 */ 514 function formscrm_normalize_date_format( $date_value ) { 515 if ( empty( $date_value ) ) { 516 return false; 517 } 518 519 $date_value = trim( $date_value ); 520 521 // Already in correct format YYYY-MM-DD. 522 if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $date_value ) ) { 523 return $date_value; 524 } 525 526 // Handle Unix timestamp. 527 if ( is_numeric( $date_value ) && strlen( $date_value ) >= 8 ) { 528 $timestamp = (int) $date_value; 529 $date = gmdate( 'Y-m-d', $timestamp ); 530 if ( false !== $date ) { 531 return $date; 532 } 533 } 534 535 // European format: dd/mm/yyyy or dd-mm-yyyy or dd.mm.yyyy. 536 if ( preg_match( '/^(\d{1,2})[\/\-\.](\d{1,2})[\/\-\.](\d{4})$/', $date_value, $matches ) ) { 537 $day = (int) $matches[1]; 538 $month = (int) $matches[2]; 539 $year = (int) $matches[3]; 540 541 // Validate date components. 542 if ( $day > 31 || $month > 12 ) { 543 // Could be US format mm/dd/yyyy, try swapping. 544 if ( $month <= 31 && $day <= 12 ) { 545 $temp = $day; 546 $day = $month; 547 $month = $temp; 548 } 549 } 550 551 // Validate the date. 552 if ( checkdate( $month, $day, $year ) ) { 553 return sprintf( '%04d-%02d-%02d', $year, $month, $day ); 554 } 555 } 556 557 // ISO format with slashes: yyyy/mm/dd. 558 if ( preg_match( '/^(\d{4})[\/](\d{1,2})[\/](\d{1,2})$/', $date_value, $matches ) ) { 559 $year = (int) $matches[1]; 560 $month = (int) $matches[2]; 561 $day = (int) $matches[3]; 562 563 if ( checkdate( $month, $day, $year ) ) { 564 return sprintf( '%04d-%02d-%02d', $year, $month, $day ); 565 } 566 } 567 568 // Try PHP's strtotime as last resort for other formats. 569 $timestamp = strtotime( $date_value ); 570 if ( false !== $timestamp && -1 !== $timestamp ) { 571 return gmdate( 'Y-m-d', $timestamp ); 572 } 573 574 return false; 575 } 576 } 577 438 578 if ( ! function_exists( 'formscrm_get_svg_icon' ) ) { 439 579 /** … … 458 598 } 459 599 } 600 601 if ( ! function_exists( 'formscrm_get_connection_status_html' ) ) { 602 /** 603 * Get HTML for API connection status indicator. 604 * 605 * @param array $settings CRM settings array with fc_crm_type and credentials. 606 * @param string $output_type Output type: 'html', 'notice', 'badge', or 'elementor'. 607 * @param string $help_text Optional help text to display below status. 608 * @return string HTML output for the connection status. 609 */ 610 function formscrm_get_connection_status_html( $settings, $output_type = 'html', $help_text = '' ) { 611 $status_data = formscrm_check_connection_status( $settings ); 612 613 return formscrm_build_status_html( $status_data, $output_type, $help_text ); 614 } 615 } 616 617 if ( ! function_exists( 'formscrm_check_connection_status' ) ) { 618 /** 619 * Check connection status and return status data array. 620 * 621 * @param array $settings CRM settings array with fc_crm_type and credentials. 622 * @return array Status data with keys: status, text, color, icon, error_message, crm_type. 623 */ 624 function formscrm_check_connection_status( $settings ) { 625 $crm_type = isset( $settings['fc_crm_type'] ) ? $settings['fc_crm_type'] : ''; 626 $data = array( 627 'status' => 'unknown', 628 'text' => __( 'Not configured', 'formscrm' ), 629 'color' => '#999999', 630 'icon' => '○', 631 'error_message' => '', 632 'crm_type' => $crm_type, 633 ); 634 635 if ( empty( $crm_type ) ) { 636 return $data; 637 } 638 639 $crmlib = formscrm_get_api_class( $crm_type ); 640 641 if ( ! isset( $crmlib ) || ! method_exists( $crmlib, 'login' ) ) { 642 return $data; 643 } 644 645 $login_result = $crmlib->login( $settings ); 646 $login_status = isset( $login_result['status'] ) ? $login_result['status'] : ''; 647 648 if ( is_array( $login_result ) && 'error' === $login_status ) { 649 $data['status'] = 'error'; 650 $data['text'] = __( 'Error', 'formscrm' ); 651 $data['color'] = '#dc3232'; 652 $data['icon'] = '✕'; 653 $data['error_message'] = isset( $login_result['message'] ) ? $login_result['message'] : ''; 654 } elseif ( true === $login_result || 'ok' === $login_status ) { 655 $data['status'] = 'connected'; 656 $data['text'] = __( 'Connected', 'formscrm' ); 657 $data['color'] = '#46b450'; 658 $data['icon'] = '✓'; 659 } else { 660 $data['status'] = 'disconnected'; 661 $data['text'] = __( 'Disconnected', 'formscrm' ); 662 $data['color'] = '#dc3232'; 663 $data['icon'] = '✕'; 664 } 665 666 return $data; 667 } 668 } 669 670 if ( ! function_exists( 'formscrm_build_status_html' ) ) { 671 /** 672 * Build HTML from status data. 673 * 674 * @param array $status_data Status data from formscrm_check_connection_status(). 675 * @param string $output_type Output type: 'html', 'notice', 'badge', or 'elementor'. 676 * @param string $help_text Optional help text to display below status. 677 * @return string HTML output. 678 */ 679 function formscrm_build_status_html( $status_data, $output_type = 'html', $help_text = '' ) { 680 $status = $status_data['status']; 681 $status_text = $status_data['text']; 682 $status_color = $status_data['color']; 683 $status_icon = $status_data['icon']; 684 $error_message = $status_data['error_message']; 685 $crm_type = $status_data['crm_type']; 686 $html = ''; 687 688 switch ( $output_type ) { 689 case 'notice': 690 $notice_class = 'connected' === $status ? 'notice-success' : ( 'unknown' === $status ? 'notice-warning' : 'notice-error' ); 691 $html = '<div class="notice ' . esc_attr( $notice_class ) . '" style="padding: 10px;">'; 692 $html .= '<strong>' . esc_html__( 'API Connection Status:', 'formscrm' ) . '</strong> '; 693 $html .= '<span style="color: ' . esc_attr( $status_color ) . '; font-weight: bold;">'; 694 $html .= esc_html( $status_icon ) . ' ' . esc_html( $status_text ); 695 $html .= '</span>'; 696 697 if ( ! empty( $crm_type ) ) { 698 $html .= ' <span style="color: #666;">(' . esc_html( ucfirst( $crm_type ) ) . ')</span>'; 699 } 700 701 if ( ! empty( $error_message ) ) { 702 $html .= '<br/><span style="color: #dc3232; font-size: 12px;">' . esc_html( $error_message ) . '</span>'; 703 } 704 705 $html .= '</div>'; 706 break; 707 708 case 'elementor': 709 $bg_color = 'connected' === $status ? '#f9f9f9' : ( 'unknown' === $status ? '#f9f9f9' : '#ffebee' ); 710 $border_color = 'connected' === $status ? '#46b450' : ( 'unknown' === $status ? '#0073aa' : '#dc3232' ); 711 $badge_color = 'connected' === $status ? '#46b450' : ( 'unknown' === $status ? '#999' : '#dc3232' ); 712 713 $html = '<div style="padding: 12px; background: ' . esc_attr( $bg_color ) . '; border-left: 4px solid ' . esc_attr( $border_color ) . '; border-radius: 4px; margin-bottom: 15px;">'; 714 $html .= '<div style="display: flex; align-items: center; justify-content: space-between;">'; 715 $html .= '<div style="display: flex; align-items: center; gap: 8px;">'; 716 $html .= '<strong style="color: #23282d;">' . esc_html__( 'API Connection Status:', 'formscrm' ) . '</strong> '; 717 $html .= '<span style="display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 3px; background: ' . esc_attr( $badge_color ) . '; color: white; font-size: 12px; font-weight: bold;">'; 718 $html .= '<span style="margin-right: 5px;">' . esc_html( $status_icon ) . '</span>' . esc_html( $status_text ); 719 $html .= '</span>'; 720 $html .= '</div>'; 721 722 if ( ! empty( $crm_type ) ) { 723 $html .= '<div style="text-align: right;">'; 724 $html .= '<span style="display: block; font-size: 11px; color: #666; text-transform: uppercase; letter-spacing: 0.5px;">' . esc_html__( 'CRM Type', 'formscrm' ) . '</span>'; 725 $html .= '<strong style="font-size: 14px; color: #0073aa;">' . esc_html( ucfirst( $crm_type ) ) . '</strong>'; 726 $html .= '</div>'; 727 } 728 729 $html .= '</div>'; 730 731 if ( ! empty( $error_message ) ) { 732 $html .= '<p style="margin: 8px 0 0 0; padding-top: 8px; border-top: 1px solid #ddd; color: #dc3232; font-size: 12px;"><strong>' . esc_html__( 'Error:', 'formscrm' ) . '</strong> ' . esc_html( $error_message ) . '</p>'; 733 } 734 735 $html .= '</div>'; 736 break; 737 738 case 'badge': 739 $html = '<div class="formscrm-connection-status" style="display: flex; align-items: center; gap: 10px;">'; 740 $html .= sprintf( 741 '<span class="formscrm-status-badge" style="display: inline-flex; align-items: center; padding: 6px 12px; border-radius: 4px; background-color: %1$s; color: white; font-weight: bold; font-size: 13px;">', 742 esc_attr( $status_color ) 743 ); 744 $html .= '<span style="margin-right: 6px; font-size: 14px;">' . esc_html( $status_icon ) . '</span>'; 745 $html .= esc_html( $status_text ); 746 $html .= '</span>'; 747 748 if ( ! empty( $crm_type ) ) { 749 $html .= sprintf( 750 '<span class="formscrm-crm-name" style="color: #666; font-size: 13px;">(%s)</span>', 751 esc_html( ucfirst( $crm_type ) ) 752 ); 753 } 754 755 $html .= '</div>'; 756 757 if ( ! empty( $error_message ) ) { 758 $html .= sprintf( 759 '<p class="formscrm-error-message" style="color: #dc3232; margin-top: 8px; font-size: 12px;"><strong>%s:</strong> %s</p>', 760 esc_html__( 'Error details', 'formscrm' ), 761 esc_html( $error_message ) 762 ); 763 } 764 break; 765 766 default: // 'html' 767 $html = '<div class="formscrm-connection-status" style="display: flex; align-items: center; gap: 10px; margin: 10px 0;">'; 768 $html .= '<strong>' . esc_html__( 'API Connection Status:', 'formscrm' ) . '</strong> '; 769 $html .= sprintf( 770 '<span class="formscrm-status-badge" style="display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 3px; background-color: %1$s; color: white; font-weight: bold; font-size: 12px;">', 771 esc_attr( $status_color ) 772 ); 773 $html .= '<span style="margin-right: 5px;">' . esc_html( $status_icon ) . '</span>'; 774 $html .= esc_html( $status_text ); 775 $html .= '</span>'; 776 777 if ( ! empty( $crm_type ) ) { 778 $html .= sprintf( 779 '<span style="color: #666; font-size: 12px;">(%s)</span>', 780 esc_html( ucfirst( $crm_type ) ) 781 ); 782 } 783 784 $html .= '</div>'; 785 786 if ( ! empty( $error_message ) ) { 787 $html .= sprintf( 788 '<p style="color: #dc3232; margin: 5px 0; font-size: 12px;"><strong>%s:</strong> %s</p>', 789 esc_html__( 'Error', 'formscrm' ), 790 esc_html( $error_message ) 791 ); 792 } 793 break; 794 } 795 796 // Add help text if provided. 797 if ( ! empty( $help_text ) ) { 798 $html .= '<p class="formscrm-status-help" style="color: #666; margin-top: 8px; font-size: 12px;">'; 799 $html .= esc_html( $help_text ); 800 $html .= '</p>'; 801 } 802 803 return $html; 804 } 805 } 806 807 if ( ! function_exists( 'formscrm_render_connection_status' ) ) { 808 /** 809 * Render API connection status indicator. 810 * 811 * Echoes the HTML for API connection status. 812 * 813 * @param array $settings CRM settings array with fc_crm_type and credentials. 814 * @param string $output_type Output type: 'html', 'notice', 'badge', or 'elementor'. 815 * @param string $help_text Optional help text to display below status. 816 * @return void 817 */ 818 function formscrm_render_connection_status( $settings, $output_type = 'html', $help_text = '' ) { 819 $allowed_html = array( 820 'div' => array( 821 'class' => array(), 822 'style' => array(), 823 ), 824 'span' => array( 825 'class' => array(), 826 'style' => array(), 827 ), 828 'strong' => array(), 829 'p' => array( 830 'class' => array(), 831 'style' => array(), 832 ), 833 'br' => array(), 834 ); 835 836 echo wp_kses( formscrm_get_connection_status_html( $settings, $output_type, $help_text ), $allowed_html ); 837 } 838 } -
formscrm/tags/4.3.0/includes/formscrm-library/loader.php
r3415133 r3460128 54 54 55 55 require_once 'class-gravityforms-widget.php'; 56 require_once 'class-gravityforms-markdown-export.php'; 56 57 } 57 58 -
formscrm/tags/4.3.0/readme.txt
r3425471 r3460128 5 5 Requires at least: 5.5 6 6 Tested up to: 6.9 7 Stable tag: 4. 2.18 Version: 4. 2.17 Stable tag: 4.3.0 8 Version: 4.3.0 9 9 License: GPL2 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 88 88 89 89 All Slack notifications use a compact, easy-to-read format with information presented in single lines. Messages are color-coded in red (danger) to stand out in your channel and ensure immediate attention to critical errors. 90 90 91 == Error Notifications == 91 92 **Custom Email for Error Reports** … … 106 107 The email is professionally formatted with color-coded sections for easy reading and quick troubleshooting. 107 108 109 == Error Log with Automatic Retry System == 110 111 **Track, Manage, and Automatically Retry Failed Form Submissions** 112 113 The Error Log feature provides a comprehensive interface to view, track, and manage all errors that occur when sending form submissions to your CRM. This powerful tool includes an automatic retry system that helps you troubleshoot issues and recover from failed submissions without requiring manual intervention or users to resubmit forms. 114 115 **Key Features:** 116 117 * **Automatic Retry System**: Failed entries are automatically retried up to 3 times with 1-hour intervals between attempts 118 * **Smart Retry Management**: Retries stop automatically when an entry is successfully sent or manually deleted 119 * **Complete Error Tracking**: All errors are automatically saved to the database with complete context including CRM type, error message, form information, lead data, and technical details 120 * **Advanced Filtering**: Filter errors by status (failed/success) and CRM type to quickly find specific issues 121 * **Detailed Error Information**: View complete error details including lead data, API URLs, JSON requests, and full error messages 122 * **One-Click Manual Resend**: Manually resend failed entries directly from the error log with a single click 123 * **Error Management**: Delete individual entries or clear all logs with confirmation dialogs 124 * **Pagination**: Navigate through large numbers of error logs with built-in pagination (20 entries per page) 125 * **Visual Status Tracking**: Status badges show failed and successful entries at a glance 126 * **Retry Progress Counter**: Shows retry attempts (e.g., "2/3") and displays time until next automatic retry 127 * **Responsive Design**: Fully responsive interface that works on all devices 128 129 **Automatic Retry System:** 130 131 When a form submission fails to send to your CRM: 132 133 1. The error is logged immediately and the first retry is scheduled for 1 hour later 134 2. If the retry fails, another retry is scheduled for 1 hour after that 135 3. This continues for up to 3 total attempts (original submission + 2 retries) 136 4. If an attempt succeeds, all future retries are automatically cancelled 137 5. You can manually resend at any time, which counts toward the 3-attempt limit 138 6. The interface shows the current attempt count (e.g., "1/3", "2/3") and time until next retry 139 140 **How to Use:** 141 142 1. Go to **WordPress Admin → FormsCRM → Error Log tab** 143 2. View all form submission errors in an organized table 144 3. Filter by status or CRM type to find specific errors 145 4. Click **Details** to view complete error information including retry schedule 146 5. Click **Resend** to manually retry sending a failed entry to your CRM 147 6. Click **Delete** to remove individual log entries and cancel any pending retries 148 7. Use **Clear All Logs** to remove all entries at once and cancel all pending retries 149 150 **What Information is Displayed:** 151 152 * Date and time of error 153 * CRM type (Holded, Clientify, etc.) 154 * Form information (type, ID, name, entry ID) 155 * Complete error message 156 * All lead data from the form submission 157 * API endpoint URL 158 * JSON request payload 159 * Retry attempts count (e.g., "2/3") 160 * Time until next automatic retry (e.g., "Next: in 45 minutes") 161 * Last resend date and time 162 163 The Error Log with automatic retry system helps you maintain data integrity by ensuring no form submissions are lost due to temporary errors, connectivity issues, or API downtime. The automatic retry mechanism increases the success rate of form submissions without requiring manual intervention. 164 165 == Markdown Export for GravityForms Entries == 166 167 **Export your GravityForms entries as portable, human-readable Markdown files** 168 169 The Markdown Export feature allows you to export GravityForms entries into clean, well-structured `.md` files. This makes it easy to document, share, version control, or integrate form submissions with knowledge bases, static site generators, or any Markdown-compatible system. 170 171 **Key Features:** 172 173 * **Single Entry Export**: Export individual entries directly from the entry detail page 174 * **Bulk Export**: Export multiple selected entries at once as a convenient ZIP file 175 * **Clean Formatting**: Produces readable, well-structured Markdown with proper headers and field organization 176 * **Comprehensive Field Support**: Handles all GravityForms field types including text, email, number, textarea, checkboxes, multiselect, name fields, address fields, file uploads, and list fields 177 * **Smart Content Handling**: Properly formats multi-line content, preserves line breaks, and handles file attachments with Markdown links 178 * **Metadata Included**: Each export includes form title, entry ID, submission date, and all field labels and values 179 * **Safe Character Escaping**: Automatically escapes Markdown special characters to ensure valid output 180 181 **How to Use:** 182 183 **Single Entry Export:** 184 1. Go to **Forms → Entries** in GravityForms 185 2. Click on any entry to view its details 186 3. Find the **Export to Markdown** widget in the right sidebar 187 4. Click **Download Markdown** to get the `.md` file 188 189 **Bulk Export:** 190 1. Go to **Forms → Entries** in GravityForms 191 2. Select one or multiple entries using the checkboxes 192 3. Choose **Export to Markdown** from the bulk actions dropdown 193 4. Click **Apply** to download a ZIP file containing all selected entries as separate Markdown files 194 195 **Exported Markdown Format:** 196 197 Each Markdown file includes: 198 - Form title as the main heading 199 - Entry ID and submission timestamp 200 - All filled fields organized in a clean bullet list format 201 - Field labels in bold with their corresponding values 202 - Multi-line content properly formatted with preserved line breaks 203 - File attachments as clickable Markdown links 204 205 **Use Cases:** 206 207 * Document form submissions for record-keeping 208 * Share entry data with team members in a readable format 209 * Version control form submissions using Git or similar tools 210 * Import entries into knowledge bases or wikis 211 * Generate reports or documentation from form data 212 * Backup form entries in a portable, future-proof format 213 * Integrate with static site generators (Jekyll, Hugo, etc.) 214 108 215 == Settings for Clientify == 109 216 **Instructions for adding Clientify cookie in the forms** … … 131 238 132 239 == Changelog == 133 = 4.2.1 = 240 241 = 4.3.0 = 242 * Added: API connection status indicators across all form integrations (GravityForms, WPForms, Elementor, Contact Form 7, WooCommerce). 243 * Added: Visual connection status badges with color coding - green (connected), red (error), gray (not configured). 244 * Added: Real-time connection validation with detailed error messages when authentication fails. 245 * Added: Markdown Export feature for GravityForms entries with single and bulk export capabilities. 246 * Added: Export entries as clean, well-structured Markdown files with full field type support. 247 * Added: Bulk export creates ZIP file with multiple entry Markdown files for easy sharing. 248 * Added: Automatic retry system with up to 3 attempts at 1-hour intervals, visual progress counter, and smart cancellation when entries succeed or are deleted. 249 * Added: Error Log feature with comprehensive tracking, filtering by status/CRM, detailed error views, resend capability, and pagination for easy management. 250 * Enhanced: Contact Form 7 module selection now auto-saves configuration with visual feedback. 251 * Enhanced: Responsive AJAX-based interface with color-coded status badges and synchronized manual/automatic retry system. 252 * Enhanced: Feed connection status in Forms list in Gravity Forms. 253 * Fixed: Resend button missing in Gravity Forms Entries view. 254 * Enhanced: Added feed selector in Resend Entry widget to choose between all feeds or individual feed. 255 * Added date conversion in Clientify for birthday field. 134 256 * Hotfix: Error not sending correctly entry id in webhook. 135 257 136 258 = 4.2.0 = 137 259 * Enhanced: New design for the settings page. 138 * Dedicated menu for FormsCRM settings.260 * Enhanced: Dedicated menu for FormsCRM settings. 139 261 * Improved: Added new tests for more consistent code coverage. 140 262 * Fixed: Fatal error in formscrm_debug_email_lead function. -
formscrm/trunk/formscrm.php
r3425471 r3460128 4 4 * Plugin URI : https://close.technology/wordpress-plugins/formscrm/ 5 5 * Description: Connects Forms with CRM, ERP and Email Marketing. 6 * Version: 4. 2.16 * Version: 4.3.0 7 7 * Author: CloseTechnology 8 8 * Author URI: https://close.technology … … 24 24 defined( 'ABSPATH' ) || die( 'No script kiddies please!' ); 25 25 26 define( 'FORMSCRM_VERSION', '4. 2.1' );26 define( 'FORMSCRM_VERSION', '4.3.0' ); 27 27 define( 'FORMSCRM_PLUGIN', __FILE__ ); 28 28 define( 'FORMSCRM_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); … … 92 92 // Include files. 93 93 require_once FORMSCRM_PLUGIN_PATH . '/includes/admin/class-admin-options.php'; 94 require_once FORMSCRM_PLUGIN_PATH . '/includes/admin/class-error-log.php'; 95 require_once FORMSCRM_PLUGIN_PATH . '/includes/admin/class-error-log-page.php'; 94 96 require_once FORMSCRM_PLUGIN_PATH . '/includes/formscrm-library/loader.php'; -
formscrm/trunk/includes/admin/class-admin-options.php
r3424189 r3460128 37 37 add_action( 'admin_menu', array( $this, 'add_plugin_page' ) ); 38 38 add_action( 'formscrm_settings', array( $this, 'settings_page' ) ); 39 add_action( 'formscrm_notifications', array( $this, 'notifications_page' ) ); 39 40 add_action( 'admin_init', array( $this, 'register_settings' ) ); 40 41 } … … 142 143 'action' => 'formscrm_settings', 143 144 ), 145 array( 146 'tab' => 'notifications', 147 'label' => esc_html__( 'Notifications', 'formscrm' ), 148 'action' => 'formscrm_notifications', 149 ), 150 array( 151 'tab' => 'error-log', 152 'label' => esc_html__( 'Error Log', 'formscrm' ), 153 'action' => 'formscrm_error_log_content', 154 ), 144 155 ) 145 156 ); … … 210 221 211 222 /** 212 * Renders the settings page. 213 * 214 * Displays the FormsCRM settings form with Slack integration options. 215 * 216 * @return void 217 */ 218 public function settings_page() { 219 $source_shop_url = 'es' === strtok( get_locale(), '_' ) ? 'https://close.technology/' : 'https://close.technology/en/'; 220 $utm_source = '?utm_source=WordPress+Settings&utm_medium=plugin&utm_campaign=link'; 223 * Renders the notifications page. 224 * 225 * Displays error notification settings including Slack and Email options. 226 * 227 * @return void 228 */ 229 public function notifications_page() { 221 230 $slack_webhook_url = get_option( 'formscrm_slack_webhook_url', '' ); 222 231 $error_notification_email = get_option( 'formscrm_error_notification_email', '' ); 223 232 ?> 224 233 225 <!-- Notifications Section -->234 <!-- Error Notifications Section --> 226 235 <div class="fcrm-section"> 227 236 <div class="fcrm-section-header"> … … 306 315 </div> 307 316 </div> 317 <?php 318 } 319 320 /** 321 * Renders the settings page. 322 * 323 * Displays supported forms and CRM integrations. 324 * 325 * @return void 326 */ 327 public function settings_page() { 328 $source_shop_url = 'es' === strtok( get_locale(), '_' ) ? 'https://close.technology/' : 'https://close.technology/en/'; 329 $utm_source = '?utm_source=WordPress+Settings&utm_medium=plugin&utm_campaign=link'; 330 ?> 308 331 309 332 <!-- Forms Supported Section --> -
formscrm/trunk/includes/assets/elementor-editor.js
r3290078 r3460128 30 30 }; 31 31 32 $form.html('Loading...'); 32 // Show loading state. 33 $('#formscrm-connection-status').html( 34 '<div style="padding: 12px; background: #f9f9f9; border-left: 4px solid #0073aa; border-radius: 4px;">' + 35 '<div style="display: flex; align-items: center; gap: 8px;">' + 36 '<strong style="color: #23282d;">' + 'API Connection Status:' + '</strong> ' + 37 '<span style="display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 3px; background: #0073aa; color: white; font-size: 12px; font-weight: bold;">' + 38 '<span style="margin-right: 5px;">⟳</span>' + 'Connecting...' + 39 '</span>' + 40 '</div></div>' 41 ); 42 $form.html('<p style="text-align: center; padding: 20px; color: #666;">Loading...</p>'); 33 43 34 44 $.ajax({ … … 39 49 success: function(response) { 40 50 console.log('Response:', response); 41 $form.html(response.data); 51 52 if (response.success) { 53 // Update connection status. 54 if (response.data.status_html) { 55 $('#formscrm-connection-status').html(response.data.status_html); 56 } 42 57 43 if ( !$('#fc_crm_module').val() ) { 44 // select first 45 let firstSelect = $form.find('select').first(); 46 let firstOption = firstSelect.find('option').first(); 47 firstSelect.val(firstOption.val()); 58 // Update form content with modules and fields. 59 $form.html(response.data.form_html); 60 61 if ( !$('#fc_crm_module').val() ) { 62 // select first 63 let firstSelect = $form.find('select').first(); 64 let firstOption = firstSelect.find('option').first(); 65 firstSelect.val(firstOption.val()); 66 } 67 68 $('.elementor-map-table[data-module="'+$('#fc_crm_module').val()+'"]').addClass('active'); 69 } else { 70 // Handle error response. 71 // Update connection status with error HTML if available. 72 if (response.data && response.data.status_html) { 73 $('#formscrm-connection-status').html(response.data.status_html); 74 } else { 75 let errorMessage = response.data && response.data.message ? response.data.message : (response.data || 'Connection failed'); 76 77 // Show error in status indicator. 78 $('#formscrm-connection-status').html( 79 '<div style="padding: 12px; background: #ffebee; border-left: 4px solid #dc3232; border-radius: 4px;">' + 80 '<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">' + 81 '<strong style="color: #23282d;">API Connection Status:</strong> ' + 82 '<span style="display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 3px; background: #dc3232; color: white; font-size: 12px; font-weight: bold;">' + 83 '<span style="margin-right: 5px;">✕</span>Error' + 84 '</span>' + 85 '</div>' + 86 '<p style="margin: 0; color: #dc3232; font-size: 12px;"><strong>Error:</strong> ' + errorMessage + '</p>' + 87 '</div>' 88 ); 89 } 90 $form.html(''); 48 91 } 49 50 $('.elementor-map-table[data-module="'+$('#fc_crm_module').val()+'"]').addClass('active');51 92 }, 52 93 error: function(xhr, status, error) { 53 94 console.error('AJAX Error:', error); 95 // Show error in status indicator. 96 $('#formscrm-connection-status').html( 97 '<div style="padding: 12px; background: #ffebee; border-left: 4px solid #dc3232; border-radius: 4px;">' + 98 '<div style="display: flex; align-items: center; gap: 8px;">' + 99 '<strong style="color: #23282d;">API Connection Status:</strong> ' + 100 '<span style="display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 3px; background: #dc3232; color: white; font-size: 12px; font-weight: bold;">' + 101 '<span style="margin-right: 5px;">✕</span>Error' + 102 '</span>' + 103 '</div>' + 104 '<p style="margin: 8px 0 0 0; color: #dc3232; font-size: 12px;"><strong>Error:</strong> Network error - ' + error + '</p>' + 105 '</div>' 106 ); 107 $form.html(''); 54 108 } 55 109 }); -
formscrm/trunk/includes/assets/formscrm-admin.css
r3424189 r3460128 114 114 /* Tabs Navigation */ 115 115 .fcrm-tabs-wrapper { 116 margin-bottom: 2rem;116 margin-bottom: 1rem; 117 117 background: white; 118 118 border-radius: 1rem; … … 413 413 align-items: center; 414 414 justify-content: center; 415 padding: 0. 75rem 1.5rem;415 padding: 0.40rem 1rem; 416 416 font-size: 1rem; 417 417 font-weight: 600; … … 555 555 height: 1.5rem; 556 556 } 557 558 /* Error Log Specific Styles */ 559 .fcrm-error-log-table-wrapper { 560 overflow-x: auto; 561 margin-top: 1.5rem; 562 } 563 564 .fcrm-table { 565 width: 100%; 566 border-collapse: collapse; 567 background: white; 568 } 569 570 .fcrm-table thead { 571 background: var(--fcrm-gray-50); 572 } 573 574 .fcrm-table th { 575 padding: 0.75rem 1rem; 576 text-align: left; 577 border-bottom: 2px solid var(--fcrm-gray-200); 578 font-weight: 600; 579 color: var(--fcrm-gray-700); 580 font-size: 0.875rem; 581 white-space: nowrap; 582 } 583 584 .fcrm-table td { 585 padding: 0.75rem 1rem; 586 border-bottom: 1px solid var(--fcrm-gray-200); 587 font-size: 0.875rem; 588 color: var(--fcrm-gray-700); 589 } 590 591 .fcrm-table tbody tr:hover { 592 background: var(--fcrm-gray-50); 593 } 594 595 .fcrm-button-small { 596 padding: 0.375rem 0.75rem; 597 font-size: 0.8125rem; 598 } 599 600 .fcrm-button-danger { 601 background: var(--fcrm-error); 602 color: white; 603 } 604 605 .fcrm-button-danger:hover { 606 background: #dc2626; 607 transform: translateY(-2px); 608 box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4); 609 } 610 611 .fcrm-status { 612 display: inline-block; 613 padding: 0.25rem 0.625rem; 614 border-radius: 0.375rem; 615 font-size: 0.75rem; 616 font-weight: 600; 617 text-transform: uppercase; 618 letter-spacing: 0.025em; 619 } 620 621 .fcrm-status-error { 622 background: #fef2f2; 623 color: #991b1b; 624 } 625 626 .fcrm-status-success { 627 background: #f0fdf4; 628 color: #15803d; 629 } 630 631 .fcrm-log-details { 632 background: var(--fcrm-gray-50); 633 } 634 635 .fcrm-log-details td { 636 padding: 1.5rem; 637 } 638 639 .fcrm-pagination { 640 display: flex; 641 justify-content: center; 642 gap: 0.5rem; 643 margin-top: 1.5rem; 644 flex-wrap: wrap; 645 } 646 647 .fcrm-notice-info { 648 background: #eff6ff; 649 border-left: 4px solid var(--fcrm-blue); 650 } 651 652 .fcrm-notice-info .fcrm-notice-text { 653 color: #1e40af; 654 } 655 656 /* Error Log Filters */ 657 .fcrm-error-log-filters { 658 margin-bottom: 1.25rem; 659 display: flex; 660 gap: 1rem; 661 align-items: center; 662 justify-content: space-between; 663 flex-wrap: wrap; 664 } 665 666 .fcrm-error-log-filters-form { 667 width: 84%; 668 display: flex; 669 gap: 0.625rem; 670 align-items: center; 671 flex-wrap: wrap; 672 } 673 674 .wp-core-ui select.fcrm-filter-select { 675 max-width: 200px; 676 flex-shrink: 0; 677 } 678 679 /* Stats Summary */ 680 .fcrm-stats-summary { 681 margin-bottom: 1.25rem; 682 padding: 0.9375rem; 683 background: var(--fcrm-gray-100); 684 border-radius: 0.3125rem; 685 } 686 687 /* Table Actions Column */ 688 .fcrm-table-actions { 689 text-align: center; 690 width: 280px; 691 min-width: 280px; 692 } 693 694 .fcrm-table-actions .fcrm-button { 695 margin-right: 0.3125rem; 696 } 697 698 .fcrm-table-actions .fcrm-button:last-child { 699 margin-right: 0; 700 } 701 702 /* Form Subtitle */ 703 .fcrm-form-subtitle { 704 color: var(--fcrm-gray-600); 705 } 706 707 /* Error Message Column */ 708 .fcrm-error-message { 709 max-width: 300px; 710 overflow: hidden; 711 text-overflow: ellipsis; 712 } 713 714 /* Details Row */ 715 .fcrm-log-details { 716 display: none; 717 } 718 719 .fcrm-details-cell { 720 padding: 1.25rem; 721 background: var(--fcrm-gray-50); 722 } 723 724 .fcrm-details-grid { 725 display: grid; 726 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 727 gap: 1.25rem; 728 } 729 730 .fcrm-details-section h4 { 731 margin-top: 0; 732 } 733 734 .fcrm-details-box { 735 background: white; 736 padding: 0.9375rem; 737 border-radius: 0.3125rem; 738 } 739 740 .fcrm-details-box-scroll { 741 max-height: 300px; 742 overflow-y: auto; 743 } 744 745 /* Lead Data Table */ 746 .fcrm-lead-data-table { 747 width: 100%; 748 } 749 750 .fcrm-lead-data-name { 751 padding: 0.3125rem; 752 font-weight: 600; 753 } 754 755 .fcrm-lead-data-value { 756 padding: 0.3125rem; 757 } 758 759 /* Code Blocks */ 760 .fcrm-code { 761 word-break: break-all; 762 font-size: 0.6875rem; 763 } 764 765 .fcrm-code-block { 766 display: block; 767 max-height: 150px; 768 overflow-y: auto; 769 background: var(--fcrm-gray-100); 770 padding: 0.625rem; 771 border-radius: 0.1875rem; 772 } 773 774 /* Full Width Details */ 775 .fcrm-details-full { 776 grid-column: 1 / -1; 777 } 778 779 /* Error Box */ 780 .fcrm-error-box { 781 background: #ffebee; 782 padding: 0.9375rem; 783 border-radius: 0.3125rem; 784 border-left: 4px solid #d32f2f; 785 } 786 787 @media (max-width: 768px) { 788 .fcrm-error-log-filters { 789 flex-direction: column; 790 align-items: stretch !important; 791 } 792 793 .fcrm-error-log-filters-form { 794 flex-direction: column; 795 width: 80%; 796 } 797 798 .fcrm-filter-select { 799 max-width: 100% !important; 800 } 801 802 .fcrm-table { 803 font-size: 0.75rem; 804 } 805 806 .fcrm-table th, 807 .fcrm-table td { 808 padding: 0.5rem; 809 } 810 811 .fcrm-table-actions { 812 width: auto; 813 min-width: auto; 814 } 815 816 .fcrm-table-actions .fcrm-button { 817 display: block; 818 margin-bottom: 0.3125rem; 819 margin-right: 0; 820 } 821 822 .fcrm-details-grid { 823 grid-template-columns: 1fr; 824 } 825 } 826 827 /* ================================================================= 828 Gravity Forms - Connected Feeds Column 829 ================================================================= */ 830 831 /* Column Styling */ 832 .gform-table .formscrm_feeds, 833 .wp-list-table .formscrm_feeds { 834 min-width: 200px; 835 max-width: 350px; 836 vertical-align: top; 837 } 838 839 .wp-list-table td.formscrm_feeds { 840 padding: 10px; 841 } 842 843 /* Status Badges - Using GravityForms native classes */ 844 /* .gform-status-indicator.gform-status--active for connected state */ 845 /* .gform-status-indicator.gform-status--inactive for disconnected/error state */ 846 847 /* Feeds Wrapper */ 848 .formscrm_feeds { 849 850 .gform-status--active { 851 border-radius: .75rem; 852 gap: .25rem; 853 padding-block: 0.0625rem; 854 padding-inline: 0.375rem 0.5rem; 855 font-size: 12px; 856 } 857 } 858 859 /* Feeds List */ 860 .formscrm-feeds-list { 861 display: flex; 862 flex-direction: column; 863 gap: 4px; 864 margin-top: 8px; 865 padding-top: 8px; 866 border-top: 1px solid #e0e0e0; 867 } 868 869 .formscrm-feed-item { 870 display: flex; 871 align-items: center; 872 gap: 6px; 873 line-height: 1.4; 874 } 875 876 .formscrm-feed-name { 877 font-weight: 500; 878 color: #000; 879 } 880 881 .formscrm-feed-crm { 882 color: #666; 883 font-size: 0.85em; 884 font-style: italic; 885 } 886 887 .formscrm-feed-total { 888 margin-top: 4px; 889 padding-top: 4px; 890 border-top: 1px solid #ddd; 891 color: #666; 892 font-size: 0.85em; 893 } 894 895 /* Mobile responsive */ 896 @media screen and (max-width: 782px) { 897 .gform-table .formscrm_feeds, 898 .wp-list-table .formscrm_feeds { 899 min-width: 150px; 900 } 901 902 .formscrm-status-badge { 903 padding: 4px 8px; 904 font-size: 0.85em; 905 } 906 907 .formscrm-feed-item { 908 font-size: 0.9em; 909 } 910 } -
formscrm/trunk/includes/assets/icons/icon-bell.svg
r3424189 r3460128 2 2 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"></path> 3 3 </svg> 4 -
formscrm/trunk/includes/assets/icons/icon-check-simple.svg
r3424189 r3460128 2 2 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path> 3 3 </svg> 4 -
formscrm/trunk/includes/assets/icons/icon-checkmark.svg
r3424189 r3460128 2 2 <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/> 3 3 </svg> 4 -
formscrm/trunk/includes/assets/icons/icon-document.svg
r3424189 r3460128 2 2 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path> 3 3 </svg> 4 -
formscrm/trunk/includes/assets/icons/icon-email.svg
r3424189 r3460128 2 2 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path> 3 3 </svg> 4 -
formscrm/trunk/includes/assets/icons/icon-plus.svg
r3424189 r3460128 2 2 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path> 3 3 </svg> 4 -
formscrm/trunk/includes/assets/icons/icon-slack.svg
r3424189 r3460128 2 2 <path d="M6 8a2 2 0 1 0-4 0 2 2 0 0 0 4 0zm8 0a2 2 0 1 0-4 0 2 2 0 0 0 4 0zm8 0a2 2 0 1 0-4 0 2 2 0 0 0 4 0zM6 16a2 2 0 1 0-4 0 2 2 0 0 0 4 0zm8 0a2 2 0 1 0-4 0 2 2 0 0 0 4 0zm8 0a2 2 0 1 0-4 0 2 2 0 0 0 4 0z"/> 3 3 </svg> 4 -
formscrm/trunk/includes/assets/icons/icon-support.svg
r3424189 r3460128 2 2 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192l-3.536 3.536M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-5 0a4 4 0 11-8 0 4 4 0 018 0z"></path> 3 3 </svg> 4 -
formscrm/trunk/includes/assets/icons/icon-users.svg
r3424189 r3460128 2 2 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path> 3 3 </svg> 4 -
formscrm/trunk/includes/crm-library/class-crmlib-brevo.php
r3415133 r3460128 238 238 $list_id = isset( $settings['fc_crm_module'] ) ? (int) $settings['fc_crm_module'] : ''; 239 239 240 // List of standard Brevo contact fields that should be at root level. 241 // All other fields are automatically treated as custom attributes. 242 // See https://developers.brevo.com/reference/createcontact. 243 $standard_fields = array( 244 'email', 245 'ext_id', 246 'emailBlacklisted', 247 'smsBlacklisted', 248 'listIds', 249 'unlinkListIds', 250 'updateEnabled', 251 'smtpBlacklistSender', 252 ); 253 240 254 $subscriber = array(); 241 255 $subscriber['listIds'] = array( $list_id ); 242 256 foreach ( $merge_vars as $element ) { 243 if ( false === strpos( $element['name'], '|' ) ) { 244 $subscriber[ $element['name'] ] = $element['value']; 257 $field_name = $element['name']; 258 $field_value = $element['value']; 259 260 // Check if field contains pipe separator (attributes|FIELDNAME). 261 if ( false === strpos( $field_name, '|' ) ) { 262 // No pipe - check if it's a standard field. 263 if ( in_array( $field_name, $standard_fields, true ) ) { 264 // Standard field - add to root level. 265 $subscriber[ $field_name ] = $field_value; 266 } else { 267 // Custom attribute - add to attributes object. 268 $subscriber['attributes'][ $field_name ] = $field_value; 269 } 245 270 } else { 246 $key = str_replace( 'attributes|', '', $element['name'] ); 247 $subscriber['attributes'][ $key ] = $element['value']; 271 // Pipe found - extract attribute name and add to attributes. 272 $key = str_replace( 'attributes|', '', $field_name ); 273 $subscriber['attributes'][ $key ] = $field_value; 248 274 } 249 275 } -
formscrm/trunk/includes/crm-library/class-crmlib-clientify.php
r3424189 r3460128 3 3 * Clientify connect library 4 4 * 5 * Has functions to login, list fields and create lead º5 * Has functions to login, list fields and create lead 6 6 * 7 7 * @author closemarketing … … 874 874 } elseif ( 'gdpr_accept' === $element['name'] || 'disclaimer' === $element['name'] ) { 875 875 $contact[ $element['name'] ] = empty( $element['value'] ) ? false : true; 876 } elseif ( 'birthday' === $element['name'] ) { 877 // Normalize birthday date format to YYYY-MM-DD. 878 $normalized_date = formscrm_normalize_date_format( $element['value'] ); 879 if ( false !== $normalized_date ) { 880 $contact[ $element['name'] ] = $normalized_date; 881 } 876 882 } else { 877 883 $contact[ $element['name'] ] = $element['value']; -
formscrm/trunk/includes/formscrm-library/class-contactform7.php
r3415133 r3460128 34 34 add_action( 'wpcf7_after_save', array( $this, 'crm_save_options' ) ); 35 35 add_action( 'wpcf7_before_send_mail', array( $this, 'crm_process_entry' ) ); 36 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_autosubmit_assets' ) ); 36 37 } 37 38 … … 62 63 $cf7_crm_defaults = array(); 63 64 $cf7_crm = get_option( 'cf7_crm_' . $args->id(), $cf7_crm_defaults ); 65 $settings_module = isset( $cf7_crm['fc_crm_module'] ) ? $cf7_crm['fc_crm_module'] : ''; 64 66 ?> 65 67 <div class="metabox-holder"> 66 68 <div class="cme-main-fields"> 67 69 <p> 68 <select name="wpcf7-crm[fc_crm_type]" class="medium" onchange="jQuery(this).parents('form').submit();" id="fc_crm_type"> 70 <label for="fc_crm_type"><?php esc_html_e( 'CRM Type:', 'formscrm' ); ?></label><br /> 71 <select name="wpcf7-crm[fc_crm_type]" class="medium formscrm-autosubmit" id="fc_crm_type" data-formscrm-autosubmit="true"> 69 72 <?php 70 73 foreach ( formscrm_get_choices() as $choice ) { … … 77 80 ?> 78 81 </select> 82 <span class="formscrm-saving-indicator" style="display:none; margin-left:10px; color:#46b450;"> 83 <span class="dashicons dashicons-update-alt" style="animation: rotation 1s infinite linear;"></span> 84 <?php esc_html_e( 'Saving...', 'formscrm' ); ?> 85 </span> 79 86 </p> 80 87 <?php if ( isset( $cf7_crm['fc_crm_type'] ) && $cf7_crm['fc_crm_type'] ) { ?> … … 126 133 ?> 127 134 <p> 128 <select name="wpcf7-crm[fc_crm_module]" class="medium" onchange="jQuery(this).parents('form').submit();" id="fc_crm_module"> 135 <label for="fc_crm_module"><?php esc_html_e( 'CRM Module:', 'formscrm' ); ?></label><br /> 136 <select name="wpcf7-crm[fc_crm_module]" class="medium formscrm-autosubmit" id="fc_crm_module" data-formscrm-autosubmit="true"> 129 137 <?php 130 $ settings_module = isset( $cf7_crm['fc_crm_module'] ) ? $cf7_crm['fc_crm_module'] : '';131 foreach ( $ this->crmlib->list_modules( $cf7_crm )as $module ) {138 $modules = $this->crmlib->list_modules( $cf7_crm ); 139 foreach ( $modules as $module ) { 132 140 $value = ''; 133 141 if ( ! empty( $module['value'] ) ) { … … 145 153 echo '>' . esc_html( $module['label'] ) . '</option>'; 146 154 } 155 if ( empty( $settings_module ) || ! in_array( $settings_module, array_column( $modules, 'value' ), true ) ) { 156 $default_value = ! empty( $modules[0]['value'] ) ? $modules[0]['value'] : ''; 157 $settings_module = $default_value; 158 $cf7_crm['fc_crm_module'] = $default_value; 159 } 147 160 ?> 148 161 </select> 162 <span class="formscrm-saving-indicator" style="display:none; margin-left:10px; color:#46b450;"> 163 <span class="dashicons dashicons-update-alt" style="animation: rotation 1s infinite linear;"></span> 164 <?php esc_html_e( 'Saving...', 'formscrm' ); ?> 165 </span> 149 166 </p> 150 167 <p> … … 155 172 </div> 156 173 <?php 174 // Show API connection status. 175 if ( ! empty( $cf7_crm['fc_crm_type'] ) ) { 176 formscrm_render_connection_status( $cf7_crm, 'html' ); 177 } 178 157 179 if ( ! empty( $this->crmlib ) ) { 158 180 $login_crm = $this->crmlib->login( $cf7_crm ); 159 181 if ( is_array( $login_crm ) && isset( $login_crm['status'] ) && 'error' === $login_crm['status'] ) { 160 echo '<p>' . esc_html__( 'We could not login to the CRM', 'formscrm' ) . ' ' . esc_html( $login_crm['message'] ) . '</p>';161 182 return; 162 183 } 163 184 164 185 if ( false === $login_crm ) { 165 echo '<p>' . esc_html__( 'We could not login to the CRM', 'formscrm' ) . '</p>';166 186 return; 167 187 } 168 188 } 169 189 170 if ( isset( $cf7_crm['fc_crm_module'] ) && $cf7_crm['fc_crm_module']) {171 $crm_fields = $this->crmlib->list_fields( $cf7_crm, $ cf7_crm['fc_crm_module']);190 if ( $settings_module ) { 191 $crm_fields = $this->crmlib->list_fields( $cf7_crm, $settings_module ); 172 192 $cf7_form = WPCF7_ContactForm::get_instance( $args->id() ); 173 193 $form_fields = ! empty( $cf7_form ) ? $cf7_form->scan_form_tags() : array(); … … 275 295 276 296 $form_info = array( 277 'form_type' => 'Contact Form 7', 278 'form_id' => $contact_form->id(), 279 'form_name' => $contact_form->title(), 297 'form_type' => 'contactform7', 298 'form_type_title' => 'Contact Form 7', 299 'form_id' => $contact_form->id(), 300 'form_name' => $contact_form->title(), 280 301 ); 281 302 … … 310 331 } 311 332 333 // Process dynamic values (shortcodes). 334 $value = $this->fill_dynamic_value( $value, $submitted_data ); 335 312 336 $merge_vars[] = array( 313 337 'name' => $crm_key, … … 318 342 return $merge_vars; 319 343 } 344 345 /** 346 * Enqueue auto-submit assets for CF7 settings 347 * 348 * @param string $hook Hook suffix for the current admin page. 349 * @return void 350 */ 351 public function enqueue_autosubmit_assets( $hook ) { 352 // Only load on CF7 edit pages. 353 if ( 'toplevel_page_wpcf7' !== $hook ) { 354 return; 355 } 356 357 // Enqueue CSS (reusing admin styles for consistency). 358 wp_enqueue_style( 359 'formscrm-admin', 360 FORMSCRM_PLUGIN_URL . 'includes/assets/formscrm-admin.css', 361 array(), 362 FORMSCRM_VERSION, 363 'all' 364 ); 365 366 // Enqueue JavaScript. 367 wp_enqueue_script( 368 'formscrm-cf7-autosubmit', 369 FORMSCRM_PLUGIN_URL . 'includes/assets/js/cf7-autosubmit.js', 370 array(), 371 FORMSCRM_VERSION, 372 true 373 ); 374 } 375 /** 376 * Fills dynamic value with shortcode support. 377 * 378 * Supports {id:field_name} syntax to reference other form field values. 379 * Example: "Customer: {id:your-name} - {id:your-email}" 380 * 381 * @param string $field_value Field value that may contain shortcodes. 382 * @param array $submitted_data All submitted form data. 383 * @return string Processed field value with shortcodes replaced. 384 */ 385 private function fill_dynamic_value( $field_value, $submitted_data ) { 386 if ( ! str_contains( $field_value, '{id:' ) ) { 387 return $field_value; 388 } 389 390 // Generate dynamic value. 391 $matches = array(); 392 preg_match_all( '/{([^}]*)}/', $field_value, $matches ); 393 if ( empty( $matches[1] ) ) { 394 return $field_value; 395 } 396 397 foreach ( $matches[1] as $match ) { 398 $field_options = explode( ':', $match ); 399 if ( ! isset( $field_options[1] ) || 'id' !== $field_options[0] ) { 400 continue; 401 } 402 403 $field_name = $field_options[1]; 404 if ( ! isset( $submitted_data[ $field_name ] ) ) { 405 continue; 406 } 407 408 // Get the value from submitted data. 409 $entry_value = $submitted_data[ $field_name ]; 410 411 // Handle array values (checkboxes, etc.). 412 if ( is_array( $entry_value ) ) { 413 $entry_value = implode( ', ', $entry_value ); 414 } 415 416 // Replace the shortcode with the actual value. 417 $field_value = str_replace( '{' . $match . '}', $entry_value, $field_value ); 418 } 419 420 return $field_value; 421 } 320 422 } 321 423 -
formscrm/trunk/includes/formscrm-library/class-elementor.php
r3415133 r3460128 16 16 } 17 17 18 use ElementorPro\Modules\Forms\Submissions\Database\Query;19 20 18 /** 21 19 * Action Class … … 183 181 ); 184 182 183 // API Connection Status indicator. 184 $widget->add_control( 185 'fc_connection_status_info', 186 array( 187 'type' => \Elementor\Controls_Manager::RAW_HTML, 188 'raw' => '<div class="formscrm-elementor-status-container" id="formscrm-connection-status">' . 189 '<div style="padding: 12px; background: #f9f9f9; border-left: 4px solid #0073aa; border-radius: 4px; margin: 10px 0;">' . 190 '<div style="display: flex; align-items: center; gap: 8px;">' . 191 '<strong style="color: #23282d;">' . esc_html__( 'API Connection Status:', 'formscrm' ) . '</strong> ' . 192 '<span style="display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 3px; background: #999; color: white; font-size: 12px; font-weight: bold;">' . 193 '<span style="margin-right: 5px;">○</span>' . esc_html__( 'Not verified', 'formscrm' ) . 194 '</span>' . 195 '</div>' . 196 '<p style="margin: 8px 0 0 0; color: #666; font-size: 12px;">' . esc_html__( 'Click "Connect" button below to verify your CRM credentials', 'formscrm' ) . '</p>' . 197 '</div></div>', 198 'content_classes' => 'formscrm-connection-status-wrapper', 199 'separator' => 'before', 200 ) 201 ); 202 185 203 $widget->add_control( 186 204 'connect_crm', … … 188 206 'label' => esc_html__( 'Connect CRM', 'formscrm' ), 189 207 'type' => \Elementor\Controls_Manager::BUTTON, 190 'separator' => 'before',191 208 'button_type' => 'info', 192 209 'text' => esc_html__( 'Connect', 'formscrm' ), … … 281 298 282 299 $form_info = array( 283 'form_type' => 'Elementor', 284 'form_id' => isset( $settings['form_id'] ) ? $settings['form_id'] : ( isset( $settings['id'] ) ? $settings['id'] : '' ), 285 'form_name' => isset( $settings['form_name'] ) ? $settings['form_name'] : '', 300 'form_type' => 'elementor', 301 'form_type_title' => 'Elementor', 302 'form_id' => isset( $settings['form_id'] ) ? $settings['form_id'] : ( isset( $settings['id'] ) ? $settings['id'] : '' ), 303 'form_name' => isset( $settings['form_name'] ) ? $settings['form_name'] : '', 286 304 ); 287 305 -
formscrm/trunk/includes/formscrm-library/class-gravityforms-widget.php
r3415133 r3460128 22 22 public function __construct() { 23 23 add_filter( 'gform_entry_detail_meta_boxes', array( $this, 'widget_resend_entries' ), 10, 3 ); 24 add_action( 'gform_post_add_feed', array( $this, 'clear_feeds_cache' ), 10, 2 ); 25 add_action( 'gform_post_update_feed', array( $this, 'clear_feeds_cache' ), 10, 2 ); 26 add_action( 'gform_post_delete_feed', array( $this, 'clear_feeds_cache' ), 10, 2 ); 27 } 28 29 /** 30 * Get feeds with caching and error handling to improve performance. 31 * 32 * @param int $form_id Form ID. 33 * @return array Array of feeds. 34 */ 35 private function get_feeds_cached( $form_id ) { 36 $cache_key = 'formscrm_feeds_' . $form_id; 37 $feeds = get_transient( $cache_key ); 38 39 if ( false === $feeds ) { 40 try { 41 // Increase max execution time temporarily for this operation. 42 $original_time_limit = ini_get( 'max_execution_time' ); 43 if ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) ) { 44 set_time_limit( 60 ); 45 } 46 47 $feeds = GFCRM::get_instance()->get_feeds( null, $form_id, 'formscrm', true ); 48 49 // Restore original time limit. 50 if ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) ) { 51 set_time_limit( (int) $original_time_limit ); 52 } 53 54 // Only cache if we got valid data. 55 if ( is_array( $feeds ) ) { 56 set_transient( $cache_key, $feeds, 5 * MINUTE_IN_SECONDS ); 57 } else { 58 $feeds = array(); 59 } 60 } catch ( Exception $e ) { 61 $feeds = array(); 62 } 63 } 64 65 return is_array( $feeds ) ? $feeds : array(); 66 } 67 68 /** 69 * Clear feeds cache for a form. 70 * 71 * @param int $feed_id Feed ID. 72 * @param array $form_id Form ID. 73 * @return void 74 */ 75 public function clear_feeds_cache( $feed_id, $form_id ) { 76 if ( ! empty( $form_id ) ) { 77 $cache_key = 'formscrm_feeds_' . $form_id; 78 delete_transient( $cache_key ); 79 } 24 80 } 25 81 … … 34 90 public function widget_resend_entries( $meta_boxes, $entry, $form ) { 35 91 $meta_boxes['formscrm'] = array( 36 'title' => esc_html__( ' Resend Entry to CRM', 'formscrm' ),92 'title' => esc_html__( 'FormsCRM: Resend', 'formscrm' ), 37 93 'callback' => array( $this, 'resend_metabox' ), 38 94 'context' => 'side', … … 42 98 return $meta_boxes; 43 99 } 100 44 101 /** 45 102 * The callback used to echo the content to the meta box. … … 48 105 */ 49 106 public function resend_metabox( $args ) { 50 $html = ''; 51 $action = 'formscrm_process_feeds'; 52 $form = ! empty( $args['form'] ) ? $args['form'] : array(); 53 $form_id = isset( $form['id'] ) ? (int) $form['id'] : 0; 54 $entry = ! empty( $args['entry'] ) ? $args['entry'] : array(); 55 56 $feeds = GFCRM::get_instance()->get_feeds( null, $form_id, 'formscrm', true ); 57 58 if ( rgpost( 'action' ) === $action ) { 59 check_admin_referer( 'gforms_save_entry', 'gforms_save_entry' ); 60 $html .= '<p><strong>' . esc_html__( 'Feeds processed:', 'formscrm' ) . '</strong></p>'; 61 $html .= '<ul>'; 62 107 $html = ''; 108 $action = 'formscrm_process_feeds'; 109 $form = ! empty( $args['form'] ) ? $args['form'] : array(); 110 $form_id = isset( $form['id'] ) ? (int) $form['id'] : 0; 111 $entry = ! empty( $args['entry'] ) ? $args['entry'] : array(); 112 $entry_id = isset( $entry['id'] ) ? (int) $entry['id'] : 0; 113 114 // Use cached version with error handling. 115 $feeds = $this->get_feeds_cached( $form_id ); 116 117 // Check if action was triggered. 118 $resend_action = isset( $_POST['formscrm_action'] ) ? sanitize_text_field( wp_unslash( $_POST['formscrm_action'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified below. 119 120 if ( $action === $resend_action ) { 121 // Verify nonce for security. 122 if ( ! isset( $_POST['formscrm_resend_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['formscrm_resend_nonce'] ) ), 'formscrm_resend_entry_' . $entry_id ) ) { 123 $html .= '<p style="color:red;">' . esc_html__( 'Security check failed. Please try again.', 'formscrm' ) . '</p>'; 124 } else { 125 // Get selected feed(s). 126 $selected_feeds = isset( $_POST['formscrm_selected_feeds'] ) && is_array( $_POST['formscrm_selected_feeds'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['formscrm_selected_feeds'] ) ) : array(); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified above. 127 $process_all = in_array( 'all', $selected_feeds, true ); 128 129 $html .= '<p><strong>' . esc_html__( 'Feeds processed:', 'formscrm' ) . '</strong></p>'; 130 $html .= '<ul>'; 131 132 foreach ( $feeds as $feed ) { 133 if ( ! $feed['is_active'] || $form_id !== (int) $feed['form_id'] ) { 134 continue; 135 } 136 137 // Process feed if "all" is selected or if this specific feed is selected. 138 if ( $process_all || in_array( (string) $feed['id'], $selected_feeds, true ) ) { 139 GFCRM::get_instance()->process_feed( $feed, $entry, $form ); 140 $html .= '<li>'; 141 $html .= sprintf( 142 // translators: %s is the name of the feed. 143 __( 'Feed: %s', 'formscrm' ), 144 isset( $feed['meta']['feedName'] ) ? $feed['meta']['feedName'] : $feed['id'], 145 ); 146 $html .= '</li>'; 147 } 148 } 149 $html .= '</ul>'; 150 } 151 } 152 153 // Always show the form with available feeds. 154 $html .= '<p>' . esc_html__( 'This will resend the entry to the CRM.', 'formscrm' ) . '</p>'; 155 $html .= '<p>' . esc_html__( 'Actual feeds actived:', 'formscrm' ) . '</p>'; 156 $html .= '<ul>'; 157 158 $active_feeds = 0; 159 foreach ( $feeds as $feed ) { 160 if ( ! $feed['is_active'] || $form_id !== (int) $feed['form_id'] ) { 161 continue; 162 } 163 ++$active_feeds; 164 $html .= '<li>'; 165 $html .= sprintf( 166 // translators: %s is the name of the feed. 167 __( 'Feed: %s', 'formscrm' ), 168 isset( $feed['meta']['feedName'] ) ? $feed['meta']['feedName'] : $feed['id'], 169 ); 170 $html .= '</li>'; 171 } 172 $html .= '</ul>'; 173 174 if ( $active_feeds > 0 ) { 175 $html .= '<br/>'; 176 $html .= '<form method="post" style="display:inline;">'; 177 $html .= wp_nonce_field( 'formscrm_resend_entry_' . $entry_id, 'formscrm_resend_nonce', true, false ); 178 $html .= '<input type="hidden" name="formscrm_action" value="' . esc_attr( $action ) . '" />'; 179 $html .= '<label for="formscrm_feed_select">' . esc_html__( 'Select Feeds to Resend', 'formscrm' ) . ':</label> '; 180 $html .= '<select id="formscrm_feed_select" name="formscrm_selected_feeds[]" style="min-width:200px;">'; 181 $html .= '<option value="all">' . esc_html__( 'All feeds', 'formscrm' ) . '</option>'; 63 182 foreach ( $feeds as $feed ) { 64 183 if ( ! $feed['is_active'] || $form_id !== (int) $feed['form_id'] ) { 65 184 continue; 66 185 } 67 GFCRM::get_instance()->process_feed( $feed, $entry, $form ); 68 $html .= '<li>'; 69 $html .= sprintf( 70 // translators: %s is the name of the feed. 71 __( 'Feed: %s', 'formscrm' ), 72 isset( $feed['meta']['feedName'] ) ? $feed['meta']['feedName'] : $feed['id'], 186 $feed_name = isset( $feed['meta']['feedName'] ) ? $feed['meta']['feedName'] : $feed['id']; 187 $html .= sprintf( 188 '<option value="%s">%s</option>', 189 esc_attr( $feed['id'] ), 190 esc_html( $feed_name ) 73 191 ); 74 $html .= '</li>'; 75 } 76 $html .= '</ul>'; 192 } 193 $html .= '</select><br/><br/>'; 194 $html .= sprintf( 195 '<input type="submit" value="%s" class="button button-primary" />', 196 esc_attr__( 'Resend Entry', 'formscrm' ) 197 ); 198 $html .= '</form>'; 77 199 } else { 78 $html .= '<p>' . esc_html__( 'This will resend the entry to the CRM.', 'formscrm' ) . '</p>'; 79 $html .= '<p>' . esc_html__( 'Actual feeds actived:', 'formscrm' ) . '</p>'; 80 $html .= '<ul>'; 81 82 foreach ( $feeds as $feed ) { 83 if ( ! $feed['is_active'] || $form_id !== (int) $feed['form_id'] ) { 84 continue; 85 } 86 $html .= '<li>'; 87 $html .= sprintf( 88 // translators: %s is the name of the feed. 89 __( 'Feed: %s', 'formscrm' ), 90 isset( $feed['meta']['feedName'] ) ? $feed['meta']['feedName'] : $feed['id'], 91 ); 92 $html .= '</li>'; 93 } 94 $html .= '</ul>'; 95 $html .= '</br>'; 96 // Add the 'Process Feeds' button. 97 $html .= sprintf( 98 '<input type="submit" value="%s" class="button" onclick="jQuery(\'#action\').val(\'%s\');" />', 99 __( 'Resend Entry', 'formscrm' ), 100 $action 101 ); 102 } 103 echo wp_kses_post( $html ); 200 $html .= '<p><em>' . esc_html__( 'No active feeds found for this form.', 'formscrm' ) . '</em></p>'; 201 } 202 203 echo wp_kses( 204 $html, 205 array( 206 'p' => array( 'style' => array() ), 207 'strong' => array(), 208 'ul' => array(), 209 'li' => array(), 210 'br' => array(), 211 'form' => array( 212 'method' => array(), 213 'style' => array(), 214 ), 215 'input' => array( 216 'type' => array(), 217 'name' => array(), 218 'value' => array(), 219 'class' => array(), 220 'id' => array(), 221 ), 222 'select' => array( 223 'name' => array(), 224 'id' => array(), 225 'multiple' => array(), 226 'style' => array(), 227 ), 228 'option' => array( 229 'value' => array(), 230 ), 231 'label' => array( 232 'for' => array(), 233 ), 234 'em' => array(), 235 ) 236 ); 104 237 } 105 238 } -
formscrm/trunk/includes/formscrm-library/class-gravityforms.php
r3415133 r3460128 145 145 146 146 $this->ensure_upgrade(); 147 148 // Add custom columns to forms list. 149 if ( is_admin() ) { 150 add_filter( 'gform_form_list_columns', array( $this, 'add_feeds_column' ), 10 ); 151 add_action( 'gform_form_list_column_formscrm_feeds', array( $this, 'display_feeds_column' ), 10, 1 ); 152 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_forms_list_styles' ) ); 153 } 154 } 155 156 /** 157 * Enqueue styles for forms list page 158 * 159 * @param string $hook Current admin page hook. 160 * @return void 161 */ 162 public function enqueue_forms_list_styles( $hook ) { 163 // Only load on Gravity Forms pages. 164 if ( 'toplevel_page_gf_edit_forms' === $hook || strpos( $hook, 'gf_' ) !== false ) { 165 wp_enqueue_style( 166 'formscrm-forms-list', 167 FORMSCRM_PLUGIN_URL . 'includes/assets/formscrm-admin.css', 168 array(), 169 FORMSCRM_VERSION 170 ); 171 } 147 172 } 148 173 … … 260 285 $fields = $this->get_crm_fields( true, array(), 'settings' ); 261 286 287 // API Connection Status. 288 $fields = array_merge( 289 $fields, 290 array( 291 array( 292 'label' => __( 'API Connection Status', 'formscrm' ), 293 'type' => 'connection_status', 294 'name' => 'fc_crm_connection_status', 295 ), 296 ), 297 ); 298 262 299 // Expert Mode. 263 300 $fields = array_merge( … … 309 346 310 347 return $api_key_field . '</br>' . $caption; 348 } 349 350 /** 351 * Settings Connection Status field. 352 * 353 * Renders the API connection status indicator for plugin settings. 354 * 355 * @param array $field Field configuration. 356 * @param bool $display Whether to display or return the HTML. 357 * @return string HTML output. 358 */ 359 public function settings_connection_status( $field, $display = true ) { 360 $settings = $this->get_plugin_settings(); 361 $help_text = __( 'Save settings and reload the page to test the connection.', 'formscrm' ); 362 $html = formscrm_get_connection_status_html( $settings, 'badge', $help_text ); 363 364 if ( $display ) { 365 formscrm_render_connection_status( $settings, 'badge', $help_text ); 366 } 367 368 return $html; 369 } 370 371 /** 372 * Settings Feed Connection Status field. 373 * 374 * Renders the API connection status indicator for feed settings. 375 * 376 * @param array $field Field configuration. 377 * @param bool $display Whether to display or return the HTML. 378 * @return string HTML output. 379 */ 380 public function settings_feed_connection_status( $field, $display = true ) { 381 $settings = $this->get_api_settings_custom(); 382 $status_data = formscrm_check_connection_status( $settings ); 383 $help_text = ''; 384 385 // Show help text only for error states. 386 if ( 'disconnected' === $status_data['status'] || 'error' === $status_data['status'] ) { 387 $help_text = __( 'Please check your CRM credentials in the FormsCRM settings.', 'formscrm' ); 388 } 389 390 $html = formscrm_build_status_html( $status_data, 'badge', $help_text ); 391 392 if ( $display ) { 393 formscrm_render_connection_status( $settings, 'badge', $help_text ); 394 } 395 396 return $html; 311 397 } 312 398 … … 414 500 $login_crm = $this->login_api_crm(); 415 501 502 // Add connection status field. 503 $crm_feed_fields[] = array( 504 'name' => 'fc_feed_connection_status', 505 'label' => __( 'API Connection Status', 'formscrm' ), 506 'type' => 'feed_connection_status', 507 ); 508 416 509 if ( is_array( $login_crm ) && isset( $login_crm['status'] ) && 'error' === $login_crm['status'] ) { 417 $crm_feed_fields[] = array(418 'name' => 'fc_login_result',419 'label' => __( 'We could not login to the CRM', 'formscrm' ) . ' ' . $login_crm['message'],420 'type' => 'hidden',421 );422 510 return $crm_feed_fields; 423 511 } 424 512 425 513 if ( false === $login_crm ) { 426 $crm_feed_fields[] = array( 427 'name' => 'fc_login_result', 428 'label' => __( 'We could not login to the CRM', 'formscrm' ), 429 'type' => 'hidden', 430 ); 514 // Connection status field already added above, no additional fields needed. 515 return $crm_feed_fields; 431 516 } else { 432 517 $module = $this->get_actual_feed_value( 'fc_crm_module', $feed_settings ); … … 576 661 update_option( 'fc_crm_upgrade', 1 ); 577 662 return true; 663 } 664 665 /** 666 * Add feeds column to forms list 667 * 668 * @param array $columns Existing columns. 669 * @return array Modified columns. 670 */ 671 public function add_feeds_column( $columns ) { 672 $columns['formscrm_feeds'] = esc_html__( 'Connected Feeds', 'formscrm' ); 673 return $columns; 674 } 675 676 /** 677 * Display feeds column content 678 * 679 * @param array $form Form object. 680 * @return void 681 */ 682 public function display_feeds_column( $form ) { 683 // Get form ID from array or object. 684 $form_id = 0; 685 686 if ( is_array( $form ) ) { 687 $form_id = isset( $form['id'] ) ? absint( $form['id'] ) : 0; 688 } elseif ( is_object( $form ) ) { 689 $form_id = isset( $form->id ) ? absint( $form->id ) : 0; 690 } 691 692 // If no form ID, show disconnected. 693 if ( ! $form_id ) { 694 echo '<span class="gform-status-indicator gform-status--inactive">● ' . esc_html__( 'Disconnected', 'formscrm' ) . '</span>'; 695 return; 696 } 697 698 try { 699 // Get feeds for this form. 700 $feeds = $this->get_feeds( $form_id ); 701 702 // No feeds - show Disconnected. 703 if ( empty( $feeds ) || ! is_array( $feeds ) ) { 704 return; 705 } 706 707 // Has feeds - show Connected. 708 $feed_count = count( $feeds ); 709 710 echo '<div class="formscrm-feeds-wrapper">'; 711 echo '<span class="gform-status-indicator gform-status--active">' . esc_html__( 'Connected', 'formscrm' ) . '</span>'; 712 713 // Show feed details. 714 echo '<div class="formscrm-feeds-list">'; 715 716 foreach ( $feeds as $feed ) { 717 if ( ! is_array( $feed ) || empty( $feed['meta'] ) ) { 718 continue; 719 } 720 721 $feed_name = isset( $feed['meta']['feedName'] ) ? $feed['meta']['feedName'] : __( 'Unnamed Feed', 'formscrm' ); 722 $crm_type = ''; 723 724 // Get CRM type. 725 if ( ! empty( $feed['meta']['fc_crm_custom_type'] ) && 'no' !== $feed['meta']['fc_crm_custom_type'] ) { 726 $crm_type = $feed['meta']['fc_crm_custom_type']; 727 } else { 728 $settings = $this->get_plugin_settings(); 729 if ( ! empty( $settings['fc_crm_type'] ) ) { 730 $crm_type = $settings['fc_crm_type']; 731 } 732 } 733 734 $is_active = ! empty( $feed['is_active'] ); 735 $status = $is_active ? '✓' : '✗'; 736 $color = $is_active ? '#46b450' : '#dc3232'; 737 $title = $is_active ? __( 'Active', 'formscrm' ) : __( 'Inactive', 'formscrm' ); 738 739 echo '<div class="formscrm-feed-item">'; 740 printf( 741 '<span style="color: %s; font-weight: bold;" title="%s">%s</span> ', 742 esc_attr( $color ), 743 esc_attr( $title ), 744 esc_html( $status ) 745 ); 746 echo '<span class="formscrm-feed-name">' . esc_html( $feed_name ) . '</span>'; 747 748 if ( ! empty( $crm_type ) ) { 749 echo ' <span class="formscrm-feed-crm">(' . esc_html( ucfirst( $crm_type ) ) . ')</span>'; 750 } 751 echo '</div>'; 752 } 753 754 // Show total if more than 1. 755 if ( $feed_count > 1 ) { 756 echo '<div class="formscrm-feed-total">'; 757 printf( 758 /* translators: %d: number of feeds */ 759 esc_html__( 'Total: %d feeds', 'formscrm' ), 760 absint( $feed_count ) 761 ); 762 echo '</div>'; 763 } 764 765 echo '</div>'; // .formscrm-feeds-list 766 echo '</div>'; // .formscrm-feeds-wrapper 767 } catch ( Exception $e ) { 768 echo '<span class="gform-status-indicator gform-status--inactive">● ' . esc_html__( 'Error', 'formscrm' ) . '</span>'; 769 } 578 770 } 579 771 … … 681 873 682 874 $form_info = array( 683 'form_type' => 'Gravity Forms', 684 'form_id' => isset( $form['id'] ) ? $form['id'] : '', 685 'form_name' => isset( $form['title'] ) ? $form['title'] : '', 686 'entry_id' => isset( $entry['id'] ) ? $entry['id'] : '', 875 'form_type' => 'gravityforms', 876 'form_type_title' => 'Gravity Forms', 877 'form_id' => isset( $form['id'] ) ? $form['id'] : '', 878 'form_name' => isset( $form['title'] ) ? $form['title'] : '', 879 'entry_id' => isset( $entry['id'] ) ? $entry['id'] : '', 687 880 ); 688 881 -
formscrm/trunk/includes/formscrm-library/class-woocommerce.php
r3415133 r3460128 194 194 'id' => 'wc_settings_formscrm_section_end', 195 195 ); 196 197 // Show API connection status. 198 if ( ! empty( $wc_formscrm['fc_crm_type'] ) ) { 199 formscrm_render_connection_status( $wc_formscrm, 'notice' ); 200 } 201 196 202 if ( ! empty( $this->crmlib ) && ! empty( $wc_formscrm ) ) { 197 203 $login_crm = $this->crmlib->login( $wc_formscrm ); 198 204 if ( is_array( $login_crm ) && isset( $login_crm['status'] ) && 'error' === $login_crm['status'] ) { 199 echo '<div class="notice notice-error"><p>' . esc_html__( 'We could not login to the CRM', 'formscrm' ) . ' ' . esc_html( $login_crm['message'] ) . '</p></div>';200 205 return $settings_crm; 201 206 } 202 207 203 208 if ( false === $login_crm ) { 204 echo '<div class="notice notice-error"><p>' . esc_html__( 'We could not login to the CRM', 'formscrm' ) . '</p></div>';205 209 return $settings_crm; 206 210 } … … 261 265 if ( 'error' === $response_result['status'] ) { 262 266 $form_info = array( 263 'form_type' => 'WooCommerce', 264 'form_id' => 'checkout', 265 'form_name' => 'WooCommerce Checkout', 266 'entry_id' => $order_id, 267 'form_type' => 'woocommerce', 268 'form_type_title' => 'WooCommerce', 269 'form_id' => 'checkout', 270 'form_name' => 'WooCommerce Checkout', 271 'entry_id' => $order_id, 267 272 ); 268 273 -
formscrm/trunk/includes/formscrm-library/class-wpforms.php
r3415133 r3460128 174 174 if ( 'error' === $api_status ) { 175 175 $form_info = array( 176 'form_type' => 'WPForms', 177 'form_id' => $form_id, 178 'form_name' => isset( $form_data['settings']['form_title'] ) ? $form_data['settings']['form_title'] : '', 179 'entry_id' => $entry_id, 176 'form_type' => 'wpforms', 177 'form_type_title' => 'WPForms', 178 'form_id' => $form_id, 179 'form_name' => isset( $form_data['settings']['form_title'] ) ? $form_data['settings']['form_title'] : '', 180 'entry_id' => $entry_id, 180 181 ); 181 182 formscrm_alert_error( $settings['fc_crm_type'], 'Error ' . $api_message, $merge_vars, '', '', $form_info ); … … 517 518 */ 518 519 public function output_options( $connection_id = '', $connection = array() ) { 519 520 // Double opt in and a welcome email are defined in the List options on FormsCRM. 521 // They can't be controlled via the API. 522 return ''; 520 $account_id = ! empty( $connection['account_id'] ) ? $connection['account_id'] : ''; 521 $html = ''; 522 523 if ( ! empty( $account_id ) ) { 524 $settings = $this->api_connect( $account_id ); 525 if ( is_array( $settings ) && ! empty( $settings['fc_crm_type'] ) ) { 526 // Get connection status and display it prominently. 527 $status_html = formscrm_get_connection_status_html( $settings, 'badge' ); 528 529 // Wrap in a visible container with proper styling. 530 $html = '<div class="wpforms-provider-connection-status" style="margin: 15px 0; padding: 12px; background: #f9f9f9; border-radius: 4px; border-left: 4px solid #0073aa;">'; 531 $html .= '<div style="display: flex; align-items: center; justify-content: space-between;">'; 532 $html .= '<div>'; 533 $html .= '<strong style="display: block; margin-bottom: 5px; color: #23282d;">' . esc_html__( 'CRM Connection:', 'formscrm' ) . '</strong>'; 534 $html .= $status_html; 535 $html .= '</div>'; 536 537 // Add CRM type info. 538 if ( ! empty( $settings['fc_crm_type'] ) ) { 539 $html .= '<div style="text-align: right;">'; 540 $html .= '<span style="display: block; font-size: 11px; color: #666; text-transform: uppercase; letter-spacing: 0.5px;">' . esc_html__( 'CRM Type', 'formscrm' ) . '</span>'; 541 $html .= '<strong style="font-size: 14px; color: #0073aa;">' . esc_html( ucfirst( $settings['fc_crm_type'] ) ) . '</strong>'; 542 $html .= '</div>'; 543 } 544 545 $html .= '</div>'; 546 $html .= '</div>'; 547 } 548 } 549 550 return $html; 523 551 } 524 552 -
formscrm/trunk/includes/formscrm-library/elementor-ajax.php
r3415133 r3460128 51 51 // 1. Check connection to CRM 52 52 $crmtype = isset( $_POST['crmSettings']['fc_crm_type'] ) ? sanitize_text_field( wp_unslash( $_POST['crmSettings']['fc_crm_type'] ) ) : ''; 53 $crmlib = null;54 53 55 54 if ( empty( $crmtype ) ) { … … 57 56 } 58 57 59 $crmname = strtolower( $crmtype ); 60 $crmclassname = str_replace( ' ', '', $crmname ); 61 $crmclassname = 'CRMLIB_' . strtoupper( $crmclassname ); 62 $crmname = str_replace( ' ', '_', $crmname ); 63 64 $array_path = formscrm_get_crmlib_path(); 65 if ( isset( $array_path[ $crmname ] ) ) { 66 include_once $array_path[ $crmname ]; 67 } 68 69 formscrm_debug_message( $array_path[ $crmname ] ); 70 71 if ( ! class_exists( $crmclassname ) ) { 72 wp_send_json_error( __( 'Class not found', 'formscrm' ) ); 73 } 74 75 $crmlib = new $crmclassname(); 58 // Load CRM library class using helper function. 59 $crmlib = formscrm_get_api_class( $crmtype ); 60 61 if ( ! $crmlib ) { 62 wp_send_json_error( __( 'Could not load CRM library', 'formscrm' ) ); 63 } 76 64 77 65 $crm_settings_raw = isset( $_POST['crmSettings'] ) ? wp_unslash( $_POST['crmSettings'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized in formscrm_elementor_process_settings(). 78 66 $post_data = formscrm_elementor_process_settings( $crm_settings_raw ); 67 68 // Check connection status. 69 $status_data = formscrm_check_connection_status( $post_data ); 70 71 // Store connection status HTML separately. 72 $status_html = formscrm_get_connection_status_html( $post_data, 'elementor' ); 73 74 // If connection failed, return error with status HTML. 75 if ( 'connected' !== $status_data['status'] ) { 76 $error_msg = __( 'Could not connect to CRM', 'formscrm' ); 77 if ( ! empty( $status_data['error_message'] ) ) { 78 $error_msg .= ': ' . $status_data['error_message']; 79 } 80 81 // Return error with status HTML for JavaScript to handle. 82 wp_send_json_error( 83 array( 84 'message' => $error_msg, 85 'status_html' => $status_html, 86 ) 87 ); 88 } 79 89 80 90 // 2. Show modules dropdown … … 196 206 } 197 207 198 wp_send_json_success( ob_get_clean() ); 208 $form_html = ob_get_clean(); 209 210 // Return success with both form HTML and status HTML. 211 wp_send_json_success( 212 array( 213 'form_html' => $form_html, 214 'status_html' => $status_html, 215 ) 216 ); 199 217 } -
formscrm/trunk/includes/formscrm-library/helpers-functions.php
r3425471 r3460128 16 16 * 17 17 * @param string $crm_type Type of CRM. 18 * @return object| void18 * @return object|null 19 19 */ 20 20 function formscrm_get_api_class( $crm_type ) { 21 $crmname = strtolower( $crm_type ); 21 // Normalize CRM type. 22 $crmname = strtolower( trim( $crm_type ) ); 22 23 $crmclassname = str_replace( ' ', '', $crmname ); 23 $crmclassname = 'CRMLIB_' . strtoupper( $crmclassname );24 $crmclassname = 'CRMLIB_' . ucfirst( $crmclassname ); 24 25 $crmname = str_replace( ' ', '_', $crmname ); 25 26 27 formscrm_debug_message( 'Attempting to load CRM: ' . $crmname ); 28 26 29 $array_path = formscrm_get_crmlib_path(); 27 30 31 // Log available CRM paths for debugging. 32 formscrm_debug_message( 'Available CRM paths: ' . wp_json_encode( array_keys( $array_path ) ) ); 33 28 34 if ( isset( $array_path[ $crmname ] ) ) { 29 include_once $array_path[ $crmname ]; 30 formscrm_debug_message( $array_path[ $crmname ] ); 31 } 32 35 $file_path = $array_path[ $crmname ]; 36 37 // Verify file exists before including. 38 if ( ! file_exists( $file_path ) ) { 39 formscrm_debug_message( 'ERROR: CRM library file not found: ' . $file_path ); 40 return null; 41 } 42 43 include_once $file_path; 44 formscrm_debug_message( 'Included CRM library: ' . $file_path ); 45 } else { 46 formscrm_debug_message( 'ERROR: CRM path not registered for: ' . $crmname ); 47 return null; 48 } 49 50 // Verify class exists after including file. 33 51 if ( class_exists( $crmclassname ) ) { 52 formscrm_debug_message( 'Successfully created instance of: ' . $crmclassname ); 34 53 return new $crmclassname(); 35 54 } 55 56 formscrm_debug_message( 'ERROR: CRM class not found: ' . $crmclassname ); 57 return null; 58 } 59 } 60 61 if ( ! function_exists( 'formscrm_get_crm_settings' ) ) { 62 /** 63 * Get CRM settings from WordPress options 64 * 65 * @param string $form_type Type of form (gravity, woocommerce, etc). 66 * @return array Settings array. 67 */ 68 function formscrm_get_crm_settings( $form_type = '' ) { 69 $settings = array(); 70 71 // Try to get settings based on form type. 72 if ( 'gravity' === $form_type || 'gravityforms' === $form_type ) { 73 $settings = get_option( 'gravityformsaddon_formscrm_settings', array() ); 74 } elseif ( 'woocommerce' === $form_type ) { 75 $settings = get_option( 'wc_formscrm', array() ); 76 } else { 77 // Default to Gravity Forms settings as fallback. 78 $settings = get_option( 'gravityformsaddon_formscrm_settings', array() ); 79 80 // Fallback to WooCommerce settings when Gravity Forms settings are empty. 81 if ( empty( $settings ) ) { 82 $settings = get_option( 'wc_formscrm', array() ); 83 } 84 } 85 86 formscrm_debug_message( 'Retrieved CRM settings for form type: ' . $form_type ); 87 88 return $settings; 36 89 } 37 90 } … … 106 159 */ 107 160 function formscrm_alert_error( $crm, $error, $data, $url = '', $json = '', $form_info = array() ) { 161 // Log error to database. 162 global $formscrm_error_log; 163 if ( isset( $formscrm_error_log ) && method_exists( $formscrm_error_log, 'insert_log' ) ) { 164 $formscrm_error_log->insert_log( $crm, $error, $data, $url, $json, $form_info ); 165 } 166 108 167 // Get custom email or fallback to admin email. 109 168 $custom_email = get_option( 'formscrm_error_notification_email', '' ); … … 436 495 } 437 496 497 if ( ! function_exists( 'formscrm_normalize_date_format' ) ) { 498 /** 499 * Normalizes date to YYYY-MM-DD format required by APIs like Clientify. 500 * 501 * Supported input formats: 502 * - dd/mm/yyyy (European format) 503 * - dd-mm-yyyy (European format with dashes) 504 * - dd.mm.yyyy (European format with dots) 505 * - yyyy-mm-dd (ISO format - already correct) 506 * - yyyy/mm/dd (ISO format with slashes) 507 * - mm/dd/yyyy (US format) 508 * - mm-dd-yyyy (US format with dashes) 509 * - Unix timestamps 510 * 511 * @param string $date_value The date value to normalize. 512 * @return string|false Normalized date in YYYY-MM-DD format or false if invalid. 513 */ 514 function formscrm_normalize_date_format( $date_value ) { 515 if ( empty( $date_value ) ) { 516 return false; 517 } 518 519 $date_value = trim( $date_value ); 520 521 // Already in correct format YYYY-MM-DD. 522 if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $date_value ) ) { 523 return $date_value; 524 } 525 526 // Handle Unix timestamp. 527 if ( is_numeric( $date_value ) && strlen( $date_value ) >= 8 ) { 528 $timestamp = (int) $date_value; 529 $date = gmdate( 'Y-m-d', $timestamp ); 530 if ( false !== $date ) { 531 return $date; 532 } 533 } 534 535 // European format: dd/mm/yyyy or dd-mm-yyyy or dd.mm.yyyy. 536 if ( preg_match( '/^(\d{1,2})[\/\-\.](\d{1,2})[\/\-\.](\d{4})$/', $date_value, $matches ) ) { 537 $day = (int) $matches[1]; 538 $month = (int) $matches[2]; 539 $year = (int) $matches[3]; 540 541 // Validate date components. 542 if ( $day > 31 || $month > 12 ) { 543 // Could be US format mm/dd/yyyy, try swapping. 544 if ( $month <= 31 && $day <= 12 ) { 545 $temp = $day; 546 $day = $month; 547 $month = $temp; 548 } 549 } 550 551 // Validate the date. 552 if ( checkdate( $month, $day, $year ) ) { 553 return sprintf( '%04d-%02d-%02d', $year, $month, $day ); 554 } 555 } 556 557 // ISO format with slashes: yyyy/mm/dd. 558 if ( preg_match( '/^(\d{4})[\/](\d{1,2})[\/](\d{1,2})$/', $date_value, $matches ) ) { 559 $year = (int) $matches[1]; 560 $month = (int) $matches[2]; 561 $day = (int) $matches[3]; 562 563 if ( checkdate( $month, $day, $year ) ) { 564 return sprintf( '%04d-%02d-%02d', $year, $month, $day ); 565 } 566 } 567 568 // Try PHP's strtotime as last resort for other formats. 569 $timestamp = strtotime( $date_value ); 570 if ( false !== $timestamp && -1 !== $timestamp ) { 571 return gmdate( 'Y-m-d', $timestamp ); 572 } 573 574 return false; 575 } 576 } 577 438 578 if ( ! function_exists( 'formscrm_get_svg_icon' ) ) { 439 579 /** … … 458 598 } 459 599 } 600 601 if ( ! function_exists( 'formscrm_get_connection_status_html' ) ) { 602 /** 603 * Get HTML for API connection status indicator. 604 * 605 * @param array $settings CRM settings array with fc_crm_type and credentials. 606 * @param string $output_type Output type: 'html', 'notice', 'badge', or 'elementor'. 607 * @param string $help_text Optional help text to display below status. 608 * @return string HTML output for the connection status. 609 */ 610 function formscrm_get_connection_status_html( $settings, $output_type = 'html', $help_text = '' ) { 611 $status_data = formscrm_check_connection_status( $settings ); 612 613 return formscrm_build_status_html( $status_data, $output_type, $help_text ); 614 } 615 } 616 617 if ( ! function_exists( 'formscrm_check_connection_status' ) ) { 618 /** 619 * Check connection status and return status data array. 620 * 621 * @param array $settings CRM settings array with fc_crm_type and credentials. 622 * @return array Status data with keys: status, text, color, icon, error_message, crm_type. 623 */ 624 function formscrm_check_connection_status( $settings ) { 625 $crm_type = isset( $settings['fc_crm_type'] ) ? $settings['fc_crm_type'] : ''; 626 $data = array( 627 'status' => 'unknown', 628 'text' => __( 'Not configured', 'formscrm' ), 629 'color' => '#999999', 630 'icon' => '○', 631 'error_message' => '', 632 'crm_type' => $crm_type, 633 ); 634 635 if ( empty( $crm_type ) ) { 636 return $data; 637 } 638 639 $crmlib = formscrm_get_api_class( $crm_type ); 640 641 if ( ! isset( $crmlib ) || ! method_exists( $crmlib, 'login' ) ) { 642 return $data; 643 } 644 645 $login_result = $crmlib->login( $settings ); 646 $login_status = isset( $login_result['status'] ) ? $login_result['status'] : ''; 647 648 if ( is_array( $login_result ) && 'error' === $login_status ) { 649 $data['status'] = 'error'; 650 $data['text'] = __( 'Error', 'formscrm' ); 651 $data['color'] = '#dc3232'; 652 $data['icon'] = '✕'; 653 $data['error_message'] = isset( $login_result['message'] ) ? $login_result['message'] : ''; 654 } elseif ( true === $login_result || 'ok' === $login_status ) { 655 $data['status'] = 'connected'; 656 $data['text'] = __( 'Connected', 'formscrm' ); 657 $data['color'] = '#46b450'; 658 $data['icon'] = '✓'; 659 } else { 660 $data['status'] = 'disconnected'; 661 $data['text'] = __( 'Disconnected', 'formscrm' ); 662 $data['color'] = '#dc3232'; 663 $data['icon'] = '✕'; 664 } 665 666 return $data; 667 } 668 } 669 670 if ( ! function_exists( 'formscrm_build_status_html' ) ) { 671 /** 672 * Build HTML from status data. 673 * 674 * @param array $status_data Status data from formscrm_check_connection_status(). 675 * @param string $output_type Output type: 'html', 'notice', 'badge', or 'elementor'. 676 * @param string $help_text Optional help text to display below status. 677 * @return string HTML output. 678 */ 679 function formscrm_build_status_html( $status_data, $output_type = 'html', $help_text = '' ) { 680 $status = $status_data['status']; 681 $status_text = $status_data['text']; 682 $status_color = $status_data['color']; 683 $status_icon = $status_data['icon']; 684 $error_message = $status_data['error_message']; 685 $crm_type = $status_data['crm_type']; 686 $html = ''; 687 688 switch ( $output_type ) { 689 case 'notice': 690 $notice_class = 'connected' === $status ? 'notice-success' : ( 'unknown' === $status ? 'notice-warning' : 'notice-error' ); 691 $html = '<div class="notice ' . esc_attr( $notice_class ) . '" style="padding: 10px;">'; 692 $html .= '<strong>' . esc_html__( 'API Connection Status:', 'formscrm' ) . '</strong> '; 693 $html .= '<span style="color: ' . esc_attr( $status_color ) . '; font-weight: bold;">'; 694 $html .= esc_html( $status_icon ) . ' ' . esc_html( $status_text ); 695 $html .= '</span>'; 696 697 if ( ! empty( $crm_type ) ) { 698 $html .= ' <span style="color: #666;">(' . esc_html( ucfirst( $crm_type ) ) . ')</span>'; 699 } 700 701 if ( ! empty( $error_message ) ) { 702 $html .= '<br/><span style="color: #dc3232; font-size: 12px;">' . esc_html( $error_message ) . '</span>'; 703 } 704 705 $html .= '</div>'; 706 break; 707 708 case 'elementor': 709 $bg_color = 'connected' === $status ? '#f9f9f9' : ( 'unknown' === $status ? '#f9f9f9' : '#ffebee' ); 710 $border_color = 'connected' === $status ? '#46b450' : ( 'unknown' === $status ? '#0073aa' : '#dc3232' ); 711 $badge_color = 'connected' === $status ? '#46b450' : ( 'unknown' === $status ? '#999' : '#dc3232' ); 712 713 $html = '<div style="padding: 12px; background: ' . esc_attr( $bg_color ) . '; border-left: 4px solid ' . esc_attr( $border_color ) . '; border-radius: 4px; margin-bottom: 15px;">'; 714 $html .= '<div style="display: flex; align-items: center; justify-content: space-between;">'; 715 $html .= '<div style="display: flex; align-items: center; gap: 8px;">'; 716 $html .= '<strong style="color: #23282d;">' . esc_html__( 'API Connection Status:', 'formscrm' ) . '</strong> '; 717 $html .= '<span style="display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 3px; background: ' . esc_attr( $badge_color ) . '; color: white; font-size: 12px; font-weight: bold;">'; 718 $html .= '<span style="margin-right: 5px;">' . esc_html( $status_icon ) . '</span>' . esc_html( $status_text ); 719 $html .= '</span>'; 720 $html .= '</div>'; 721 722 if ( ! empty( $crm_type ) ) { 723 $html .= '<div style="text-align: right;">'; 724 $html .= '<span style="display: block; font-size: 11px; color: #666; text-transform: uppercase; letter-spacing: 0.5px;">' . esc_html__( 'CRM Type', 'formscrm' ) . '</span>'; 725 $html .= '<strong style="font-size: 14px; color: #0073aa;">' . esc_html( ucfirst( $crm_type ) ) . '</strong>'; 726 $html .= '</div>'; 727 } 728 729 $html .= '</div>'; 730 731 if ( ! empty( $error_message ) ) { 732 $html .= '<p style="margin: 8px 0 0 0; padding-top: 8px; border-top: 1px solid #ddd; color: #dc3232; font-size: 12px;"><strong>' . esc_html__( 'Error:', 'formscrm' ) . '</strong> ' . esc_html( $error_message ) . '</p>'; 733 } 734 735 $html .= '</div>'; 736 break; 737 738 case 'badge': 739 $html = '<div class="formscrm-connection-status" style="display: flex; align-items: center; gap: 10px;">'; 740 $html .= sprintf( 741 '<span class="formscrm-status-badge" style="display: inline-flex; align-items: center; padding: 6px 12px; border-radius: 4px; background-color: %1$s; color: white; font-weight: bold; font-size: 13px;">', 742 esc_attr( $status_color ) 743 ); 744 $html .= '<span style="margin-right: 6px; font-size: 14px;">' . esc_html( $status_icon ) . '</span>'; 745 $html .= esc_html( $status_text ); 746 $html .= '</span>'; 747 748 if ( ! empty( $crm_type ) ) { 749 $html .= sprintf( 750 '<span class="formscrm-crm-name" style="color: #666; font-size: 13px;">(%s)</span>', 751 esc_html( ucfirst( $crm_type ) ) 752 ); 753 } 754 755 $html .= '</div>'; 756 757 if ( ! empty( $error_message ) ) { 758 $html .= sprintf( 759 '<p class="formscrm-error-message" style="color: #dc3232; margin-top: 8px; font-size: 12px;"><strong>%s:</strong> %s</p>', 760 esc_html__( 'Error details', 'formscrm' ), 761 esc_html( $error_message ) 762 ); 763 } 764 break; 765 766 default: // 'html' 767 $html = '<div class="formscrm-connection-status" style="display: flex; align-items: center; gap: 10px; margin: 10px 0;">'; 768 $html .= '<strong>' . esc_html__( 'API Connection Status:', 'formscrm' ) . '</strong> '; 769 $html .= sprintf( 770 '<span class="formscrm-status-badge" style="display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 3px; background-color: %1$s; color: white; font-weight: bold; font-size: 12px;">', 771 esc_attr( $status_color ) 772 ); 773 $html .= '<span style="margin-right: 5px;">' . esc_html( $status_icon ) . '</span>'; 774 $html .= esc_html( $status_text ); 775 $html .= '</span>'; 776 777 if ( ! empty( $crm_type ) ) { 778 $html .= sprintf( 779 '<span style="color: #666; font-size: 12px;">(%s)</span>', 780 esc_html( ucfirst( $crm_type ) ) 781 ); 782 } 783 784 $html .= '</div>'; 785 786 if ( ! empty( $error_message ) ) { 787 $html .= sprintf( 788 '<p style="color: #dc3232; margin: 5px 0; font-size: 12px;"><strong>%s:</strong> %s</p>', 789 esc_html__( 'Error', 'formscrm' ), 790 esc_html( $error_message ) 791 ); 792 } 793 break; 794 } 795 796 // Add help text if provided. 797 if ( ! empty( $help_text ) ) { 798 $html .= '<p class="formscrm-status-help" style="color: #666; margin-top: 8px; font-size: 12px;">'; 799 $html .= esc_html( $help_text ); 800 $html .= '</p>'; 801 } 802 803 return $html; 804 } 805 } 806 807 if ( ! function_exists( 'formscrm_render_connection_status' ) ) { 808 /** 809 * Render API connection status indicator. 810 * 811 * Echoes the HTML for API connection status. 812 * 813 * @param array $settings CRM settings array with fc_crm_type and credentials. 814 * @param string $output_type Output type: 'html', 'notice', 'badge', or 'elementor'. 815 * @param string $help_text Optional help text to display below status. 816 * @return void 817 */ 818 function formscrm_render_connection_status( $settings, $output_type = 'html', $help_text = '' ) { 819 $allowed_html = array( 820 'div' => array( 821 'class' => array(), 822 'style' => array(), 823 ), 824 'span' => array( 825 'class' => array(), 826 'style' => array(), 827 ), 828 'strong' => array(), 829 'p' => array( 830 'class' => array(), 831 'style' => array(), 832 ), 833 'br' => array(), 834 ); 835 836 echo wp_kses( formscrm_get_connection_status_html( $settings, $output_type, $help_text ), $allowed_html ); 837 } 838 } -
formscrm/trunk/includes/formscrm-library/loader.php
r3415133 r3460128 54 54 55 55 require_once 'class-gravityforms-widget.php'; 56 require_once 'class-gravityforms-markdown-export.php'; 56 57 } 57 58 -
formscrm/trunk/readme.txt
r3425471 r3460128 5 5 Requires at least: 5.5 6 6 Tested up to: 6.9 7 Stable tag: 4. 2.18 Version: 4. 2.17 Stable tag: 4.3.0 8 Version: 4.3.0 9 9 License: GPL2 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 88 88 89 89 All Slack notifications use a compact, easy-to-read format with information presented in single lines. Messages are color-coded in red (danger) to stand out in your channel and ensure immediate attention to critical errors. 90 90 91 == Error Notifications == 91 92 **Custom Email for Error Reports** … … 106 107 The email is professionally formatted with color-coded sections for easy reading and quick troubleshooting. 107 108 109 == Error Log with Automatic Retry System == 110 111 **Track, Manage, and Automatically Retry Failed Form Submissions** 112 113 The Error Log feature provides a comprehensive interface to view, track, and manage all errors that occur when sending form submissions to your CRM. This powerful tool includes an automatic retry system that helps you troubleshoot issues and recover from failed submissions without requiring manual intervention or users to resubmit forms. 114 115 **Key Features:** 116 117 * **Automatic Retry System**: Failed entries are automatically retried up to 3 times with 1-hour intervals between attempts 118 * **Smart Retry Management**: Retries stop automatically when an entry is successfully sent or manually deleted 119 * **Complete Error Tracking**: All errors are automatically saved to the database with complete context including CRM type, error message, form information, lead data, and technical details 120 * **Advanced Filtering**: Filter errors by status (failed/success) and CRM type to quickly find specific issues 121 * **Detailed Error Information**: View complete error details including lead data, API URLs, JSON requests, and full error messages 122 * **One-Click Manual Resend**: Manually resend failed entries directly from the error log with a single click 123 * **Error Management**: Delete individual entries or clear all logs with confirmation dialogs 124 * **Pagination**: Navigate through large numbers of error logs with built-in pagination (20 entries per page) 125 * **Visual Status Tracking**: Status badges show failed and successful entries at a glance 126 * **Retry Progress Counter**: Shows retry attempts (e.g., "2/3") and displays time until next automatic retry 127 * **Responsive Design**: Fully responsive interface that works on all devices 128 129 **Automatic Retry System:** 130 131 When a form submission fails to send to your CRM: 132 133 1. The error is logged immediately and the first retry is scheduled for 1 hour later 134 2. If the retry fails, another retry is scheduled for 1 hour after that 135 3. This continues for up to 3 total attempts (original submission + 2 retries) 136 4. If an attempt succeeds, all future retries are automatically cancelled 137 5. You can manually resend at any time, which counts toward the 3-attempt limit 138 6. The interface shows the current attempt count (e.g., "1/3", "2/3") and time until next retry 139 140 **How to Use:** 141 142 1. Go to **WordPress Admin → FormsCRM → Error Log tab** 143 2. View all form submission errors in an organized table 144 3. Filter by status or CRM type to find specific errors 145 4. Click **Details** to view complete error information including retry schedule 146 5. Click **Resend** to manually retry sending a failed entry to your CRM 147 6. Click **Delete** to remove individual log entries and cancel any pending retries 148 7. Use **Clear All Logs** to remove all entries at once and cancel all pending retries 149 150 **What Information is Displayed:** 151 152 * Date and time of error 153 * CRM type (Holded, Clientify, etc.) 154 * Form information (type, ID, name, entry ID) 155 * Complete error message 156 * All lead data from the form submission 157 * API endpoint URL 158 * JSON request payload 159 * Retry attempts count (e.g., "2/3") 160 * Time until next automatic retry (e.g., "Next: in 45 minutes") 161 * Last resend date and time 162 163 The Error Log with automatic retry system helps you maintain data integrity by ensuring no form submissions are lost due to temporary errors, connectivity issues, or API downtime. The automatic retry mechanism increases the success rate of form submissions without requiring manual intervention. 164 165 == Markdown Export for GravityForms Entries == 166 167 **Export your GravityForms entries as portable, human-readable Markdown files** 168 169 The Markdown Export feature allows you to export GravityForms entries into clean, well-structured `.md` files. This makes it easy to document, share, version control, or integrate form submissions with knowledge bases, static site generators, or any Markdown-compatible system. 170 171 **Key Features:** 172 173 * **Single Entry Export**: Export individual entries directly from the entry detail page 174 * **Bulk Export**: Export multiple selected entries at once as a convenient ZIP file 175 * **Clean Formatting**: Produces readable, well-structured Markdown with proper headers and field organization 176 * **Comprehensive Field Support**: Handles all GravityForms field types including text, email, number, textarea, checkboxes, multiselect, name fields, address fields, file uploads, and list fields 177 * **Smart Content Handling**: Properly formats multi-line content, preserves line breaks, and handles file attachments with Markdown links 178 * **Metadata Included**: Each export includes form title, entry ID, submission date, and all field labels and values 179 * **Safe Character Escaping**: Automatically escapes Markdown special characters to ensure valid output 180 181 **How to Use:** 182 183 **Single Entry Export:** 184 1. Go to **Forms → Entries** in GravityForms 185 2. Click on any entry to view its details 186 3. Find the **Export to Markdown** widget in the right sidebar 187 4. Click **Download Markdown** to get the `.md` file 188 189 **Bulk Export:** 190 1. Go to **Forms → Entries** in GravityForms 191 2. Select one or multiple entries using the checkboxes 192 3. Choose **Export to Markdown** from the bulk actions dropdown 193 4. Click **Apply** to download a ZIP file containing all selected entries as separate Markdown files 194 195 **Exported Markdown Format:** 196 197 Each Markdown file includes: 198 - Form title as the main heading 199 - Entry ID and submission timestamp 200 - All filled fields organized in a clean bullet list format 201 - Field labels in bold with their corresponding values 202 - Multi-line content properly formatted with preserved line breaks 203 - File attachments as clickable Markdown links 204 205 **Use Cases:** 206 207 * Document form submissions for record-keeping 208 * Share entry data with team members in a readable format 209 * Version control form submissions using Git or similar tools 210 * Import entries into knowledge bases or wikis 211 * Generate reports or documentation from form data 212 * Backup form entries in a portable, future-proof format 213 * Integrate with static site generators (Jekyll, Hugo, etc.) 214 108 215 == Settings for Clientify == 109 216 **Instructions for adding Clientify cookie in the forms** … … 131 238 132 239 == Changelog == 133 = 4.2.1 = 240 241 = 4.3.0 = 242 * Added: API connection status indicators across all form integrations (GravityForms, WPForms, Elementor, Contact Form 7, WooCommerce). 243 * Added: Visual connection status badges with color coding - green (connected), red (error), gray (not configured). 244 * Added: Real-time connection validation with detailed error messages when authentication fails. 245 * Added: Markdown Export feature for GravityForms entries with single and bulk export capabilities. 246 * Added: Export entries as clean, well-structured Markdown files with full field type support. 247 * Added: Bulk export creates ZIP file with multiple entry Markdown files for easy sharing. 248 * Added: Automatic retry system with up to 3 attempts at 1-hour intervals, visual progress counter, and smart cancellation when entries succeed or are deleted. 249 * Added: Error Log feature with comprehensive tracking, filtering by status/CRM, detailed error views, resend capability, and pagination for easy management. 250 * Enhanced: Contact Form 7 module selection now auto-saves configuration with visual feedback. 251 * Enhanced: Responsive AJAX-based interface with color-coded status badges and synchronized manual/automatic retry system. 252 * Enhanced: Feed connection status in Forms list in Gravity Forms. 253 * Fixed: Resend button missing in Gravity Forms Entries view. 254 * Enhanced: Added feed selector in Resend Entry widget to choose between all feeds or individual feed. 255 * Added date conversion in Clientify for birthday field. 134 256 * Hotfix: Error not sending correctly entry id in webhook. 135 257 136 258 = 4.2.0 = 137 259 * Enhanced: New design for the settings page. 138 * Dedicated menu for FormsCRM settings.260 * Enhanced: Dedicated menu for FormsCRM settings. 139 261 * Improved: Added new tests for more consistent code coverage. 140 262 * Fixed: Fatal error in formscrm_debug_email_lead function.
Note: See TracChangeset
for help on using the changeset viewer.