Changeset 3333095
- Timestamp:
- 07/23/2025 05:32:08 PM (7 months ago)
- Location:
- nexlifydesk
- Files:
-
- 354 added
- 26 edited
-
tags/1.0.3 (added)
-
tags/1.0.3/assets (added)
-
tags/1.0.3/assets/css (added)
-
tags/1.0.3/assets/css/nexlifydesk-admin.css (added)
-
tags/1.0.3/assets/css/nexlifydesk.css (added)
-
tags/1.0.3/assets/images (added)
-
tags/1.0.3/assets/images/dashboard-icon.png (added)
-
tags/1.0.3/assets/images/file-types (added)
-
tags/1.0.3/assets/images/file-types/document.png (added)
-
tags/1.0.3/assets/images/file-types/image.png (added)
-
tags/1.0.3/assets/images/file-types/pdf.png (added)
-
tags/1.0.3/assets/images/nexlifydesk-logo-small.png (added)
-
tags/1.0.3/assets/images/nexlifydesk-logo.png (added)
-
tags/1.0.3/assets/images/priority (added)
-
tags/1.0.3/assets/images/priority/high.png (added)
-
tags/1.0.3/assets/images/priority/low.png (added)
-
tags/1.0.3/assets/images/priority/medium.png (added)
-
tags/1.0.3/assets/images/status (added)
-
tags/1.0.3/assets/images/status/closed.png (added)
-
tags/1.0.3/assets/images/status/open.png (added)
-
tags/1.0.3/assets/images/status/pending.png (added)
-
tags/1.0.3/assets/images/status/resolved.png (added)
-
tags/1.0.3/assets/images/support-icon.png (added)
-
tags/1.0.3/assets/images/ticket-icon.png (added)
-
tags/1.0.3/assets/js (added)
-
tags/1.0.3/assets/js/admin-ticket-list.js (added)
-
tags/1.0.3/assets/js/nexlifydesk.js (added)
-
tags/1.0.3/email-source (added)
-
tags/1.0.3/email-source/nexlifydesk-email-pipe.php (added)
-
tags/1.0.3/email-source/providers (added)
-
tags/1.0.3/email-source/providers/aws-ses (added)
-
tags/1.0.3/email-source/providers/aws-ses/aws-handler.php (added)
-
tags/1.0.3/email-source/providers/google (added)
-
tags/1.0.3/email-source/providers/google/google-handler.php (added)
-
tags/1.0.3/email-source/providers/outlook (added)
-
tags/1.0.3/includes (added)
-
tags/1.0.3/includes/class-nexlifydesk-admin.php (added)
-
tags/1.0.3/includes/class-nexlifydesk-ajax.php (added)
-
tags/1.0.3/includes/class-nexlifydesk-database.php (added)
-
tags/1.0.3/includes/class-nexlifydesk-rate-limiter.php (added)
-
tags/1.0.3/includes/class-nexlifydesk-reports.php (added)
-
tags/1.0.3/includes/class-nexlifydesk-shortcodes.php (added)
-
tags/1.0.3/includes/class-nexlifydesk-tickets.php (added)
-
tags/1.0.3/includes/class-nexlifydesk-users.php (added)
-
tags/1.0.3/includes/class-support.php (added)
-
tags/1.0.3/includes/helpers.php (added)
-
tags/1.0.3/includes/nexlifydesk-functions.php (added)
-
tags/1.0.3/includes/textanalysis (added)
-
tags/1.0.3/includes/textanalysis/Comparisons (added)
-
tags/1.0.3/includes/textanalysis/Comparisons/CosineSimilarityComparison.php (added)
-
tags/1.0.3/includes/textanalysis/Documents (added)
-
tags/1.0.3/includes/textanalysis/Documents/DocumentAbstract.php (added)
-
tags/1.0.3/includes/textanalysis/Documents/TokensDocument.php (added)
-
tags/1.0.3/includes/textanalysis/Interfaces (added)
-
tags/1.0.3/includes/textanalysis/Interfaces/IDistance.php (added)
-
tags/1.0.3/includes/textanalysis/Interfaces/IExtractStrategy.php (added)
-
tags/1.0.3/includes/textanalysis/Interfaces/ISimilarity.php (added)
-
tags/1.0.3/includes/textanalysis/Interfaces/IStemmer.php (added)
-
tags/1.0.3/includes/textanalysis/Interfaces/ITokenTransformation.php (added)
-
tags/1.0.3/includes/textanalysis/SECURITY.md (added)
-
tags/1.0.3/includes/textanalysis/Tokenizers (added)
-
tags/1.0.3/includes/textanalysis/Tokenizers/GeneralTokenizer.php (added)
-
tags/1.0.3/includes/textanalysis/Tokenizers/TokenizerAbstract.php (added)
-
tags/1.0.3/includes/textanalysis/Tokenizers/WhitespaceTokenizer.php (added)
-
tags/1.0.3/languages (added)
-
tags/1.0.3/languages/nexlifydesk-de_DE_formal.mo (added)
-
tags/1.0.3/languages/nexlifydesk-de_DE_formal.po (added)
-
tags/1.0.3/languages/nexlifydesk-es_ES.mo (added)
-
tags/1.0.3/languages/nexlifydesk-es_ES.po (added)
-
tags/1.0.3/languages/nexlifydesk-fr_FR.mo (added)
-
tags/1.0.3/languages/nexlifydesk-fr_FR.po (added)
-
tags/1.0.3/languages/nexlifydesk-it_IT.mo (added)
-
tags/1.0.3/languages/nexlifydesk-it_IT.po (added)
-
tags/1.0.3/languages/nexlifydesk-ja.mo (added)
-
tags/1.0.3/languages/nexlifydesk-ja.po (added)
-
tags/1.0.3/languages/nexlifydesk-pt_BR.mo (added)
-
tags/1.0.3/languages/nexlifydesk-pt_BR.po (added)
-
tags/1.0.3/languages/nexlifydesk-pt_PT.mo (added)
-
tags/1.0.3/languages/nexlifydesk-pt_PT.po (added)
-
tags/1.0.3/languages/nexlifydesk-ru_RU.mo (added)
-
tags/1.0.3/languages/nexlifydesk-ru_RU.po (added)
-
tags/1.0.3/languages/nexlifydesk-zh_CN.mo (added)
-
tags/1.0.3/languages/nexlifydesk-zh_CN.po (added)
-
tags/1.0.3/languages/nexlifydesk.pot (added)
-
tags/1.0.3/license.txt (added)
-
tags/1.0.3/nexlifydesk.php (added)
-
tags/1.0.3/readme.txt (added)
-
tags/1.0.3/templates (added)
-
tags/1.0.3/templates/admin (added)
-
tags/1.0.3/templates/admin/imap-auth.php (added)
-
tags/1.0.3/templates/admin/partials (added)
-
tags/1.0.3/templates/admin/partials/single-reply.php (added)
-
tags/1.0.3/templates/admin/reports.php (added)
-
tags/1.0.3/templates/admin/settings.php (added)
-
tags/1.0.3/templates/admin/ticket-single.php (added)
-
tags/1.0.3/templates/admin/tickets-list.php (added)
-
tags/1.0.3/templates/emails (added)
-
tags/1.0.3/templates/emails/new_reply.php (added)
-
tags/1.0.3/templates/emails/new_ticket.php (added)
-
tags/1.0.3/templates/emails/sla_breach.php (added)
-
tags/1.0.3/templates/emails/status_changed.php (added)
-
tags/1.0.3/templates/frontend (added)
-
tags/1.0.3/templates/frontend/partials (added)
-
tags/1.0.3/templates/frontend/partials/single-reply.php (added)
-
tags/1.0.3/templates/frontend/ticket-form.php (added)
-
tags/1.0.3/templates/frontend/ticket-list.php (added)
-
tags/1.0.3/templates/frontend/ticket-single.php (added)
-
tags/1.0.3/uninstall.php (added)
-
tags/1.0.3/vendor (added)
-
tags/1.0.3/vendor/freemius (added)
-
tags/1.0.3/vendor/freemius/LICENSE.txt (added)
-
tags/1.0.3/vendor/freemius/README.md (added)
-
tags/1.0.3/vendor/freemius/assets (added)
-
tags/1.0.3/vendor/freemius/assets/css (added)
-
tags/1.0.3/vendor/freemius/assets/css/admin (added)
-
tags/1.0.3/vendor/freemius/assets/css/admin/account.css (added)
-
tags/1.0.3/vendor/freemius/assets/css/admin/add-ons.css (added)
-
tags/1.0.3/vendor/freemius/assets/css/admin/affiliation.css (added)
-
tags/1.0.3/vendor/freemius/assets/css/admin/checkout.css (added)
-
tags/1.0.3/vendor/freemius/assets/css/admin/clone-resolution.css (added)
-
tags/1.0.3/vendor/freemius/assets/css/admin/common.css (added)
-
tags/1.0.3/vendor/freemius/assets/css/admin/connect.css (added)
-
tags/1.0.3/vendor/freemius/assets/css/admin/debug.css (added)
-
tags/1.0.3/vendor/freemius/assets/css/admin/dialog-boxes.css (added)
-
tags/1.0.3/vendor/freemius/assets/css/admin/gdpr-optin-notice.css (added)
-
tags/1.0.3/vendor/freemius/assets/css/admin/index.php (added)
-
tags/1.0.3/vendor/freemius/assets/css/admin/optout.css (added)
-
tags/1.0.3/vendor/freemius/assets/css/admin/plugins.css (added)
-
tags/1.0.3/vendor/freemius/assets/css/customizer.css (added)
-
tags/1.0.3/vendor/freemius/assets/css/index.php (added)
-
tags/1.0.3/vendor/freemius/assets/img (added)
-
tags/1.0.3/vendor/freemius/assets/img/index.php (added)
-
tags/1.0.3/vendor/freemius/assets/img/nexlifydesk.png (added)
-
tags/1.0.3/vendor/freemius/assets/img/plugin-icon.png (added)
-
tags/1.0.3/vendor/freemius/assets/img/theme-icon.png (added)
-
tags/1.0.3/vendor/freemius/assets/index.php (added)
-
tags/1.0.3/vendor/freemius/assets/js (added)
-
tags/1.0.3/vendor/freemius/assets/js/index.php (added)
-
tags/1.0.3/vendor/freemius/assets/js/jquery.form.js (added)
-
tags/1.0.3/vendor/freemius/assets/js/nojquery.ba-postmessage.js (added)
-
tags/1.0.3/vendor/freemius/assets/js/postmessage.js (added)
-
tags/1.0.3/vendor/freemius/assets/js/pricing (added)
-
tags/1.0.3/vendor/freemius/assets/js/pricing/14fb1bd5b7c41648488b06147f50a0dc.svg (added)
-
tags/1.0.3/vendor/freemius/assets/js/pricing/178afa6030e76635dbe835e111d2c507.png (added)
-
tags/1.0.3/vendor/freemius/assets/js/pricing/27b5a722a5553d9de0170325267fccec.png (added)
-
tags/1.0.3/vendor/freemius/assets/js/pricing/4375c4a3ddc6f637c2ab9a2d7220f91e.png (added)
-
tags/1.0.3/vendor/freemius/assets/js/pricing/4529cac82a2d1f300d3c4702b7b5e8f3.svg (added)
-
tags/1.0.3/vendor/freemius/assets/js/pricing/5480ed23b199531a8cbc05924f26952b.png (added)
-
tags/1.0.3/vendor/freemius/assets/js/pricing/b4f3b958f4a019862d81b15f3f8eee3a.svg (added)
-
tags/1.0.3/vendor/freemius/assets/js/pricing/c03f665db27af43971565560adfba594.png (added)
-
tags/1.0.3/vendor/freemius/assets/js/pricing/cb5fc4f6ec7ada72e986f6e7dde365bf.png (added)
-
tags/1.0.3/vendor/freemius/assets/js/pricing/dd89563360f0272635c8f0ab7d7f1402.png (added)
-
tags/1.0.3/vendor/freemius/assets/js/pricing/e366d70661d8ad2493bd6afbd779f125.png (added)
-
tags/1.0.3/vendor/freemius/assets/js/pricing/f18006f6535a1a6e9c6bfbffafe6f18a.svg (added)
-
tags/1.0.3/vendor/freemius/assets/js/pricing/f3aac72a8e63997d6bb888f816457e9b.png (added)
-
tags/1.0.3/vendor/freemius/assets/js/pricing/f928f1be99776af83e8e6be4baf8ffe7.svg (added)
-
tags/1.0.3/vendor/freemius/assets/js/pricing/fde48e4609a6ddc11d639fc2421f2afd.png (added)
-
tags/1.0.3/vendor/freemius/assets/js/pricing/freemius-pricing.js (added)
-
tags/1.0.3/vendor/freemius/assets/js/pricing/freemius-pricing.js.LICENSE.txt (added)
-
tags/1.0.3/vendor/freemius/composer.json (added)
-
tags/1.0.3/vendor/freemius/config.php (added)
-
tags/1.0.3/vendor/freemius/includes (added)
-
tags/1.0.3/vendor/freemius/includes/class-freemius-abstract.php (added)
-
tags/1.0.3/vendor/freemius/includes/class-freemius.php (added)
-
tags/1.0.3/vendor/freemius/includes/class-fs-admin-notices.php (added)
-
tags/1.0.3/vendor/freemius/includes/class-fs-api.php (added)
-
tags/1.0.3/vendor/freemius/includes/class-fs-garbage-collector.php (added)
-
tags/1.0.3/vendor/freemius/includes/class-fs-lock.php (added)
-
tags/1.0.3/vendor/freemius/includes/class-fs-logger.php (added)
-
tags/1.0.3/vendor/freemius/includes/class-fs-options.php (added)
-
tags/1.0.3/vendor/freemius/includes/class-fs-plugin-updater.php (added)
-
tags/1.0.3/vendor/freemius/includes/class-fs-security.php (added)
-
tags/1.0.3/vendor/freemius/includes/class-fs-storage.php (added)
-
tags/1.0.3/vendor/freemius/includes/class-fs-user-lock.php (added)
-
tags/1.0.3/vendor/freemius/includes/customizer (added)
-
tags/1.0.3/vendor/freemius/includes/customizer/class-fs-customizer-support-section.php (added)
-
tags/1.0.3/vendor/freemius/includes/customizer/class-fs-customizer-upsell-control.php (added)
-
tags/1.0.3/vendor/freemius/includes/customizer/index.php (added)
-
tags/1.0.3/vendor/freemius/includes/debug (added)
-
tags/1.0.3/vendor/freemius/includes/debug/class-fs-debug-bar-panel.php (added)
-
tags/1.0.3/vendor/freemius/includes/debug/debug-bar-start.php (added)
-
tags/1.0.3/vendor/freemius/includes/debug/index.php (added)
-
tags/1.0.3/vendor/freemius/includes/entities (added)
-
tags/1.0.3/vendor/freemius/includes/entities/class-fs-affiliate-terms.php (added)
-
tags/1.0.3/vendor/freemius/includes/entities/class-fs-affiliate.php (added)
-
tags/1.0.3/vendor/freemius/includes/entities/class-fs-billing.php (added)
-
tags/1.0.3/vendor/freemius/includes/entities/class-fs-entity.php (added)
-
tags/1.0.3/vendor/freemius/includes/entities/class-fs-payment.php (added)
-
tags/1.0.3/vendor/freemius/includes/entities/class-fs-plugin-info.php (added)
-
tags/1.0.3/vendor/freemius/includes/entities/class-fs-plugin-license.php (added)
-
tags/1.0.3/vendor/freemius/includes/entities/class-fs-plugin-plan.php (added)
-
tags/1.0.3/vendor/freemius/includes/entities/class-fs-plugin-tag.php (added)
-
tags/1.0.3/vendor/freemius/includes/entities/class-fs-plugin.php (added)
-
tags/1.0.3/vendor/freemius/includes/entities/class-fs-pricing.php (added)
-
tags/1.0.3/vendor/freemius/includes/entities/class-fs-scope-entity.php (added)
-
tags/1.0.3/vendor/freemius/includes/entities/class-fs-site.php (added)
-
tags/1.0.3/vendor/freemius/includes/entities/class-fs-subscription.php (added)
-
tags/1.0.3/vendor/freemius/includes/entities/class-fs-user.php (added)
-
tags/1.0.3/vendor/freemius/includes/entities/index.php (added)
-
tags/1.0.3/vendor/freemius/includes/fs-core-functions.php (added)
-
tags/1.0.3/vendor/freemius/includes/fs-essential-functions.php (added)
-
tags/1.0.3/vendor/freemius/includes/fs-html-escaping-functions.php (added)
-
tags/1.0.3/vendor/freemius/includes/fs-plugin-info-dialog.php (added)
-
tags/1.0.3/vendor/freemius/includes/index.php (added)
-
tags/1.0.3/vendor/freemius/includes/l10n.php (added)
-
tags/1.0.3/vendor/freemius/includes/managers (added)
-
tags/1.0.3/vendor/freemius/includes/managers/class-fs-admin-menu-manager.php (added)
-
tags/1.0.3/vendor/freemius/includes/managers/class-fs-admin-notice-manager.php (added)
-
tags/1.0.3/vendor/freemius/includes/managers/class-fs-cache-manager.php (added)
-
tags/1.0.3/vendor/freemius/includes/managers/class-fs-checkout-manager.php (added)
-
tags/1.0.3/vendor/freemius/includes/managers/class-fs-clone-manager.php (added)
-
tags/1.0.3/vendor/freemius/includes/managers/class-fs-contact-form-manager.php (added)
-
tags/1.0.3/vendor/freemius/includes/managers/class-fs-debug-manager.php (added)
-
tags/1.0.3/vendor/freemius/includes/managers/class-fs-gdpr-manager.php (added)
-
tags/1.0.3/vendor/freemius/includes/managers/class-fs-key-value-storage.php (added)
-
tags/1.0.3/vendor/freemius/includes/managers/class-fs-license-manager.php (added)
-
tags/1.0.3/vendor/freemius/includes/managers/class-fs-option-manager.php (added)
-
tags/1.0.3/vendor/freemius/includes/managers/class-fs-permission-manager.php (added)
-
tags/1.0.3/vendor/freemius/includes/managers/class-fs-plan-manager.php (added)
-
tags/1.0.3/vendor/freemius/includes/managers/class-fs-plugin-manager.php (added)
-
tags/1.0.3/vendor/freemius/includes/managers/index.php (added)
-
tags/1.0.3/vendor/freemius/includes/sdk (added)
-
tags/1.0.3/vendor/freemius/includes/sdk/Exceptions (added)
-
tags/1.0.3/vendor/freemius/includes/sdk/Exceptions/ArgumentNotExistException.php (added)
-
tags/1.0.3/vendor/freemius/includes/sdk/Exceptions/EmptyArgumentException.php (added)
-
tags/1.0.3/vendor/freemius/includes/sdk/Exceptions/Exception.php (added)
-
tags/1.0.3/vendor/freemius/includes/sdk/Exceptions/InvalidArgumentException.php (added)
-
tags/1.0.3/vendor/freemius/includes/sdk/Exceptions/OAuthException.php (added)
-
tags/1.0.3/vendor/freemius/includes/sdk/Exceptions/index.php (added)
-
tags/1.0.3/vendor/freemius/includes/sdk/FreemiusBase.php (added)
-
tags/1.0.3/vendor/freemius/includes/sdk/FreemiusWordPress.php (added)
-
tags/1.0.3/vendor/freemius/includes/sdk/LICENSE.txt (added)
-
tags/1.0.3/vendor/freemius/includes/sdk/index.php (added)
-
tags/1.0.3/vendor/freemius/includes/supplements (added)
-
tags/1.0.3/vendor/freemius/includes/supplements/fs-essential-functions-1.1.7.1.php (added)
-
tags/1.0.3/vendor/freemius/includes/supplements/fs-essential-functions-2.2.1.php (added)
-
tags/1.0.3/vendor/freemius/includes/supplements/fs-migration-2.5.1.php (added)
-
tags/1.0.3/vendor/freemius/includes/supplements/index.php (added)
-
tags/1.0.3/vendor/freemius/index.php (added)
-
tags/1.0.3/vendor/freemius/languages (added)
-
tags/1.0.3/vendor/freemius/languages/freemius-cs_CZ.mo (added)
-
tags/1.0.3/vendor/freemius/languages/freemius-da_DK.mo (added)
-
tags/1.0.3/vendor/freemius/languages/freemius-de_DE.mo (added)
-
tags/1.0.3/vendor/freemius/languages/freemius-es_ES.mo (added)
-
tags/1.0.3/vendor/freemius/languages/freemius-fr_FR.mo (added)
-
tags/1.0.3/vendor/freemius/languages/freemius-he_IL.mo (added)
-
tags/1.0.3/vendor/freemius/languages/freemius-hu_HU.mo (added)
-
tags/1.0.3/vendor/freemius/languages/freemius-it_IT.mo (added)
-
tags/1.0.3/vendor/freemius/languages/freemius-ja.mo (added)
-
tags/1.0.3/vendor/freemius/languages/freemius-nl_NL.mo (added)
-
tags/1.0.3/vendor/freemius/languages/freemius-ru_RU.mo (added)
-
tags/1.0.3/vendor/freemius/languages/freemius-ta.mo (added)
-
tags/1.0.3/vendor/freemius/languages/freemius-zh_CN.mo (added)
-
tags/1.0.3/vendor/freemius/languages/freemius.pot (added)
-
tags/1.0.3/vendor/freemius/languages/index.php (added)
-
tags/1.0.3/vendor/freemius/require.php (added)
-
tags/1.0.3/vendor/freemius/start.php (added)
-
tags/1.0.3/vendor/freemius/templates (added)
-
tags/1.0.3/vendor/freemius/templates/account (added)
-
tags/1.0.3/vendor/freemius/templates/account.php (added)
-
tags/1.0.3/vendor/freemius/templates/account/billing.php (added)
-
tags/1.0.3/vendor/freemius/templates/account/index.php (added)
-
tags/1.0.3/vendor/freemius/templates/account/partials (added)
-
tags/1.0.3/vendor/freemius/templates/account/partials/activate-license-button.php (added)
-
tags/1.0.3/vendor/freemius/templates/account/partials/addon.php (added)
-
tags/1.0.3/vendor/freemius/templates/account/partials/deactivate-license-button.php (added)
-
tags/1.0.3/vendor/freemius/templates/account/partials/disconnect-button.php (added)
-
tags/1.0.3/vendor/freemius/templates/account/partials/index.php (added)
-
tags/1.0.3/vendor/freemius/templates/account/partials/site.php (added)
-
tags/1.0.3/vendor/freemius/templates/account/payments.php (added)
-
tags/1.0.3/vendor/freemius/templates/add-ons.php (added)
-
tags/1.0.3/vendor/freemius/templates/add-trial-to-pricing.php (added)
-
tags/1.0.3/vendor/freemius/templates/admin-notice.php (added)
-
tags/1.0.3/vendor/freemius/templates/ajax-loader.php (added)
-
tags/1.0.3/vendor/freemius/templates/api-connectivity-message-js.php (added)
-
tags/1.0.3/vendor/freemius/templates/auto-installation.php (added)
-
tags/1.0.3/vendor/freemius/templates/checkout (added)
-
tags/1.0.3/vendor/freemius/templates/checkout.php (added)
-
tags/1.0.3/vendor/freemius/templates/checkout/frame.php (added)
-
tags/1.0.3/vendor/freemius/templates/checkout/process-redirect.php (added)
-
tags/1.0.3/vendor/freemius/templates/checkout/redirect.php (added)
-
tags/1.0.3/vendor/freemius/templates/clone-resolution-js.php (added)
-
tags/1.0.3/vendor/freemius/templates/connect (added)
-
tags/1.0.3/vendor/freemius/templates/connect.php (added)
-
tags/1.0.3/vendor/freemius/templates/connect/index.php (added)
-
tags/1.0.3/vendor/freemius/templates/connect/permission.php (added)
-
tags/1.0.3/vendor/freemius/templates/connect/permissions-group.php (added)
-
tags/1.0.3/vendor/freemius/templates/contact.php (added)
-
tags/1.0.3/vendor/freemius/templates/debug (added)
-
tags/1.0.3/vendor/freemius/templates/debug.php (added)
-
tags/1.0.3/vendor/freemius/templates/debug/api-calls.php (added)
-
tags/1.0.3/vendor/freemius/templates/debug/index.php (added)
-
tags/1.0.3/vendor/freemius/templates/debug/logger.php (added)
-
tags/1.0.3/vendor/freemius/templates/debug/plugins-themes-sync.php (added)
-
tags/1.0.3/vendor/freemius/templates/debug/scheduled-crons.php (added)
-
tags/1.0.3/vendor/freemius/templates/email.php (added)
-
tags/1.0.3/vendor/freemius/templates/forms (added)
-
tags/1.0.3/vendor/freemius/templates/forms/affiliation.php (added)
-
tags/1.0.3/vendor/freemius/templates/forms/data-debug-mode.php (added)
-
tags/1.0.3/vendor/freemius/templates/forms/deactivation (added)
-
tags/1.0.3/vendor/freemius/templates/forms/deactivation/contact.php (added)
-
tags/1.0.3/vendor/freemius/templates/forms/deactivation/form.php (added)
-
tags/1.0.3/vendor/freemius/templates/forms/deactivation/index.php (added)
-
tags/1.0.3/vendor/freemius/templates/forms/deactivation/retry-skip.php (added)
-
tags/1.0.3/vendor/freemius/templates/forms/email-address-update.php (added)
-
tags/1.0.3/vendor/freemius/templates/forms/index.php (added)
-
tags/1.0.3/vendor/freemius/templates/forms/license-activation.php (added)
-
tags/1.0.3/vendor/freemius/templates/forms/optout.php (added)
-
tags/1.0.3/vendor/freemius/templates/forms/premium-versions-upgrade-handler.php (added)
-
tags/1.0.3/vendor/freemius/templates/forms/premium-versions-upgrade-metadata.php (added)
-
tags/1.0.3/vendor/freemius/templates/forms/resend-key.php (added)
-
tags/1.0.3/vendor/freemius/templates/forms/subscription-cancellation.php (added)
-
tags/1.0.3/vendor/freemius/templates/forms/trial-start.php (added)
-
tags/1.0.3/vendor/freemius/templates/forms/user-change.php (added)
-
tags/1.0.3/vendor/freemius/templates/gdpr-optin-js.php (added)
-
tags/1.0.3/vendor/freemius/templates/index.php (added)
-
tags/1.0.3/vendor/freemius/templates/js (added)
-
tags/1.0.3/vendor/freemius/templates/js/index.php (added)
-
tags/1.0.3/vendor/freemius/templates/js/jquery.content-change.php (added)
-
tags/1.0.3/vendor/freemius/templates/js/open-license-activation.php (added)
-
tags/1.0.3/vendor/freemius/templates/js/permissions.php (added)
-
tags/1.0.3/vendor/freemius/templates/js/style-premium-theme.php (added)
-
tags/1.0.3/vendor/freemius/templates/partials (added)
-
tags/1.0.3/vendor/freemius/templates/partials/index.php (added)
-
tags/1.0.3/vendor/freemius/templates/partials/network-activation.php (added)
-
tags/1.0.3/vendor/freemius/templates/plugin-icon.php (added)
-
tags/1.0.3/vendor/freemius/templates/plugin-info (added)
-
tags/1.0.3/vendor/freemius/templates/plugin-info/description.php (added)
-
tags/1.0.3/vendor/freemius/templates/plugin-info/features.php (added)
-
tags/1.0.3/vendor/freemius/templates/plugin-info/index.php (added)
-
tags/1.0.3/vendor/freemius/templates/plugin-info/screenshots.php (added)
-
tags/1.0.3/vendor/freemius/templates/pricing.php (added)
-
tags/1.0.3/vendor/freemius/templates/secure-https-header.php (added)
-
tags/1.0.3/vendor/freemius/templates/sticky-admin-notice-js.php (added)
-
tags/1.0.3/vendor/freemius/templates/tabs-capture-js.php (added)
-
tags/1.0.3/vendor/freemius/templates/tabs.php (added)
-
trunk/assets/css/nexlifydesk-admin.css (modified) (6 diffs)
-
trunk/assets/css/nexlifydesk.css (modified) (1 diff)
-
trunk/assets/js/nexlifydesk.js (modified) (3 diffs)
-
trunk/email-source/nexlifydesk-email-pipe.php (modified) (13 diffs)
-
trunk/email-source/providers/aws-ses/aws-handler.php (modified) (11 diffs)
-
trunk/email-source/providers/google/google-handler.php (modified) (6 diffs)
-
trunk/includes/class-nexlifydesk-admin.php (modified) (6 diffs)
-
trunk/includes/class-nexlifydesk-ajax.php (modified) (7 diffs)
-
trunk/includes/class-nexlifydesk-database.php (modified) (4 diffs)
-
trunk/includes/class-nexlifydesk-rate-limiter.php (modified) (9 diffs)
-
trunk/includes/class-nexlifydesk-shortcodes.php (modified) (5 diffs)
-
trunk/includes/class-nexlifydesk-tickets.php (modified) (15 diffs)
-
trunk/includes/helpers.php (modified) (14 diffs)
-
trunk/includes/nexlifydesk-functions.php (modified) (2 diffs)
-
trunk/includes/textanalysis (added)
-
trunk/includes/textanalysis/Comparisons (added)
-
trunk/includes/textanalysis/Comparisons/CosineSimilarityComparison.php (added)
-
trunk/includes/textanalysis/Documents (added)
-
trunk/includes/textanalysis/Documents/DocumentAbstract.php (added)
-
trunk/includes/textanalysis/Documents/TokensDocument.php (added)
-
trunk/includes/textanalysis/Interfaces (added)
-
trunk/includes/textanalysis/Interfaces/IDistance.php (added)
-
trunk/includes/textanalysis/Interfaces/IExtractStrategy.php (added)
-
trunk/includes/textanalysis/Interfaces/ISimilarity.php (added)
-
trunk/includes/textanalysis/Interfaces/IStemmer.php (added)
-
trunk/includes/textanalysis/Interfaces/ITokenTransformation.php (added)
-
trunk/includes/textanalysis/SECURITY.md (added)
-
trunk/includes/textanalysis/Tokenizers (added)
-
trunk/includes/textanalysis/Tokenizers/GeneralTokenizer.php (added)
-
trunk/includes/textanalysis/Tokenizers/TokenizerAbstract.php (added)
-
trunk/includes/textanalysis/Tokenizers/WhitespaceTokenizer.php (added)
-
trunk/nexlifydesk.php (modified) (15 diffs)
-
trunk/readme.txt (modified) (1 diff)
-
trunk/templates/admin/imap-auth.php (modified) (1 diff)
-
trunk/templates/admin/settings.php (modified) (2 diffs)
-
trunk/templates/admin/ticket-single.php (modified) (4 diffs)
-
trunk/templates/admin/tickets-list.php (modified) (2 diffs)
-
trunk/templates/emails/new_reply.php (modified) (1 diff)
-
trunk/templates/emails/new_ticket.php (modified) (1 diff)
-
trunk/templates/emails/sla_breach.php (modified) (1 diff)
-
trunk/templates/emails/status_changed.php (modified) (1 diff)
-
trunk/templates/frontend/ticket-list.php (modified) (2 diffs)
-
trunk/templates/frontend/ticket-single.php (modified) (2 diffs)
-
trunk/uninstall.php (added)
Legend:
- Unmodified
- Added
- Removed
-
nexlifydesk/trunk/assets/css/nexlifydesk-admin.css
r3330741 r3333095 12 12 padding: 20px; 13 13 max-width: 100%; 14 overflow-x: auto; 15 min-width: 0; 14 16 } 15 17 … … 68 70 flex-direction: column; 69 71 gap: 24px; 72 min-width: 0; 73 overflow-wrap: break-word; 74 word-wrap: break-word; 75 word-break: break-word; 70 76 } 71 77 … … 163 169 padding: 12px; 164 170 width: 100%; 171 min-width: 0; 172 overflow-wrap: break-word; 173 word-wrap: break-word; 174 word-break: break-word; 165 175 } 166 176 .nexlifydesk-admin-single-ticket-ui .agent-message .message-content { … … 188 198 line-height: 1.6; 189 199 color: #374151; 200 overflow-wrap: break-word; 201 word-wrap: break-word; 202 word-break: break-word; 190 203 } 191 204 … … 200 213 text-decoration: none; 201 214 word-break: break-all; 215 overflow-wrap: break-word; 216 word-wrap: break-word; 202 217 } 203 218 .nexlifydesk-admin-single-ticket-ui .message .attachments { … … 1361 1376 font-style: italic; 1362 1377 } 1378 -
nexlifydesk/trunk/assets/css/nexlifydesk.css
r3326104 r3333095 790 790 } 791 791 792 /* --- Frontend Ticket List Styles --- */ 793 .nexlifydesk-ticket-list-header { 792 /* --- Frontend Ticket List Styles (Table Design) --- */ 793 .nexlifydesk-table-container { 794 width: 100%; 795 max-width: 1200px; 796 margin: 20px auto; 797 padding: 20px; 798 background: #fff; 799 border-radius: 8px; 800 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); 801 font-family: Arial, sans-serif; 802 color: #333; 803 line-height: 1.6; 804 } 805 806 /* Header */ 807 .nexlifydesk-table-header { 794 808 display: flex; 795 809 justify-content: space-between; 796 810 align-items: center; 797 flex-wrap: wrap; 798 gap: 16px; 799 margin-bottom: 32px; 800 } 801 .nexlifydesk-ticket-list-title { 802 font-size: 2rem; 803 font-weight: 700; 804 color: #2d3748; 811 margin-bottom: 20px; 812 } 813 814 .nexlifydesk-table-header h1 { 815 font-size: 24px; 805 816 margin: 0; 806 } 807 .nexlifydesk-ticket-list-desc { 808 margin: 4px 0 0 0; 809 color: #6c757d; 810 font-size: 1rem; 811 } 812 .nexlifydesk-ticket-list-header__btn { 813 padding: 12px 28px; 814 font-size: 1rem; 815 border-radius: 8px; 816 background: linear-gradient(135deg, #0f2027, #203a43, #2c5364); 817 color: #333; 818 } 819 820 .nexlifydesk-header-actions { 821 display: flex; 822 gap: 10px; 823 } 824 825 /* Buttons */ 826 .nexlifydesk-btn-primary { 827 padding: 8px 12px; 828 background: #007bff; 817 829 color: #fff; 818 font-weight: 600; 830 border: none; 831 border-radius: 4px; 832 cursor: pointer; 819 833 text-decoration: none; 820 box-shadow: 0 4px 16px rgba(44,83,100,0.08); 821 } 822 .nexlifydesk-ticket-list-header__btn:hover, 823 .nexlifydesk-no-tickets__btn:hover { 824 background: linear-gradient(135deg, #203a43, #2c5364, #0f2027); 834 font-size: 14px; 835 font-weight: normal; 836 display: inline-block; 837 } 838 839 .nexlifydesk-btn-primary:hover { 840 background: #0056b3; 825 841 color: #fff; 826 } 827 828 .nexlifydesk-no-tickets { 842 text-decoration: none; 843 } 844 845 .nexlifydesk-view-btn { 846 padding: 6px 10px; 847 background: #007bff; 848 color: #fff; 849 border: none; 850 border-radius: 4px; 851 cursor: pointer; 852 text-decoration: none; 853 font-size: 14px; 854 display: inline-block; 855 } 856 857 .nexlifydesk-view-btn:hover { 858 background: #0056b3; 859 color: #fff; 860 text-decoration: none; 861 } 862 863 /* Table styles */ 864 .nexlifydesk-ticket-table { 865 width: 100%; 866 border-collapse: collapse; 867 margin-bottom: 20px; 868 } 869 870 .nexlifydesk-ticket-table th, 871 .nexlifydesk-ticket-table td { 872 padding: 12px; 873 text-align: left; 874 border-bottom: 1px solid #ddd; 875 } 876 877 .nexlifydesk-ticket-table th { 878 background: #f8f9fa; 879 font-weight: bold; 880 color: #333; 881 } 882 883 .nexlifydesk-ticket-table tr:hover { 884 background: #f1f1f1; 885 } 886 887 .nexlifydesk-subject-cell { 888 max-width: 300px; 889 } 890 891 .nexlifydesk-ticket-preview { 892 font-size: 12px; 893 color: #666; 894 margin-top: 4px; 895 font-style: italic; 896 } 897 898 /* Status badges */ 899 .nexlifydesk-status { 900 padding: 4px 8px; 901 border-radius: 4px; 902 font-size: 12px; 903 color: #fff; 904 font-weight: normal; 905 text-transform: capitalize; 906 } 907 908 .nexlifydesk-status.open { 909 background: #28a745; 910 } 911 912 .nexlifydesk-status.pending { 913 background: #ffc107; 914 color: #333; 915 } 916 917 .nexlifydesk-status.in-progress { 918 background: #ffc107; 919 color: #333; 920 } 921 922 .nexlifydesk-status.resolved { 923 background: #28a745; 924 } 925 926 .nexlifydesk-status.closed { 927 background: #dc3545; 928 } 929 930 /* Priority badges */ 931 .nexlifydesk-priority { 932 padding: 4px 8px; 933 border-radius: 4px; 934 font-size: 12px; 935 color: #fff; 936 font-weight: normal; 937 text-transform: capitalize; 938 } 939 940 .nexlifydesk-priority.low { 941 background: #28a745; 942 } 943 944 .nexlifydesk-priority.medium { 945 background: #ffc107; 946 color: #333; 947 } 948 949 .nexlifydesk-priority.high { 950 background: #fd7e14; 951 } 952 953 .nexlifydesk-priority.urgent { 954 background: #dc3545; 955 } 956 957 /* No tickets state */ 958 .nexlifydesk-no-tickets-table { 829 959 text-align: center; 830 960 padding: 60px 20px; 831 961 background: #f8f9fa; 832 border-radius: 16px; 833 box-shadow: 0 4px 24px rgba(44,83,100,0.06); 834 } 835 .nexlifydesk-no-tickets__icon { 836 font-size: 56px; 962 border-radius: 8px; 963 border: 1px solid #ddd; 964 } 965 966 .nexlifydesk-no-tickets-icon { 967 font-size: 48px; 837 968 margin-bottom: 20px; 838 } 839 .nexlifydesk-no-tickets__title { 840 font-size: 1.5rem; 841 font-weight: 700; 842 color: #2d3748; 843 margin-bottom: 8px; 844 } 845 .nexlifydesk-no-tickets__desc { 846 color: #6c757d; 847 font-size: 1rem; 848 margin-bottom: 24px; 849 } 850 .nexlifydesk-no-tickets__btn { 851 padding: 12px 28px; 852 font-size: 1rem; 853 border-radius: 8px; 854 background: linear-gradient(135deg, #0f2027, #203a43, #2c5364); 855 color: #fff; 856 font-weight: 600; 857 text-decoration: none; 858 } 859 860 .nexlifydesk-ticket-list-grid { 861 display: grid; 862 grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); 863 gap: 24px; 864 } 865 .nexlifydesk-ticket-card { 866 background: #fff; 867 border-radius: 16px; 868 box-shadow: 0 4px 24px rgba(44,83,100,0.08); 869 padding: 24px; 870 display: flex; 871 flex-direction: column; 872 gap: 12px; 873 border: 1px solid #e2e8f0; 874 transition: box-shadow 0.2s; 875 } 876 .nexlifydesk-ticket-card:hover { 877 box-shadow: 0 8px 32px rgba(44,83,100,0.14); 878 } 879 .nexlifydesk-ticket-card__top { 880 display: flex; 881 justify-content: space-between; 882 align-items: flex-start; 883 gap: 12px; 884 } 885 .nexlifydesk-ticket-card__id { 969 opacity: 0.5; 970 } 971 972 .nexlifydesk-no-tickets-table h3 { 973 font-size: 20px; 974 margin-bottom: 10px; 975 color: #333; 976 } 977 978 .nexlifydesk-no-tickets-table p { 979 color: #666; 980 margin-bottom: 20px; 981 } 982 983 /* Footer */ 984 .nexlifydesk-table-footer { 985 text-align: center; 986 color: #777; 886 987 font-size: 14px; 887 color: #6c757d; 888 font-weight: 500; 889 background: #e9ecef; 890 padding: 4px 12px; 891 border-radius: 12px; 892 } 893 .nexlifydesk-ticket-card__status { 894 padding: 6px 16px; 895 border-radius: 20px; 896 font-size: 14px; 897 font-weight: 600; 898 text-transform: uppercase; 899 letter-spacing: 0.5px; 900 border: 1px solid transparent; 901 } 902 .nexlifydesk-ticket-card__status.status-open { background: #e6fffa; color: #00b894; border-color: #00b894; } 903 .nexlifydesk-ticket-card__status.status-pending { background: #fff3cd; color: #f39c12; border-color: #f39c12; } 904 .nexlifydesk-ticket-card__status.status-resolved { background: #e8f5e8; color: #27ae60; border-color: #27ae60; } 905 .nexlifydesk-ticket-card__status.status-closed { background: #f8f9fa; color: #6c757d; border-color: #6c757d; } 906 907 .nexlifydesk-ticket-card__title { 908 font-size: 1.1rem; 909 font-weight: 700; 910 color: #2d3748; 911 margin: 8px 0 0 0; 912 } 913 .nexlifydesk-ticket-card__title a { 914 color: #2d3748; 915 text-decoration: none; 916 } 917 .nexlifydesk-ticket-card__title a:hover { 918 text-decoration: underline; 919 } 920 .nexlifydesk-ticket-card__meta { 921 display: flex; 922 gap: 10px; 923 align-items: center; 924 flex-wrap: wrap; 925 } 926 .nexlifydesk-ticket-card__priority { 927 padding: 6px 16px; 928 border-radius: 20px; 929 font-size: 14px; 930 font-weight: 600; 931 text-transform: uppercase; 932 letter-spacing: 0.5px; 933 border: 1px solid transparent; 934 } 935 .nexlifydesk-ticket-card__priority.priority-high { background: #ffe6e6; color: #e74c3c; border-color: #e74c3c; } 936 .nexlifydesk-ticket-card__priority.priority-urgent { background: #e74c3c; color: #fff; border-color: #c0392b; } 937 .nexlifydesk-ticket-card__priority.priority-medium { background: #fff3cd; color: #f39c12; border-color: #f39c12; } 938 .nexlifydesk-ticket-card__priority.priority-low { background: #e8f5e8; color: #27ae60; border-color: #27ae60; } 939 .nexlifydesk-ticket-card__created { 940 font-size: 13px; 941 color: #6c757d; 942 } 943 .nexlifydesk-ticket-card__actions { 944 margin-top: 10px; 945 } 946 .nexlifydesk-ticket-card__actions .btn { 947 padding: 10px 20px; 948 border-radius: 8px; 949 background: #f8f9fa; 950 color: #2d3748; 951 border: 1px solid #e2e8f0; 952 font-weight: 600; 953 text-decoration: none; 954 font-size: 14px; 955 transition: background 0.2s; 956 } 957 .nexlifydesk-ticket-card__actions .btn:hover { 958 background: #e2e8f0; 959 } 960 988 } 989 990 .nexlifydesk-table-footer p { 991 margin: 0; 992 } 993 994 /* Responsive design */ 961 995 @media (max-width: 768px) { 962 .nexlifydesk-ticket-list-header { flex-direction: column; align-items: flex-start; gap: 8px; } 963 .nexlifydesk-ticket-list-title { font-size: 1.3rem; } 964 .nexlifydesk-ticket-list-grid { grid-template-columns: 1fr; } 965 .nexlifydesk-ticket-card { padding: 16px; } 966 } 996 .nexlifydesk-table-container { 997 margin: 10px; 998 padding: 15px; 999 } 1000 1001 .nexlifydesk-ticket-table, 1002 .nexlifydesk-ticket-table thead, 1003 .nexlifydesk-ticket-table tbody, 1004 .nexlifydesk-ticket-table th, 1005 .nexlifydesk-ticket-table td, 1006 .nexlifydesk-ticket-table tr { 1007 display: block; 1008 } 1009 1010 .nexlifydesk-ticket-table thead tr { 1011 display: none; 1012 } 1013 1014 .nexlifydesk-ticket-table tr { 1015 margin-bottom: 15px; 1016 border: 1px solid #ddd; 1017 border-radius: 4px; 1018 padding: 10px; 1019 background: #fff; 1020 } 1021 1022 .nexlifydesk-ticket-table td { 1023 border: none; 1024 position: relative; 1025 padding-left: 50%; 1026 text-align: right; 1027 padding-bottom: 8px; 1028 padding-top: 8px; 1029 } 1030 1031 .nexlifydesk-ticket-table td:before { 1032 content: attr(data-label) ":"; 1033 position: absolute; 1034 left: 10px; 1035 width: 45%; 1036 padding-right: 10px; 1037 white-space: nowrap; 1038 text-align: left; 1039 font-weight: bold; 1040 color: #333; 1041 } 1042 1043 .nexlifydesk-table-header { 1044 flex-direction: column; 1045 align-items: flex-start; 1046 gap: 15px; 1047 } 1048 1049 .nexlifydesk-table-header h1 { 1050 font-size: 20px; 1051 } 1052 1053 .nexlifydesk-header-actions { 1054 width: 100%; 1055 } 1056 1057 .nexlifydesk-subject-cell { 1058 max-width: none; 1059 } 1060 1061 .nexlifydesk-ticket-preview { 1062 margin-top: 5px; 1063 text-align: right; 1064 } 1065 } 1066 1067 @media (max-width: 480px) { 1068 .nexlifydesk-table-container { 1069 margin: 5px; 1070 padding: 10px; 1071 } 1072 1073 .nexlifydesk-table-header h1 { 1074 font-size: 18px; 1075 } 1076 1077 .nexlifydesk-ticket-table td { 1078 padding-left: 45%; 1079 font-size: 14px; 1080 } 1081 1082 .nexlifydesk-ticket-table td:before { 1083 width: 40%; 1084 font-size: 12px; 1085 } 1086 } -
nexlifydesk/trunk/assets/js/nexlifydesk.js
r3330741 r3333095 18 18 19 19 $(document).ready(function() { 20 20 21 $('.page-title-action').on('click', function(e) { 21 22 e.preventDefault(); … … 700 701 }); 701 702 702 $('#keep_data_on_uninstall').on('change', function() {703 if ($(this).is(':checked')) {704 $('#data-deletion-warning').slideUp();705 } else {706 $('#data-deletion-warning').slideDown();707 }708 });709 710 if (!$('#keep_data_on_uninstall').is(':checked')) {711 $('#data-deletion-warning').show();712 }713 714 703 })(jQuery); 715 704 … … 1415 1404 }); 1416 1405 1406 $('#aws-diagnostics').on('click', function(){ 1407 var $btn = $(this); 1408 $btn.data('diagnostics-bound', true); 1409 1410 var $result = $('#aws-diagnostics-result'); 1411 1412 if ($result.length === 0) { 1413 alert('Error: Diagnostics result container not found. Please refresh the page.'); 1414 return; 1415 } 1416 1417 if (!window.nexlifydesk_aws_test_nonce) { 1418 $result.html('<p style="color: red;">❌ Security nonce not available. Please refresh the page.</p>'); 1419 return; 1420 } 1421 1422 $btn.prop('disabled', true).text('Running Diagnostics...'); 1423 $result.html('<p style="color: #666;">Running system diagnostics...</p>'); 1424 1425 $.post(ajaxurl, { 1426 action: 'nexlifydesk_aws_diagnostics', 1427 nonce: window.nexlifydesk_aws_test_nonce || '' 1428 }).done(function(response) { 1429 $btn.prop('disabled', false).text('System Diagnostics'); 1430 if (response.success && response.data.diagnostics) { 1431 var html = '<div style="background: #f5f5f5; padding: 15px; border-radius: 5px; margin-top: 10px;">'; 1432 html += '<h4 style="margin-top: 0;">🔍 System Diagnostics Report</h4>'; 1433 1434 Object.keys(response.data.diagnostics).forEach(function(key) { 1435 var diag = response.data.diagnostics[key]; 1436 var statusIcon = ''; 1437 var statusColor = ''; 1438 1439 switch(diag.status) { 1440 case 'OK': 1441 statusIcon = '✅'; 1442 statusColor = 'green'; 1443 break; 1444 case 'FAILED': 1445 statusIcon = '❌'; 1446 statusColor = 'red'; 1447 break; 1448 case 'WARNING': 1449 statusIcon = '⚠️'; 1450 statusColor = 'orange'; 1451 break; 1452 default: 1453 statusIcon = 'ℹ️'; 1454 statusColor = 'blue'; 1455 } 1456 1457 html += '<div style="margin: 8px 0; padding: 8px; background: white; border-radius: 3px;">'; 1458 html += '<strong style="color: ' + statusColor + ';">' + statusIcon + ' ' + key.replace(/_/g, ' ').toUpperCase() + ':</strong> '; 1459 html += '<span style="color: #333;">' + diag.message + '</span>'; 1460 html += '</div>'; 1461 }); 1462 1463 html += '<div style="margin-top: 15px; padding: 10px; background: #e3f2fd; border-radius: 3px; font-size: 12px; color: #1976d2;">'; 1464 html += '<strong>💡 Troubleshooting Tips:</strong><br>'; 1465 html += '• If IMAP extension is missing, contact your hosting provider<br>'; 1466 html += '• SSL/HTTPS is required for AWS WorkMail<br>'; 1467 html += '• Ensure your server can connect to AWS (check firewall/network)<br>'; 1468 html += '• Verify your AWS WorkMail credentials are correct'; 1469 html += '</div>'; 1470 html += '</div>'; 1471 1472 $result.html(html); 1473 } else { 1474 $result.html('<p style="color: red;">❌ Diagnostics failed to run.</p>'); 1475 } 1476 }).fail(function() { 1477 $btn.prop('disabled', false).text('System Diagnostics'); 1478 $result.html('<p style="color: red;">❌ Diagnostics request failed.</p>'); 1479 }); 1480 }); 1481 1482 $(document).on('click', '#aws-diagnostics', function(e) { 1483 if ($(this).data('diagnostics-bound')) { 1484 return; 1485 } 1486 1487 var $btn = $(this); 1488 var $result = $('#aws-diagnostics-result'); 1489 1490 if ($result.length === 0) { 1491 alert('Error: Diagnostics result container not found. Please refresh the page.'); 1492 return; 1493 } 1494 1495 if (!window.nexlifydesk_aws_test_nonce) { 1496 $result.html('<p style="color: red;">❌ Security nonce not available. Please refresh the page.</p>'); 1497 return; 1498 } 1499 1500 $btn.prop('disabled', true).text('Running Diagnostics...'); 1501 $result.html('<p style="color: #666;">Running system diagnostics...</p>'); 1502 1503 $.post(ajaxurl, { 1504 action: 'nexlifydesk_aws_diagnostics', 1505 nonce: window.nexlifydesk_aws_test_nonce || '' 1506 }).done(function(response) { 1507 $btn.prop('disabled', false).text('System Diagnostics'); 1508 if (response.success && response.data.diagnostics) { 1509 var html = '<div style="background: #f5f5f5; padding: 15px; border-radius: 5px; margin-top: 10px;">'; 1510 html += '<h4 style="margin-top: 0;">🔍 System Diagnostics Report</h4>'; 1511 1512 Object.keys(response.data.diagnostics).forEach(function(key) { 1513 var diag = response.data.diagnostics[key]; 1514 var statusIcon = ''; 1515 var statusColor = ''; 1516 1517 switch(diag.status) { 1518 case 'OK': 1519 statusIcon = '✅'; 1520 statusColor = 'green'; 1521 break; 1522 case 'FAILED': 1523 statusIcon = '❌'; 1524 statusColor = 'red'; 1525 break; 1526 case 'WARNING': 1527 statusIcon = '⚠️'; 1528 statusColor = 'orange'; 1529 break; 1530 default: 1531 statusIcon = 'ℹ️'; 1532 statusColor = 'blue'; 1533 } 1534 1535 html += '<div style="margin: 8px 0; padding: 8px; background: white; border-radius: 3px;">'; 1536 html += '<strong style="color: ' + statusColor + ';">' + statusIcon + ' ' + key.replace(/_/g, ' ').toUpperCase() + ':</strong> '; 1537 html += '<span style="color: #333;">' + diag.message + '</span>'; 1538 html += '</div>'; 1539 }); 1540 1541 html += '<div style="margin-top: 15px; padding: 10px; background: #e3f2fd; border-radius: 3px; font-size: 12px; color: #1976d2;">'; 1542 html += '<strong>💡 Troubleshooting Tips:</strong><br>'; 1543 html += '• If IMAP extension is missing, contact your hosting provider<br>'; 1544 html += '• SSL/HTTPS is required for AWS WorkMail<br>'; 1545 html += '• Ensure your server can connect to AWS (check firewall/network)<br>'; 1546 html += '• Verify your AWS WorkMail credentials are correct'; 1547 html += '</div>'; 1548 html += '</div>'; 1549 1550 $result.html(html); 1551 } else { 1552 $result.html('<p style="color: red;">❌ Diagnostics failed to run.</p>'); 1553 } 1554 }).fail(function() { 1555 $btn.prop('disabled', false).text('System Diagnostics'); 1556 $result.html('<p style="color: red;">❌ Diagnostics request failed.</p>'); 1557 }); 1558 }); 1559 1417 1560 $('#test-google-connection').on('click', function(){ 1418 1561 var $btn = $(this); -
nexlifydesk/trunk/email-source/nexlifydesk-email-pipe.php
r3330879 r3333095 14 14 $provider = isset($settings['provider']) ? $settings['provider'] : 'custom'; 15 15 16 // Check if IMAP extension is available for providers that need it17 16 if (($provider === 'custom' || $provider === 'aws') && !extension_loaded('imap')) { 18 // Add admin notice for users to see19 17 add_action('admin_notices', function() { 20 18 if (current_user_can('manage_options')) { … … 54 52 55 53 /** 56 * Gets ticket by ticket_id string (e.g., "T1102") 57 * 54 * Get ticket by ticket ID 58 55 * @param string $ticket_id The ticket ID string 59 56 * @return object|null The ticket object if found, null otherwise … … 64 61 65 62 /** 66 * Clean email subject for better duplicate detection63 * 67 64 * Removes Re:, Fwd:, etc. prefixes and normalizes the subject 68 65 * … … 93 90 $table_name = $wpdb->prefix . 'nexlifydesk_tickets'; 94 91 95 // For non-registered users (user_id = 0)96 92 if ($user_id == 0 && !empty($email)) { 97 93 … … 166 162 167 163 function nexlifydesk_fetch_custom_emails() { 168 // Check if IMAP extension is available169 164 if (!extension_loaded('imap')) { 170 // Add admin notice for users to see171 165 add_action('admin_notices', function() { 172 166 if (current_user_can('manage_options')) { … … 206 200 $date = isset($overview->date) ? $overview->date : ''; 207 201 208 $subject = nexlifydesk_decode_email_content($subject); 202 if (function_exists('nexlifydesk_decode_email_subject')) { 203 $subject = nexlifydesk_decode_email_subject($subject); 204 } else { 205 $subject = nexlifydesk_decode_email_content($subject); 206 } 209 207 $from = nexlifydesk_decode_email_content($from); 210 208 … … 380 378 $date = isset($overview->date) ? $overview->date : (isset($header->date) ? $header->date : ''); 381 379 382 $subject = nexlifydesk_decode_email_content($subject); 380 if (function_exists('nexlifydesk_decode_email_subject')) { 381 $subject = nexlifydesk_decode_email_subject($subject); 382 } else { 383 $subject = nexlifydesk_decode_email_content($subject); 384 } 383 385 $from = nexlifydesk_decode_email_content($from); 384 386 … … 566 568 return $email_content; 567 569 } 568 569 570 if (!empty($from_email) && nexlifydesk_is_admin_or_agent_email($from_email)) { 570 571 return $email_content; 571 572 } 572 573 573 $original_content = $email_content; 574 575 if (preg_match('/^[A-Za-z0-9+\/=\s]+$/', $email_content) && base64_decode($email_content, true) !== false) { 576 $decoded_base64 = base64_decode($email_content); 577 if (strlen($decoded_base64) > strlen($email_content) * 0.5) { 578 $email_content = $decoded_base64; 579 } 580 } 581 582 if (strpos($email_content, '=') !== false) { 583 if (preg_match('/=\r?\n/', $email_content) || preg_match('/=[0-9A-F]{2}/', $email_content)) { 584 $email_content = quoted_printable_decode($email_content); 585 } 586 } 587 588 if (strpos($email_content, '=?') !== false) { 589 $email_content = preg_replace_callback('/=\?([^?]*)\?([BQ])\?([^?]*)\?=/', function($matches) { 574 $body = str_replace(["\r\n", "\r"], "\n", $email_content); 575 if (preg_match('/^[A-Za-z0-9+\/=\s]+$/', $body) && base64_decode($body, true) !== false) { 576 $decoded_base64 = base64_decode($body); 577 if (strlen($decoded_base64) > strlen($body) * 0.5) { 578 $body = $decoded_base64; 579 } 580 } 581 if (strpos($body, '=') !== false) { 582 if (preg_match('/=\n/', $body) || preg_match('/=[0-9A-F]{2}/', $body)) { 583 $body = quoted_printable_decode($body); 584 } 585 } 586 if (strpos($body, '=?') !== false) { 587 $body = preg_replace_callback('/=\?([^?]*)\?([BQ])\?([^?]*)\?=/', function($matches) { 590 588 $charset = $matches[1]; 591 589 $encoding = strtoupper($matches[2]); 592 590 $text = $matches[3]; 593 594 591 if ($encoding === 'B') { 595 592 $decoded = base64_decode($text); … … 599 596 return $matches[0]; 600 597 } 601 602 598 if (strcasecmp($charset, 'UTF-8') !== 0) { 603 599 $decoded = mb_convert_encoding($decoded, 'UTF-8', $charset); 604 600 } 605 606 601 return $decoded; 607 }, $email_content); 608 } 609 610 if (!mb_check_encoding($email_content, 'UTF-8')) { 611 $email_content = mb_convert_encoding($email_content, 'UTF-8', 'auto'); 612 } 613 614 $is_html = (stripos($email_content, '<html') !== false || stripos($email_content, '<body') !== false || strpos($email_content, '<') !== false); 615 602 }, $body); 603 } 604 if (!mb_check_encoding($body, 'UTF-8')) { 605 $body = mb_convert_encoding($body, 'UTF-8', 'auto'); 606 } 607 $is_html = (stripos($body, '<html') !== false || stripos($body, '<body') !== false || strpos($body, '<') !== false); 616 608 if ($is_html) { 617 609 if (function_exists('mb_convert_encoding')) { 618 $email_content = mb_convert_encoding($email_content, 'HTML-ENTITIES', 'UTF-8'); 619 } 620 621 $email_content = preg_replace('/<br\s*\/?>/i', "\n", $email_content); 622 $email_content = preg_replace('/<\/p>/i', "\n\n", $email_content); 623 $email_content = preg_replace('/<\/div>/i', "\n", $email_content); 624 $email_content = preg_replace('/<\/h[1-6]>/i', "\n\n", $email_content); 625 $email_content = preg_replace('/<\/li>/i', "\n", $email_content); 626 $email_content = preg_replace('/<hr\s*\/?>/i', "\n---\n", $email_content); 627 628 $email_content = wp_strip_all_tags($email_content); 629 630 $email_content = html_entity_decode($email_content, ENT_QUOTES, 'UTF-8'); 631 } 632 633 $email_content = preg_replace('/\r\n|\r/', "\n", $email_content); 634 $email_content = preg_replace('/\n{3,}/', "\n\n", $email_content); 635 $lines = explode("\n", $email_content); 636 $clean_lines = []; 637 $found_reply_separator = false; 638 $signature_start = -1; 639 640 for ($i = 0; $i < count($lines); $i++) { 641 $line = trim($lines[$i]); 642 643 $strong_thread_indicators = [ 644 '/^-----Original Message-----/', 645 '/^From:.*@.*/', 646 '/^To:.*@.*/', 647 '/^Sent:.*\d{4}/', 648 '/^Date:.*\d{4}/', 649 '/^On .+\d{4}.+at.+wrote:/', 650 '/^>.+wrote:/', 651 '/^________________________________/', 652 '/^______________/', 653 '/^-{3,}\s*On\s+.+wrote\s*-{3,}/', // Matches "---- On [date] [name] wrote ---" 654 '/^-{2,}\s*On\s+.+\d{4}.+wrote\s*-{2,}/', // More flexible version 655 '/^-{2,}\s*On\s+\w+,\s*\d{1,2}\s+\w+\s+\d{4}.+wrote\s*-{2,}/', // "-- On Tue, 15 Jul 2025 ... wrote --" 656 '/^On\s+\w+,\s*\d{1,2}\s+\w+\s+\d{4}.+wrote\s*:?$/', // "On Tue, 15 Jul 2025 ... wrote:" 657 '/^At\s+\d{1,2}:\d{2}.*on\s+\d{1,2}\/\d{1,2}\/\d{4}.*wrote:/', // "At 08:44 on 15/07/2025 ... wrote:" 658 '/^Le\s+\d{1,2}\/\d{1,2}\/\d{4}.*a\s+écrit\s*:/', // French format 659 '/^Am\s+\d{1,2}\.\d{1,2}\.\d{4}.*schrieb\s*:/', // German format 660 '/^Il\s+\d{1,2}\/\d{1,2}\/\d{4}.*ha\s+scritto\s*:/', // Italian format 661 '/^El\s+\d{1,2}\/\d{1,2}\/\d{4}.*escribió\s*:/', // Spanish format 662 '/^Em\s+\d{1,2}\/\d{1,2}\/\d{4}.*escreveu\s*:/', // Portuguese format 663 '/^\d{1,2}\/\d{1,2}\/\d{4}.*wrote\s*:/', // Simple date format 664 '/=+$/', // Long line of equal signs 665 '/-{10,}/', // Long line of dashes 666 '/_{10,}/', // Long line of underscores 667 '/^Reply above this line/', 668 '/^Please reply above this line/', 669 '/^Do not reply below this line/', 670 '/^-----\s*Reply\s*above\s*this\s*line\s*-----/', 671 ]; 672 673 foreach ($strong_thread_indicators as $pattern) { 674 if (preg_match($pattern, $line)) { 675 $found_reply_separator = true; 676 $signature_start = $i; 677 break 2; 678 } 679 } 680 681 if ($i > 10 && count($clean_lines) > 5) { 682 $signature_patterns = [ 683 '/^--$/', 684 '/^---$/', 685 '/^Best regards,?$/i', 686 '/^Thank you,?$/i', 687 '/^Sincerely,?$/i', 688 '/^Kind regards,?$/i', 689 ]; 690 691 foreach ($signature_patterns as $pattern) { 692 if (preg_match($pattern, $line)) { 693 $signature_start = $i; 694 break 2; 695 } 696 } 697 } 698 } 699 700 for ($i = 0; $i < count($lines); $i++) { 701 if ($signature_start !== -1 && $i >= $signature_start) { 610 $body = mb_convert_encoding($body, 'HTML-ENTITIES', 'UTF-8'); 611 } 612 $body = preg_replace('/<br\s*\/?>(?i)/', "\n", $body); 613 $body = preg_replace('/<\/p>(?i)/', "\n\n", $body); 614 $body = preg_replace('/<\/div>(?i)/', "\n", $body); 615 $body = preg_replace('/<\/h[1-6]>(?i)/', "\n\n", $body); 616 $body = preg_replace('/<\/li>(?i)/', "\n", $body); 617 $body = preg_replace('/<hr\s*\/?>(?i)/', "\n---\n", $body); 618 $body = wp_strip_all_tags($body); 619 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8'); 620 } 621 $body = preg_replace('/\n{3,}/u', "\n\n", $body); 622 $header_pattern = '/^On[\s\S]+?wrote:\s*$/mus'; 623 $reply_separator_regexes = [ 624 $header_pattern, 625 '/^Am[\s\S]+?schrieb:\s*$/mus', 626 '/^Le[\s\S]+?écrit\s*:/mus', 627 '/^Il[\s\S]+?scritto\s*:/mus', 628 '/^El[\s\S]+?escribió\s*:/mus', 629 '/^Em[\s\S]+?escreveu\s*:/mus', 630 '/^At[\s\S]+?wrote:\s*$/mus', 631 '/^From:[\s\S]+?$/mus', 632 '/^Sent:[\s\S]+?$/mus', 633 '/^Date:[\s\S]+?$/mus', 634 '/^To:[\s\S]+?$/mus', 635 '/^Subject:[\s\S]+?$/mus', 636 '/^Reply above this line$/mus', 637 '/^Please reply above this line$/mus', 638 '/^Do not reply below this line$/mus', 639 '/^-----Original Message-----$/mus', 640 '/^-{2,}\s*On[\s\S]+wrote\s*-{2,}$/mus', 641 '/=+$/mus', 642 '/-{10,}/u', 643 '/_{10,}/u', 644 ]; 645 $separator_pos = false; 646 foreach ($reply_separator_regexes as $regex) { 647 if (preg_match($regex, $body, $match, PREG_OFFSET_CAPTURE)) { 648 $separator_pos = $match[0][1]; 702 649 break; 703 650 } 704 705 $line = $lines[$i]; 706 $trimmed_line = trim($line); 707 708 $is_thread_line = false; 709 foreach ($strong_thread_indicators as $pattern) { 710 if (preg_match($pattern, $trimmed_line)) { 711 $is_thread_line = true; 712 break; 713 } 714 } 715 716 if ($is_thread_line) { 717 break; 718 } 719 720 if (preg_match('/^>\s*/', $line)) { 721 if (stripos($line, 'wrote:') !== false || 722 stripos($line, 'original message') !== false || 723 stripos($line, 'from:') !== false || 724 stripos($line, 'sent:') !== false || 725 stripos($line, 'date:') !== false || 726 stripos($line, 'to:') !== false || 727 preg_match('/\d{4}/', $line)) { 728 break; 729 } 730 } 731 732 if (preg_match('/^(From|To|Sent|Date|Subject|CC|BCC):\s*/', $trimmed_line)) { 733 break; 734 } 735 736 if (preg_match('/^(Sent from my iPhone|Sent from my iPad|Sent from my Android|Get Outlook for)/', $trimmed_line)) { 737 break; 738 } 739 740 $clean_lines[] = $line; 741 } 742 743 $clean_content = implode("\n", $clean_lines); 744 $clean_content = preg_replace('/\n\s*\n\s*\n/', "\n\n", $clean_content); 745 $clean_content = trim($clean_content); 746 747 if (strlen($clean_content) < (strlen($original_content) * 0.3) && strlen($original_content) > 100) { 748 749 $conservative_lines = explode("\n", $email_content); 750 $preserved_lines = []; 751 752 foreach ($conservative_lines as $line) { 753 $trimmed_line = trim($line); 754 755 $is_thread_indicator = false; 756 foreach ($strong_thread_indicators as $pattern) { 757 if (preg_match($pattern, $trimmed_line)) { 758 $is_thread_indicator = true; 759 break; 760 } 761 } 762 763 if ($is_thread_indicator) { 764 break; 765 } 766 767 if (preg_match('/^>\s*/', $line) && 768 (stripos($line, 'wrote:') !== false || 769 stripos($line, 'original message') !== false || 770 stripos($line, 'from:') !== false || 771 stripos($line, 'sent:') !== false || 772 stripos($line, 'date:') !== false)) { 773 break; 774 } 775 776 if (preg_match('/^(From|To|Sent|Date|Subject|CC|BCC):\s*/', $trimmed_line)) { 777 break; 778 } 779 780 $preserved_lines[] = $line; 781 } 782 783 $clean_content = implode("\n", $preserved_lines); 784 $clean_content = preg_replace('/\n\s*\n\s*\n/', "\n\n", $clean_content); 785 $clean_content = trim($clean_content); 786 } 787 651 } 652 if ($separator_pos !== false) { 653 $body = substr($body, 0, $separator_pos); 654 } 655 $body = preg_replace('/^\s*(>|\|).*/m', '', $body); 656 $body = preg_replace('/\n{3,}/u', "\n\n", $body); 657 $clean_content = trim($body); 788 658 if (empty($clean_content)) { 789 659 $clean_content = '[Customer replied via email]'; 790 660 } 791 792 661 return $clean_content; 793 662 } 794 663 795 664 /** 796 * Safe password retrieval with fallback797 * Handles both encrypted and plain text passwords665 * Safe password retrieval with strict encryption enforcement 666 * Only returns decrypted passwords, never plain text 798 667 */ 799 668 function nexlifydesk_get_safe_password($encrypted_password) { … … 802 671 } 803 672 673 // Only proceed if password is properly encrypted 804 674 if (nexlifydesk_is_encrypted($encrypted_password)) { 805 675 $decrypted = nexlifydesk_decrypt($encrypted_password); … … 808 678 } 809 679 } 810 811 return $encrypted_password; 812 } 813 814 /** 815 * Extract email body content from IMAP message with proper encoding handling 816 * 680 // If not encrypted or decryption fails, return empty string 681 return ''; 682 } 683 684 /** 817 685 * @param resource $inbox IMAP connection resource 818 686 * @param int $message_number Message number … … 829 697 $part_number = $i + 1; 830 698 831 if ($part->type == 0) { // TEXT699 if ($part->type == 0) { 832 700 $body = imap_fetchbody($inbox, $message_number, $part_number); 833 701 … … 899 767 900 768 /** 901 * Convert HTML email content to plain text902 769 * 903 770 * @param string $html_content The HTML content to convert -
nexlifydesk/trunk/email-source/providers/aws-ses/aws-handler.php
r3330751 r3333095 2 2 3 3 if (!defined('ABSPATH')) exit; 4 5 // Ensure helpers are available for email decoding functions 6 if (!function_exists('nexlifydesk_decode_email_subject')) { 7 require_once dirname(__FILE__) . '/../../includes/helpers.php'; 8 } 4 9 5 10 /** … … 12 17 */ 13 18 function nexlifydesk_fetch_aws_emails() { 14 // Check if IMAP extension is available 15 if (!extension_loaded('imap') ) {19 // Check if IMAP extension is available with better detection 20 if (!extension_loaded('imap') || !function_exists('imap_open')) { 16 21 17 22 add_action('admin_notices', function() { 18 23 if (current_user_can('manage_options')) { 19 24 echo '<div class="notice notice-error"><p><strong>NexlifyDesk:</strong> ' . 20 esc_html__('IMAP extension is not installed on this server. AWS WorkMail email piping is not available. Please contact your hosting provider to enable the PHP IMAP extension.', 'nexlifydesk') .25 esc_html__('IMAP extension is not properly installed or configured on this server. AWS WorkMail email piping is not available. Please contact your hosting provider to enable the PHP IMAP extension with all required functions.', 'nexlifydesk') . 21 26 '</p></div>'; 22 27 } … … 67 72 $mailbox = "{{$imap_host}:{$imap_port}/imap/{$encryption}}INBOX"; 68 73 69 $inbox = @imap_open($mailbox, $email, $password); 74 // Clear any previous IMAP errors 75 if (function_exists('imap_errors')) { 76 imap_errors(); 77 } 78 if (function_exists('imap_alerts')) { 79 imap_alerts(); 80 } 81 82 $inbox = @imap_open($mailbox, $email, $password, OP_SILENT); 70 83 if (!$inbox) { 71 84 return; 85 } 86 87 $mailbox_info = imap_mailboxmsginfo($inbox); 88 if ($mailbox_info) { 89 } else { 90 $total_msgs = imap_num_msg($inbox); 72 91 } 73 92 … … 85 104 $emails = imap_search($inbox, $search_criteria); 86 105 106 // If no emails found with date criteria, try just UNSEEN to see if there are any unread emails at all 107 if (!$emails) { 108 $emails_unseen_only = imap_search($inbox, "UNSEEN"); 109 if ($emails_unseen_only) { 110 // For now, let's process all unseen emails to solve the immediate issue 111 $emails = $emails_unseen_only; 112 } else { 113 // IMAP search is failing, let's manually check all messages 114 $total_msgs = imap_num_msg($inbox); 115 116 $manually_found = []; 117 for ($i = 1; $i <= $total_msgs; $i++) { 118 $overview = imap_fetch_overview($inbox, $i, 0); 119 if (!empty($overview) && isset($overview[0])) { 120 $msg = $overview[0]; 121 // Check if message is unread (not seen) 122 if (!isset($msg->seen) || $msg->seen == 0) { 123 $manually_found[] = $i; 124 } 125 } 126 } 127 128 if (!empty($manually_found)) { 129 $emails = $manually_found; 130 } 131 } 132 } 133 87 134 if (!$emails) { 88 135 imap_close($inbox); … … 107 154 $from = $header->fromaddress ?? $overview->from; 108 155 $subject = $overview->subject ?? '(No Subject)'; 156 157 // Decode MIME-encoded subject to fix encoding issues like "=?UTF-8?Q?..." 158 if (function_exists('nexlifydesk_decode_email_subject')) { 159 $subject = nexlifydesk_decode_email_subject($subject); 160 } 109 161 110 162 $parsed_from = function_exists('nexlifydesk_parse_email_from') ? … … 148 200 } 149 201 150 $email_date = isset($overview->date) ? strtotime($overview->date) : 0;151 if ($email_date < $fetch_start_time) {152 imap_setflag_full($inbox, $email_number, "\\Seen");153 continue;154 }155 156 202 if (function_exists('nexlifydesk_should_block_email') && 157 203 nexlifydesk_should_block_email($email_address, $subject, $message, $settings)) { … … 166 212 $user_id = $user ? $user->ID : 0; 167 213 168 // Check rate limit for this email address169 214 if (class_exists('NexlifyDesk_Rate_Limiter') && NexlifyDesk_Rate_Limiter::is_rate_limited($user_id, $email_address)) { 170 171 // Mark email as processed to prevent reprocessing172 215 imap_setflag_full($inbox, $email_number, "\\Seen"); 173 216 if ($delete_emails) { … … 195 238 ]; 196 239 197 // Use the email-specific duplicate detection function198 240 if (function_exists('nexlifydesk_check_email_duplicate')) { 199 241 $existing_ticket = nexlifydesk_check_email_duplicate($ticket_data); 200 242 } else { 201 // Fallback to main duplicate detection202 243 $existing_ticket = NexlifyDesk_Tickets::check_for_duplicate_ticket($ticket_data); 203 244 } … … 318 359 } 319 360 320 /** 321 * Check if AWS credentials are properly configured 322 */ 361 // Check if the connection is ready for AWS SES 323 362 public function is_connection_ready() { 324 363 $is_ssl_enabled = $this->check_ssl_enabled(); … … 358 397 */ 359 398 public function test_imap_connection() { 399 if (!extension_loaded('imap') || !function_exists('imap_open')) { 400 return new WP_Error('imap_not_available', 'IMAP extension is not properly installed or configured on this server. Please contact your hosting provider to enable the PHP IMAP extension with all required functions.'); 401 } 402 360 403 $is_ssl_enabled = $this->check_ssl_enabled(); 361 404 … … 376 419 $mailbox = "{{$imap_host}:993/imap/ssl}INBOX"; 377 420 378 $inbox = @imap_open($mailbox, $email, $password); 421 if (function_exists('imap_errors')) { 422 imap_errors(); 423 } 424 if (function_exists('imap_alerts')) { 425 imap_alerts(); 426 } 427 428 $inbox = @imap_open($mailbox, $email, $password, OP_READONLY | OP_SILENT); 379 429 380 430 if (!$inbox) { 381 $error = imap_last_error(); 431 $error = 'Unknown connection error'; 432 433 if (function_exists('imap_last_error')) { 434 $last_error = imap_last_error(); 435 if ($last_error) { 436 $error = $last_error; 437 } 438 } 439 382 440 return new WP_Error('connection_failed', "AWS WorkMail IMAP connection failed: {$error}"); 383 441 } -
nexlifydesk/trunk/email-source/providers/google/google-handler.php
r3330751 r3333095 2 2 3 3 if (!defined('ABSPATH')) exit; 4 5 // Ensure helpers are available for email decoding functions 6 if (!function_exists('nexlifydesk_decode_email_subject')) { 7 require_once dirname(__FILE__) . '/../../includes/helpers.php'; 8 } 4 9 5 10 /** … … 17 22 } 18 23 19 // Verify OAuth state parameter for security20 24 if (isset($_GET['state'])) { 21 25 $state = sanitize_text_field(wp_unslash($_GET['state'])); … … 118 122 } 119 123 120 // Generate a state parameter for security121 124 $state = wp_generate_password(32, false); 122 set_transient('nexlifydesk_google_oauth_state', $state, 600); // 10 minutes125 set_transient('nexlifydesk_google_oauth_state', $state, 600); 123 126 124 127 $redirect_uri = admin_url('admin.php?action=nexlifydesk_google_oauth_callback'); … … 214 217 $from_header = array_values(array_filter($headers, fn($h) => $h['name'] === 'From'))[0]['value'] ?? ''; 215 218 $subject_header = array_values(array_filter($headers, fn($h) => $h['name'] === 'Subject'))[0]['value'] ?? '(No Subject)'; 219 220 // Decode MIME-encoded subject to fix encoding issues like "=?UTF-8?Q?..." 221 if (function_exists('nexlifydesk_decode_email_subject')) { 222 $subject_header = nexlifydesk_decode_email_subject($subject_header); 223 } 216 224 217 225 $parsed_from = function_exists('nexlifydesk_parse_email_from') ? … … 437 445 438 446 /** 439 * Marks a Gmail message as read without deleting it.440 447 * 441 448 * @param string $message_id The ID of the message to modify. … … 452 459 453 460 /** 454 * Marks a Gmail message as read and moves it to trash.455 461 * 456 462 * @param string $message_id The ID of the message to modify. -
nexlifydesk/trunk/includes/class-nexlifydesk-admin.php
r3330879 r3333095 1496 1496 'default_priority' => isset($_POST['default_priority']) ? sanitize_text_field(wp_unslash($_POST['default_priority'])) : '', 1497 1497 'auto_assign' => isset($_POST['auto_assign']) ? 1 : 0, 1498 'auto_assign_to_admin' => isset($_POST['auto_assign_to_admin']) ? 1 : 0, 1498 1499 'allowed_file_types' => isset($_POST['allowed_file_types']) ? self::validate_file_types(sanitize_text_field(wp_unslash($_POST['allowed_file_types']))) : 'jpg,jpeg,png,pdf', 1499 1500 'max_file_size' => isset($_POST['max_file_size']) ? absint($_POST['max_file_size']) : 0, … … 1505 1506 'ticket_id_start' => isset($_POST['ticket_id_start']) ? absint($_POST['ticket_id_start']) : 0, 1506 1507 'status_change_notification' => isset($_POST['status_change_notification']) ? 1 : 0, 1507 'auto_assign_to_admin' => isset($_POST['auto_assign_to_admin']) ? 1 : 0, 1508 'keep_data_on_uninstall' => isset($_POST['keep_data_on_uninstall']) ? 1 : 0, 1509 'check_duplicates' => isset($_POST['check_duplicates']) ? 1 : 0, 1510 'duplicate_threshold' => isset($_POST['duplicate_threshold']) ? absint($_POST['duplicate_threshold']) : 80, 1508 1511 ); 1509 1512 … … 1648 1651 <div class="wrap"> 1649 1652 <h1><?php esc_html_e('NexlifyDesk Email Templates', 'nexlifydesk'); ?></h1> 1653 1654 <div class="notice notice-info"> 1655 <p> 1656 <strong><?php esc_html_e('Template System Information:', 'nexlifydesk'); ?></strong><br> 1657 <?php esc_html_e('When fields below are empty, the system uses default template files from ', 'nexlifydesk'); ?> 1658 <code>templates/emails/</code> <?php esc_html_e('directory. Add content here only if you want to customize the default templates.', 'nexlifydesk'); ?> 1659 </p> 1660 </div> 1661 1650 1662 <form method="post"> 1651 1663 <?php wp_nonce_field('nexlifydesk_save_email_templates'); ?> 1652 1664 1653 1665 <h2><?php esc_html_e('New Ticket', 'nexlifydesk'); ?></h2> 1666 <?php if (empty($templates['new_ticket'])): ?> 1667 <p class="description" style="color: #0073aa; margin-bottom: 10px;"> 1668 <strong><?php esc_html_e('Currently using:', 'nexlifydesk'); ?></strong> 1669 <code>templates/emails/new_ticket.php</code> 1670 <?php esc_html_e('(Default template with proper styling)', 'nexlifydesk'); ?> 1671 </p> 1672 <?php endif; ?> 1654 1673 <?php wp_editor( 1655 1674 $templates['new_ticket'], … … 1670 1689 1671 1690 <h2><?php esc_html_e('New Reply', 'nexlifydesk'); ?></h2> 1691 <?php if (empty($templates['new_reply'])): ?> 1692 <p class="description" style="color: #0073aa; margin-bottom: 10px;"> 1693 <strong><?php esc_html_e('Currently using:', 'nexlifydesk'); ?></strong> 1694 <code>templates/emails/new_reply.php</code> 1695 <?php esc_html_e('(Default template with proper styling)', 'nexlifydesk'); ?> 1696 </p> 1697 <?php endif; ?> 1672 1698 <?php wp_editor( 1673 1699 $templates['new_reply'], … … 1688 1714 1689 1715 <h2><?php esc_html_e('Status Changed', 'nexlifydesk'); ?></h2> 1716 <?php if (empty($templates['status_changed'])): ?> 1717 <p class="description" style="color: #0073aa; margin-bottom: 10px;"> 1718 <strong><?php esc_html_e('Currently using:', 'nexlifydesk'); ?></strong> 1719 <code>templates/emails/status_changed.php</code> 1720 <?php esc_html_e('(Default template with proper styling)', 'nexlifydesk'); ?> 1721 </p> 1722 <?php endif; ?> 1690 1723 <?php wp_editor( 1691 1724 $templates['status_changed'], … … 1742 1775 1743 1776 <h2><?php esc_html_e('SLA Breach', 'nexlifydesk'); ?></h2> 1777 <?php if (empty($templates['sla_breach'])): ?> 1778 <p class="description" style="color: #0073aa; margin-bottom: 10px;"> 1779 <strong><?php esc_html_e('Currently using:', 'nexlifydesk'); ?></strong> 1780 <code>templates/emails/sla_breach.php</code> 1781 <?php esc_html_e('(Default template with proper styling)', 'nexlifydesk'); ?> 1782 </p> 1783 <?php endif; ?> 1744 1784 <?php wp_editor( 1745 1785 $templates['sla_breach'], -
nexlifydesk/trunk/includes/class-nexlifydesk-ajax.php
r3330879 r3333095 18 18 add_action('wp_ajax_nexlifydesk_add_category', array(__CLASS__, 'add_category')); 19 19 add_action('wp_ajax_nexlifydesk_test_aws_connection', array(__CLASS__, 'test_aws_connection')); 20 add_action('wp_ajax_nexlifydesk_aws_diagnostics', array(__CLASS__, 'aws_diagnostics')); 20 21 add_action('wp_ajax_nexlifydesk_manual_fetch_emails', array(__CLASS__, 'manual_fetch_emails')); 21 22 add_action('wp_ajax_nexlifydesk_bulk_action', array(__CLASS__, 'handle_bulk_action')); … … 187 188 } 188 189 190 $current_user = wp_get_current_user(); 191 $can_reply = false; 192 193 if (current_user_can('nexlifydesk_manage_tickets') || current_user_can('manage_options')) { 194 $can_reply = true; 195 } 196 197 if (!$can_reply && (int)$ticket->user_id === (int)$current_user->ID && $ticket->user_id > 0) { 198 $can_reply = true; 199 } 200 201 if (!$can_reply && (int)$ticket->user_id === 0) { 202 $customer_email = get_post_meta($ticket->id, 'customer_email', true); 203 if ($customer_email && $customer_email === $current_user->user_email) { 204 $can_reply = true; 205 } 206 } 207 208 if (!$can_reply) { 209 wp_send_json_error(__('You are not authorized to reply to this ticket.', 'nexlifydesk')); 210 return; 211 } 212 189 213 $data = array( 190 214 'ticket_id' => $ticket->id, 191 215 'message' => wp_kses_post(wp_unslash($_POST['message'])) 192 216 ); 193 194 $current_user = wp_get_current_user();195 217 196 218 if ($ticket && $ticket->status === 'closed') { … … 770 792 } 771 793 772 // Check if IMAP extension is available773 if (! extension_loaded('imap')) {774 wp_send_json_error(array('message' => __('IMAP extension is not installed on this server. Please contact your hosting provider to enable the PHP IMAP extension.', 'nexlifydesk')));794 $imap_available = extension_loaded('imap') && function_exists('imap_open'); 795 if (!$imap_available) { 796 wp_send_json_error(array('message' => __('IMAP extension is not properly installed or configured on this server. Please contact your hosting provider to enable the PHP IMAP extension with all required functions.', 'nexlifydesk'))); 775 797 } 776 798 … … 823 845 $imap_port = 993; 824 846 847 if (function_exists('imap_errors')) { 848 imap_errors(); 849 } 850 if (function_exists('imap_alerts')) { 851 imap_alerts(); 852 } 853 825 854 $connection = @imap_open( 826 855 "{{$imap_host}:{$imap_port}/imap/ssl}INBOX", 827 856 $email, 828 857 $password, 829 OP_READONLY 858 OP_READONLY | OP_SILENT 830 859 ); 831 860 832 861 if (!$connection) { 833 $error = imap_last_error(); 834 /* translators: 1: IMAP error, 2: Organization ID, 3: Email, 4: AWS region */ 835 wp_send_json_error(array('message' => sprintf(__('AWS WorkMail IMAP connection failed: %1$s. Please verify your Organization ID (%2$s), Email (%3$s), and Password are correct for region %4$s.', 'nexlifydesk'), $error, $organization_id, $email, $region))); 862 $error = 'Unknown connection error'; 863 864 if (function_exists('imap_last_error')) { 865 $last_error = imap_last_error(); 866 if ($last_error) { 867 $error = $last_error; 868 } 869 } 870 871 $error_message = sprintf( 872 /* translators: %1$s: Error message from IMAP connection */ 873 __('AWS WorkMail IMAP connection failed: %1$s', 'nexlifydesk'), 874 $error 875 ); 876 877 if (strpos($error, 'connect') !== false || strpos($error, 'timeout') !== false) { 878 $error_message .= ' ' . __('This usually indicates a network connectivity issue or firewall blocking. Please verify your server can connect to AWS WorkMail servers.', 'nexlifydesk'); 879 } elseif (strpos($error, 'authenticate') !== false || strpos($error, 'login') !== false) { 880 /* translators: 1: Organization ID, 2: Email, 3: AWS Region */ 881 $error_message .= ' ' . sprintf(__('Please verify your Organization ID (%1$s), Email (%2$s), and Password are correct for region %3$s.', 'nexlifydesk'), $organization_id, $email, $region); 882 } else { 883 /* translators: 1: Organization ID, 2: Email, 3: AWS Region */ 884 $error_message .= ' ' . sprintf(__('Please verify your AWS WorkMail configuration: Organization ID (%1$s), Email (%2$s), Region (%3$s).', 'nexlifydesk'), $organization_id, $email, $region); 885 } 886 887 wp_send_json_error(array('message' => $error_message)); 836 888 } 837 889 838 890 imap_close($connection); 839 $messages[] = 'AWS WorkMail IMAP connection successful'; 891 /* translators: 1: Email address, 2: AWS region */ 892 $messages[] = sprintf(__('AWS WorkMail IMAP connection successful for %1$s in region %2$s', 'nexlifydesk'), $email, $region); 840 893 841 894 if (!empty($access_key_id) && !empty($secret_access_key)) { … … 868 921 869 922 $final_message = implode('. ', $messages); 870 wp_send_json_success(array('message' => $final_message)); 923 924 $note = ' ' . __('Note: A successful connection test means IMAP authentication works. If email fetching still has issues, check your email provider settings, firewall, or contact support.', 'nexlifydesk'); 925 926 wp_send_json_success(array('message' => $final_message . $note)); 871 927 872 928 } catch (Exception $e) { … … 882 938 check_ajax_referer('nexlifydesk-ajax-nonce', 'nonce'); 883 939 884 // Check if IMAP extension is available885 940 if (!extension_loaded('imap')) { 886 941 wp_send_json_error(array('message' => __('IMAP extension is not installed on this server. Please contact your hosting provider to enable the PHP IMAP extension.', 'nexlifydesk'))); … … 1504 1559 return $size; 1505 1560 } 1561 1562 /** 1563 * AWS IMAP Diagnostics - provides detailed information about IMAP setup, required in production. 1564 */ 1565 public static function aws_diagnostics() { 1566 1567 if (!current_user_can('nexlifydesk_manage_tickets') && !current_user_can('manage_options')) { 1568 wp_send_json_error(array('message' => __('You do not have permission.', 'nexlifydesk'))); 1569 } 1570 1571 if ( 1572 !isset($_POST['nonce']) || 1573 !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'nexlifydesk_aws_test') 1574 ) { 1575 wp_send_json_error(array('message' => __('Invalid nonce.', 'nexlifydesk'))); 1576 } 1577 1578 $diagnostics = []; 1579 $settings = get_option('nexlifydesk_imap_settings', []); 1580 $aws_configured = false; 1581 1582 if (!empty($settings['aws_region']) && !empty($settings['aws_organization_id']) && 1583 !empty($settings['aws_email']) && !empty($settings['aws_password'])) { 1584 $aws_configured = true; 1585 } 1586 1587 if (!$aws_configured) { 1588 $diagnostics['aws_configuration'] = [ 1589 'status' => 'WARNING', 1590 'message' => 'No AWS WorkMail credentials configured. Please configure AWS settings first before running diagnostics.' 1591 ]; 1592 wp_send_json_success(array('diagnostics' => $diagnostics)); 1593 return; 1594 } 1595 1596 $diagnostics['imap_extension'] = [ 1597 'status' => extension_loaded('imap') ? 'OK' : 'FAILED', 1598 'message' => extension_loaded('imap') ? 'IMAP extension is loaded' : 'IMAP extension is NOT loaded' 1599 ]; 1600 1601 $required_functions = ['imap_open', 'imap_search', 'imap_fetchheader', 'imap_body', 'imap_close', 'imap_last_error']; 1602 $missing_functions = []; 1603 1604 foreach ($required_functions as $function) { 1605 if (!function_exists($function)) { 1606 $missing_functions[] = $function; 1607 } 1608 } 1609 1610 $diagnostics['imap_functions'] = [ 1611 'status' => empty($missing_functions) ? 'OK' : 'FAILED', 1612 'message' => empty($missing_functions) ? 'All required IMAP functions are available' : 'Missing functions: ' . implode(', ', $missing_functions) 1613 ]; 1614 1615 $ssl_transports = stream_get_transports(); 1616 $has_ssl = in_array('ssl', $ssl_transports) && in_array('tls', $ssl_transports); 1617 1618 $diagnostics['ssl_support'] = [ 1619 'status' => $has_ssl ? 'OK' : 'FAILED', 1620 'message' => $has_ssl ? 'SSL/TLS support is available' : 'SSL/TLS support is NOT available' 1621 ]; 1622 1623 $is_ssl_enabled = ( 1624 is_ssl() || 1625 (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') || 1626 (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') || 1627 (isset($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] === 'on') || 1628 (isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] === 'https') || 1629 (wp_parse_url(home_url(), PHP_URL_SCHEME) === 'https') 1630 ); 1631 1632 if (defined('NEXLIFYDESK_FORCE_SSL_ENABLED') && NEXLIFYDESK_FORCE_SSL_ENABLED) { 1633 $is_ssl_enabled = true; 1634 } 1635 1636 $diagnostics['site_ssl'] = [ 1637 'status' => $is_ssl_enabled ? 'OK' : 'WARNING', 1638 'message' => $is_ssl_enabled ? 'Site is running on HTTPS' : 'Site is NOT running on HTTPS (required for AWS WorkMail)' 1639 ]; 1640 1641 $aws_hosts_to_check = [ 1642 'imap.mail.us-east-1.awsapps.com', 1643 'imap.mail.us-west-2.awsapps.com', 1644 'imap.mail.eu-west-1.awsapps.com' 1645 ]; 1646 1647 $connectivity_results = []; 1648 foreach ($aws_hosts_to_check as $host) { 1649 $ip = gethostbyname($host); 1650 $connectivity_results[] = ($ip !== $host) ? "$host: OK" : "$host: DNS resolution failed"; 1651 } 1652 1653 $diagnostics['aws_connectivity'] = [ 1654 'status' => 'INFO', 1655 'message' => 'DNS Resolution Test: ' . implode(', ', $connectivity_results) 1656 ]; 1657 1658 $current_host = "imap.mail.{$settings['aws_region']}.awsapps.com"; 1659 $diagnostics['current_config'] = [ 1660 'status' => 'INFO', 1661 'message' => "Current AWS WorkMail host: {$current_host}, Organization: {$settings['aws_organization_id']}, Username: {$settings['aws_email']}" 1662 ]; 1663 1664 $aws_password = nexlifydesk_decrypt($settings['aws_password']); 1665 if (!empty($aws_password)) { 1666 try { 1667 $connection = @imap_open( 1668 "{{$current_host}:993/imap/ssl}INBOX", 1669 $settings['aws_email'], 1670 $aws_password, 1671 OP_READONLY | OP_SILENT 1672 ); 1673 1674 if ($connection) { 1675 imap_close($connection); 1676 $diagnostics['aws_connection_test'] = [ 1677 'status' => 'OK', 1678 'message' => 'AWS WorkMail IMAP connection test successful with saved credentials' 1679 ]; 1680 } else { 1681 $error = imap_last_error() ?: 'Connection failed'; 1682 $diagnostics['aws_connection_test'] = [ 1683 'status' => 'FAILED', 1684 'message' => "AWS WorkMail IMAP connection test failed: {$error}" 1685 ]; 1686 } 1687 } catch (Exception $e) { 1688 $diagnostics['aws_connection_test'] = [ 1689 'status' => 'FAILED', 1690 'message' => "AWS WorkMail connection test error: {$e->getMessage()}" 1691 ]; 1692 } 1693 } else { 1694 $diagnostics['aws_connection_test'] = [ 1695 'status' => 'WARNING', 1696 'message' => 'Cannot test AWS connection - password decryption failed' 1697 ]; 1698 } 1699 1700 $php_version = PHP_VERSION; 1701 $openssl_available = extension_loaded('openssl'); 1702 1703 $diagnostics['php_info'] = [ 1704 'status' => $openssl_available ? 'OK' : 'WARNING', 1705 'message' => "PHP Version: {$php_version}, OpenSSL: " . ($openssl_available ? 'Available' : 'NOT Available') 1706 ]; 1707 1708 wp_send_json_success(array('diagnostics' => $diagnostics)); 1709 } 1506 1710 } -
nexlifydesk/trunk/includes/class-nexlifydesk-database.php
r3330879 r3333095 193 193 194 194 public static function check_and_run_migrations() { 195 $current_version = get_option('nexlifydesk_db_version', '1.0. 2');195 $current_version = get_option('nexlifydesk_db_version', '1.0.3'); 196 196 $plugin_version = NEXLIFYDESK_VERSION; 197 197 198 if (version_compare($current_version, '1.0. 2', '<')) {198 if (version_compare($current_version, '1.0.3', '<')) { 199 199 self::migrate_to_1_0_1(); 200 update_option('nexlifydesk_db_version', '1.0. 2');201 } 202 203 if (version_compare($current_version, '1.0. 2', '<')) {200 update_option('nexlifydesk_db_version', '1.0.3'); 201 } 202 203 if (version_compare($current_version, '1.0.3', '<')) { 204 204 self::migrate_to_1_0_2(); 205 update_option('nexlifydesk_db_version', '1.0.2'); 206 } 207 208 // Update to current version 205 update_option('nexlifydesk_db_version', '1.0.3'); 206 } 207 208 $security_migration_done = get_option('nexlifydesk_password_encryption_migration', false); 209 if (!$security_migration_done) { 210 self::migrate_encrypt_passwords(); 211 update_option('nexlifydesk_password_encryption_migration', true); 212 } 213 209 214 if ($current_version !== $plugin_version) { 210 215 update_option('nexlifydesk_db_version', $plugin_version); … … 215 220 global $wpdb; 216 221 217 // Ensure tables array is initialized218 222 if (empty(self::$tables)) { 219 223 self::init(); … … 289 293 global $wpdb; 290 294 291 $current_version = get_option('nexlifydesk_version', '1.0. 2');292 293 if ($current_version === '1.0. 2' && !get_option('nexlifydesk_db_version')) {295 $current_version = get_option('nexlifydesk_version', '1.0.3'); 296 297 if ($current_version === '1.0.3' && !get_option('nexlifydesk_db_version')) { 294 298 return; 295 299 } … … 329 333 } 330 334 } 335 336 /** 337 * Security migration: Encrypt any plain text passwords in IMAP settings 338 */ 339 private static function migrate_encrypt_passwords() { 340 $imap_settings = get_option('nexlifydesk_imap_settings', array()); 341 342 if (empty($imap_settings)) { 343 return; 344 } 345 346 $updated = false; 347 348 if (isset($imap_settings['aws']) && is_array($imap_settings['aws'])) { 349 foreach ($imap_settings['aws'] as $key => $aws_config) { 350 if (isset($aws_config['password']) && !empty($aws_config['password'])) { 351 if (!nexlifydesk_is_encrypted($aws_config['password'])) { 352 $imap_settings['aws'][$key]['password'] = nexlifydesk_encrypt($aws_config['password']); 353 $updated = true; 354 } 355 } 356 } 357 } 358 359 $providers = array('google', 'outlook', 'generic'); 360 foreach ($providers as $provider) { 361 if (isset($imap_settings[$provider]) && is_array($imap_settings[$provider])) { 362 foreach ($imap_settings[$provider] as $key => $config) { 363 if (isset($config['password']) && !empty($config['password'])) { 364 if (!nexlifydesk_is_encrypted($config['password'])) { 365 $imap_settings[$provider][$key]['password'] = nexlifydesk_encrypt($config['password']); 366 $updated = true; 367 } 368 } 369 } 370 } 371 } 372 373 // Save updated settings if any passwords were encrypted 374 if ($updated) { 375 update_option('nexlifydesk_imap_settings', $imap_settings); 376 } 377 } 331 378 } -
nexlifydesk/trunk/includes/class-nexlifydesk-rate-limiter.php
r3330751 r3333095 20 20 * Time window in seconds (30 minutes) 21 21 */ 22 const TIME_WINDOW_SECONDS = 1800; // 30 minutes22 const TIME_WINDOW_SECONDS = 1800; 23 23 24 24 /** … … 58 58 59 59 /** 60 * Record any email activity (ticket creation or reply) for rate limiting61 *62 60 * @param int $user_id WordPress user ID (0 for non-registered users) 63 61 * @param string $email_address Email address … … 82 80 83 81 /** 84 * Get the remaining tickets allowed for a user/email85 *86 82 * @param int $user_id WordPress user ID (0 for non-registered users) 87 83 * @param string $email_address Email address … … 102 98 103 99 /** 104 * Get the time remaining until rate limit resets105 *106 100 * @param int $user_id WordPress user ID (0 for non-registered users) 107 101 * @param string $email_address Email address … … 126 120 127 121 /** 128 * Clear rate limit for a user/email (admin function)129 *130 122 * @param int $user_id WordPress user ID (0 for non-registered users) 131 123 * @param string $email_address Email address … … 144 136 145 137 /** 146 * Get rate limit status for a user/email147 *148 138 * @param int $user_id WordPress user ID (0 for non-registered users) 149 139 * @param string $email_address Email address … … 172 162 173 163 /** 174 * Get a unique identifier for rate limiting175 * Uses user ID if available, otherwise falls back to email address176 *177 164 * @param int $user_id WordPress user ID (0 for non-registered users) 178 165 * @param string $email_address Email address … … 188 175 189 176 /** 190 * Format time remaining in human readable format191 *192 177 * @param int $seconds Seconds remaining 193 178 * @return string Human readable time … … 226 211 227 212 /** 228 * Get rate limit error message229 *230 213 * @param int $user_id WordPress user ID (0 for non-registered users) 231 214 * @param string $email_address Email address -
nexlifydesk/trunk/includes/class-nexlifydesk-shortcodes.php
r3330879 r3333095 16 16 ), $atts, 'nexlifydesk_ticket_form'); 17 17 18 // Return shortcode placeholder ONLY for documentation pages that are specifically about documentation19 // NOT for actual support ticket submission pages20 18 if (self::is_documentation_page() && !self::is_actual_support_page()) { 21 19 return '<div class="nexlifydesk-shortcode-placeholder" style="background: #f0f0f0; padding: 15px; border: 1px dashed #ccc; border-radius: 4px; margin: 10px 0; text-align: center; color: #666;"><strong>Shortcode:</strong> <code>[nexlifydesk_ticket_form]</code><br><small>This shortcode displays the ticket submission form for customers.</small></div>'; … … 40 38 ), $atts, 'nexlifydesk_ticket_list'); 41 39 42 // Return shortcode placeholder ONLY for documentation pages that are specifically about documentation43 // NOT for actual support ticket list pages44 40 if (self::is_documentation_page() && !self::is_actual_support_page()) { 45 41 return '<div class="nexlifydesk-shortcode-placeholder" style="background: #f0f0f0; padding: 15px; border: 1px dashed #ccc; border-radius: 4px; margin: 10px 0; text-align: center; color: #666;"><strong>Shortcode:</strong> <code>[nexlifydesk_ticket_list]</code><br><small>This shortcode displays the user\'s ticket history and allows them to view their support tickets.</small></div>'; … … 181 177 'nexlifydesk-help', 182 178 'nexlifydesk-manual', 183 'knowledge-base', 179 'knowledge-base', 180 'nexlifydesk', 184 181 'kb', 185 182 'faq', … … 228 225 $current_page_id = $post->ID; 229 226 230 // Check if this is the configured support pages231 227 if ($current_page_id === $ticket_form_page_id || $current_page_id === $ticket_page_id) { 232 228 return true; 233 229 } 234 230 235 // Check if page title or slug indicates it's for actual support (ticket submission/viewing)236 231 $page_title = strtolower($post->post_title); 237 232 $page_slug = strtolower($post->post_name); … … 254 249 foreach ($support_keywords as $keyword) { 255 250 if (strpos($page_title, $keyword) !== false || strpos($page_slug, $keyword) !== false) { 256 // But exclude documentation-specific terms257 251 if (strpos($page_title, 'documentation') === false && 258 252 strpos($page_title, 'guide') === false && -
nexlifydesk/trunk/includes/class-nexlifydesk-tickets.php
r3330879 r3333095 1 1 <?php 2 2 3 if (!defined('ABSPATH')) { 3 4 exit; 5 } 6 7 if (!function_exists('nexlifydesk_extract_customer_details')) { 8 require_once __DIR__ . '/helpers.php'; 4 9 } 5 10 … … 71 76 $data['email'] = $email_address; 72 77 73 // For non-registered users, add a small delay to avoid race conditions with concurrent email processing74 78 if (!empty($data['source']) && $data['source'] === 'email' && $user_id == 0) { 75 // Add a micro-delay to reduce race conditions in email processing76 79 usleep(100000); // 100ms delay 77 80 } … … 132 135 $new_ticket_id = $wpdb->insert_id; 133 136 134 // Store customer email as post meta for both web and email tickets135 137 if (!empty($data['email'])) { 136 138 update_post_meta($new_ticket_id, 'customer_email', $data['email']); … … 148 150 149 151 register_shutdown_function(function() use ($ticket, $data) { 150 // Different notification handling for email vs web channels151 152 if (!empty($data['source']) && $data['source'] === 'email') { 152 // Send simple auto-response for email tickets153 153 NexlifyDesk_Tickets::send_email_channel_notification($ticket, 'new_ticket'); 154 154 } else { 155 // Send standard notifications for web tickets156 155 NexlifyDesk_Tickets::send_notification($ticket, 'new_ticket'); 157 156 } … … 442 441 443 442 if (isset($attachments['name']) && is_array($attachments['name'])) { 444 // Legacy multi-file format from $_FILES445 443 $file_count = count($attachments['name']); 446 444 … … 461 459 } 462 460 } else { 463 // Process each attachment individually (new format from AJAX)464 461 foreach ($attachments as $attachment) { 465 462 if (is_array($attachment) && isset($attachment['name'])) { … … 609 606 global $wpdb; 610 607 611 $allowed_statuses = array('open', 'pending', 'in_progress', 'resolved', 'closed'); // <-- Add in_progress here608 $allowed_statuses = array('open', 'pending', 'in_progress', 'resolved', 'closed'); 612 609 if (!in_array($status, $allowed_statuses)) { 613 610 return false; … … 677 674 $user = get_userdata($ticket->user_id); 678 675 $admin_email = get_option('admin_email'); 679 680 // Check if admin notifications are enabled681 676 $admin_notifications_enabled = !empty($settings['admin_email_notifications']); 682 683 677 $customer_details = nexlifydesk_extract_customer_details($ticket->message); 684 678 $customer_name = $user ? $user->display_name : ($customer_details['name'] ?: 'Guest'); … … 727 721 } 728 722 729 // Send to admin only if:730 // 1. Admin notifications are enabled AND731 // 2. Either no agent is assigned OR the ticket is assigned to admin732 723 if ($admin_notifications_enabled && !in_array($admin_email, $emailed)) { 733 724 $should_notify_admin = false; 734 725 735 726 if (empty($ticket->assigned_to)) { 736 // No agent assigned, notify admin737 727 $should_notify_admin = true; 738 728 } else { 739 // Check if assigned to admin740 729 $assigned_user = get_userdata($ticket->assigned_to); 741 730 if ($assigned_user && in_array('administrator', $assigned_user->roles)) { … … 881 870 ob_start(); 882 871 include NEXLIFYDESK_PLUGIN_DIR . 'templates/emails/' . $template . '.php'; 883 return ob_get_clean(); 872 $template_content = ob_get_clean(); 873 874 // Fallback if template file is empty or doesn't exist 875 if (empty($template_content)) { 876 $template_content = self::get_fallback_email_template($template, $ticket, $reply_id); 877 } 878 879 return $template_content; 884 880 } 885 881 … … 948 944 949 945 /** 946 * Get fallback email template if no custom template is set and file template is empty 947 */ 948 private static function get_fallback_email_template($template, $ticket, $reply_id = null) { 949 // Generate a basic fallback template if template files are not available 950 $user = get_userdata($ticket->user_id); 951 $customer_details = nexlifydesk_extract_customer_details($ticket->message); 952 $customer_name = $user ? $user->display_name : ($customer_details['name'] ?: 'Guest'); 953 954 switch ($template) { 955 case 'new_ticket': 956 return '<p>Hello ' . esc_html($customer_name) . ',</p><p>Your support ticket #' . esc_html($ticket->ticket_id) . ' has been created.</p><p>Subject: ' . esc_html($ticket->subject) . '</p><p>We will get back to you soon.</p>'; 957 case 'new_reply': 958 return '<p>Hello ' . esc_html($customer_name) . ',</p><p>You have a new reply on ticket #' . esc_html($ticket->ticket_id) . '.</p>'; 959 case 'status_changed': 960 return '<p>Hello ' . esc_html($customer_name) . ',</p><p>The status of your ticket #' . esc_html($ticket->ticket_id) . ' has been updated to: ' . esc_html(ucfirst($ticket->status)) . '.</p>'; 961 case 'sla_breach': 962 return '<p>Attention: Ticket #' . esc_html($ticket->ticket_id) . ' has breached its SLA.</p>'; 963 default: 964 return '<p>Email notification for ticket #' . esc_html($ticket->ticket_id) . '</p>'; 965 } 966 } 967 968 /** 950 969 * 951 970 * This function detects common SMTP plugins and avoids adding Message-ID, … … 1105 1124 $from_name = $reply_user->display_name ?: get_bloginfo('name'); 1106 1125 $from_email = $reply_user->user_email ?: get_option('admin_email'); 1107 1108 // Create fresh headers to avoid conflicts with SMTP plugins1109 1126 $reply_headers = self::get_email_headers($ticket); 1110 // Override the From header with the reply user's details1111 1127 $reply_headers[1] = 'From: ' . $from_name . ' <' . $from_email . '>'; 1112 1128 … … 1196 1212 $subject = sprintf(__('[New Email Ticket] [#%1$s] %2$s', 'nexlifydesk'), $ticket->ticket_id, $ticket->subject); 1197 1213 1198 // Notify admin only if admin notifications are enabled and conditions are met1199 1214 if ($admin_notifications_enabled) { 1200 1215 $should_notify_admin = false; … … 1620 1635 1621 1636 /** 1622 * Check for duplicate tickets based on simple rules:1623 * - For non-registered users: Check if they have any active ticket (any status except closed/resolved)1624 * - For registered users: Check for subject similarity only1625 *1626 1637 * @param array $data Ticket data to check for duplicates 1627 1638 * @return object|false Returns existing ticket if duplicate found, false otherwise 1628 1639 */ 1629 1640 public static function check_for_duplicate_ticket($data) { 1630 global $wpdb; 1631 1632 // Clear relevant caches to ensure fresh duplicate detection 1633 $user_id = isset($data['user_id']) ? absint($data['user_id']) : get_current_user_id(); 1634 $email = isset($data['email']) ? sanitize_email($data['email']) : ''; 1635 $subject = sanitize_text_field($data['subject']); 1636 $source = isset($data['source']) ? sanitize_text_field($data['source']) : ''; 1637 1641 if (!function_exists('nexlifydesk_find_duplicate_ticket')) { 1642 require_once dirname(__FILE__) . '/nexlifydesk-functions.php'; 1643 } 1638 1644 $settings = get_option('nexlifydesk_settings', array()); 1639 1645 $check_duplicates = isset($settings['check_duplicates']) ? $settings['check_duplicates'] : true; 1640 1641 1646 if (!$check_duplicates) { 1642 1647 return false; 1643 1648 } 1644 1645 $table_name = NexlifyDesk_Database::get_table('tickets'); 1646 1647 if ($user_id == 0 && !empty($email)) { 1648 1649 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table requires direct query 1650 $existing_ticket = $wpdb->get_row($wpdb->prepare( 1651 "SELECT t.* FROM {$table_name} t 1652 LEFT JOIN {$wpdb->prefix}postmeta pm ON pm.post_id = t.id AND pm.meta_key = 'customer_email' 1653 WHERE t.user_id = 0 1654 AND pm.meta_value = %s 1655 AND t.status IN ('open', 'pending', 'in_progress') 1656 ORDER BY t.created_at DESC 1657 LIMIT 1", 1658 $email 1659 )); 1660 1661 if (!$existing_ticket) { 1662 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table requires direct query 1663 $existing_ticket = $wpdb->get_row($wpdb->prepare( 1664 "SELECT t.* FROM {$table_name} t 1665 WHERE t.user_id = 0 1666 AND t.message LIKE %s 1667 AND t.status IN ('open', 'pending', 'in_progress') 1668 ORDER BY t.created_at DESC 1669 LIMIT 1", 1670 '%Email: ' . $email . '%' 1671 )); 1672 } 1673 1674 if ($existing_ticket) { 1675 return $existing_ticket; 1676 } 1677 } 1678 1679 if ($user_id > 0) { 1680 1681 $cache_key = 'nexlifydesk_registered_user_duplicate_' . intval($user_id) . '_' . md5($subject); 1682 $existing_ticket = wp_cache_get($cache_key); 1683 1684 if (false === $existing_ticket) { 1685 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table requires direct query 1686 $existing_ticket = $wpdb->get_row($wpdb->prepare( 1687 "SELECT * FROM {$table_name} 1688 WHERE user_id = %d 1689 AND subject = %s 1690 AND status IN ('open', 'pending', 'in_progress') 1691 AND created_at > DATE_SUB(NOW(), INTERVAL 30 DAY) 1692 ORDER BY created_at DESC 1693 LIMIT 1", 1694 $user_id, 1695 $subject 1696 )); 1697 wp_cache_set($cache_key, $existing_ticket, '', 300); 1698 } 1699 1700 if ($existing_ticket) { 1701 return $existing_ticket; 1702 } 1703 } 1704 1705 return false; 1649 return nexlifydesk_find_duplicate_ticket($data); 1706 1650 } 1707 1651 … … 1824 1768 1825 1769 /** 1826 * Update ticket priority1827 1770 * 1828 1771 * @param int $ticket_id Ticket ID -
nexlifydesk/trunk/includes/helpers.php
r3330751 r3333095 114 114 return true; 115 115 } 116 117 /**118 * Enhanced email parsing functions for better email address and name extraction119 */120 116 121 117 /** … … 185 181 186 182 /** 187 * Robust marketing email detection for NexlifyDesk. 183 * Decode MIME-encoded email subjects to human-readable format 184 * Fixes issue where subjects like "=?UTF-8?Q?Issue_with_Order_#5628_–_Missing_Items?=" 185 * appear encoded in tickets instead of being properly decoded 186 * 187 * @param string $subject The encoded email subject 188 * @return string The decoded subject 189 */ 190 function nexlifydesk_decode_email_subject($subject) { 191 if (empty($subject)) { 192 return ''; 193 } 194 195 $subject = trim($subject); 196 197 // Check if subject contains MIME encoding 198 if (strpos($subject, '=?') !== false && strpos($subject, '?=') !== false) { 199 // Use imap_mime_header_decode to properly decode the subject 200 $decoded_parts = imap_mime_header_decode($subject); 201 202 if (is_array($decoded_parts) && count($decoded_parts) > 0) { 203 $decoded_subject = ''; 204 foreach ($decoded_parts as $part) { 205 $decoded_subject .= $part->text; 206 } 207 return trim($decoded_subject); 208 } 209 } 210 211 // If no encoding detected or decoding failed, return original subject 212 return $subject; 213 } 214 215 /** 216 * Marketing email detection for NexlifyDesk. 188 217 * 189 218 * @param string $email_address The sender's email address. … … 194 223 */ 195 224 function nexlifydesk_is_marketing_email($email_address, $subject, $message, $settings = []) { 196 // Whitelist: never treat these as marketing197 225 $whitelist = $settings['marketing_whitelist'] ?? []; 198 226 if (in_array(strtolower($email_address), array_map('strtolower', $whitelist), true)) { … … 200 228 } 201 229 202 // Basic checks203 230 if (empty($email_address) || empty($subject) || empty($message)) { 204 231 return false; … … 232 259 $message_lower = strtolower($message); 233 260 234 // Sender check235 261 $is_marketing_sender = false; 236 262 foreach ($marketing_senders as $indicator) { … … 241 267 } 242 268 243 // Subject check244 269 $has_marketing_subject = false; 245 270 foreach ($marketing_subject_keywords as $keyword) { … … 250 275 } 251 276 252 // Content check253 277 $has_marketing_content = false; 254 278 foreach ($marketing_content_keywords as $keyword) { … … 259 283 } 260 284 261 // HTML patterns (common in marketing)262 285 $html_patterns = [ 263 286 '/<table[^>]*>.*?<\/table>/is', '/<img[^>]*src=[^>]*>/i', … … 272 295 } 273 296 274 // Count links275 297 preg_match_all('/https?:\/\/[^\s<>"{}|\\^`\[\]]+/i', $message, $matches); 276 298 $url_count = count($matches[0]); 277 299 278 // Scoring system279 300 $marketing_score = 0; 280 301 if ($is_marketing_sender) $marketing_score += 2; … … 296 317 297 318 /** 298 * Enhanced email thread detection and content extraction299 * Specifically designed to handle complex email thread patterns300 319 * 301 320 * @param string $email_content The full email content … … 306 325 return $email_content; 307 326 } 308 309 327 if (!empty($from_email) && function_exists('nexlifydesk_is_admin_or_agent_email') && nexlifydesk_is_admin_or_agent_email($from_email)) { 310 328 return $email_content; 311 329 } 312 313 if (!preg_match('/^(On\s+.*wrote:|From:.*@|-----Original Message-----)/im', $email_content)) { 314 $has_email_markers = preg_match('/\b(wrote:|From:.*@|Original Message|Reply above|Sent from my)/i', $email_content); 315 if (!$has_email_markers) { 316 return $email_content; 317 } 318 } 319 320 $thread_separators = [ 321 // Common formats from the user's example 322 '/^-{3,}\s*On\s+.+wrote\s*-{3,}/', // "---- On [date] [name] wrote ---" 323 '/^-{2,}\s*On\s+\w+,\s*\d{1,2}\s+\w+\s+\d{4}.+wrote\s*-{2,}/', // "-- On Tue, 15 Jul 2025 ... wrote --" 324 '/^On\s+\w+,\s*\d{1,2}\s+\w+\s+\d{4}.+wrote\s*:?$/', // "On Tue, 15 Jul 2025 ... wrote:" 325 '/^At\s+\d{1,2}:\d{2}.*on\s+\d{1,2}\/\d{1,2}\/\d{4}.*wrote:/', // "At 08:44 on 15/07/2025 ... wrote:" 326 327 // Standard email patterns 328 '/^-----Original Message-----/', 329 '/^From:.*@.*/', 330 '/^To:.*@.*/', 331 '/^Sent:.*\d{4}/', 332 '/^Date:.*\d{4}/', 333 '/^Subject:.*/', 334 '/^________________________________/', 335 '/^______________/', 336 337 '/^=+$/', 338 '/^-{10,}$/', 339 '/^_{10,}$/', 340 341 // Gmail/Outlook specific 342 '/^Reply above this line/', 343 '/^Please reply above this line/', 344 '/^Do not reply below this line/', 345 '/^-----\s*Reply\s*above\s*this\s*line\s*-----/', 346 347 // Mobile signatures 348 '/^Sent from my iPhone/', 349 '/^Sent from my iPad/', 350 '/^Sent from my Android/', 351 '/^Get Outlook for/', 352 353 // International formats 354 '/^Le\s+\d{1,2}\/\d{1,2}\/\d{4}.*a\s+écrit\s*:/', // French 355 '/^Am\s+\d{1,2}\.\d{1,2}\.\d{4}.*schrieb\s*:/', // German 356 '/^Il\s+\d{1,2}\/\d{1,2}\/\d{4}.*ha\s+scritto\s*:/', // Italian 357 '/^El\s+\d{1,2}\/\d{1,2}\/\d{4}.*escribió\s*:/', // Spanish 358 '/^Em\s+\d{1,2}\/\d{1,2}\/\d{4}.*escreveu\s*:/', // Portuguese 330 $body = str_replace(["\r\n", "\r"], "\n", $email_content); 331 $header_pattern = '/^On\s.+?(?:at\s.+?)?\s?.+?\swrote:\s*$/mus'; 332 $reply_separator_regexes = [ 333 $header_pattern, 334 '/^-{3,}\s*On\s+.+wrote\s*-{3,}/mus', 335 '/^-{2,}\s*On\s+\w+,\s*\d{1,2}\s+\w+\s+\d{4}.+wrote\s*-{2,}/mus', 336 '/^At\s+\d{1,2}:\d{2}.*on\s+\d{1,2}\/\d{1,2}\/\d{4}.*wrote:/mus', 337 '/^-----Original Message-----/mus', 338 '/^From:[\s\S]+?$/mus', 339 '/^To:[\s\S]+?$/mus', 340 '/^Sent:[\s\S]+?$/mus', 341 '/^Date:[\s\S]+?$/mus', 342 '/^Subject:[\s\S]+?$/mus', 343 '/^Reply above this line$/mus', 344 '/^Please reply above this line$/mus', 345 '/^Do not reply below this line$/mus', 346 '/^-----\s*Reply\s*above\s*this\s*line\s*-----/mus', 347 '/^Le\s+\d{1,2}\/\d{1,2}\/\d{4}.*a\s+écrit\s*:/mus', 348 '/^Am\s+\d{1,2}\.\d{1,2}\.\d{4}.*schrieb\s*:/mus', 349 '/^Il\s+\d{1,2}\/\d{1,2}\/\d{4}.*ha\s+scritto\s*:/mus', 350 '/^El\s+\d{1,2}\/\d{1,2}\/\d{4}.*escribió\s*:/mus', 351 '/^Em\s+\d{1,2}\/\d{1,2}\/\d{4}.*escreveu\s*:/mus', 352 '/=+$/mus', 353 '/-{10,}/u', 354 '/_{10,}/u', 359 355 ]; 360 361 $lines = explode("\n", $email_content); 362 $clean_lines = []; 363 364 foreach ($lines as $line) { 365 $trimmed_line = trim($line); 366 367 $is_thread_separator = false; 368 foreach ($thread_separators as $pattern) { 369 if (preg_match($pattern, $trimmed_line)) { 370 $is_thread_separator = true; 371 break; 372 } 373 } 374 375 if ($is_thread_separator) { 356 $separator_pos = false; 357 foreach ($reply_separator_regexes as $regex) { 358 if (preg_match($regex, $body, $match, PREG_OFFSET_CAPTURE)) { 359 $separator_pos = $match[0][1]; 376 360 break; 377 361 } 378 379 if (preg_match('/^>\s*/', $line)) { 380 if (stripos($line, 'wrote:') !== false || 381 stripos($line, 'original message') !== false || 382 stripos($line, 'from:') !== false || 383 stripos($line, 'sent:') !== false || 384 stripos($line, 'date:') !== false || 385 stripos($line, 'to:') !== false || 386 preg_match('/\d{4}/', $line)) { 387 break; 388 } 389 } 390 391 $clean_lines[] = $line; 392 } 393 394 $clean_content = implode("\n", $clean_lines); 395 396 $clean_content = preg_replace('/\n\s*\n\s*\n/', "\n\n", $clean_content); 397 $clean_content = trim($clean_content); 398 362 } 363 if ($separator_pos !== false) { 364 $body = substr($body, 0, $separator_pos); 365 } 366 $body = preg_replace('/^\s*(>|).*/m', '', $body); 367 $body = preg_replace('/\n\s*-\s*\n+/u', "\n- ", $body); 368 $body = preg_replace('/\n{2,}-/u', "\n-", $body); 369 $body = preg_replace('/-\s*\n{2,}/u', "-\n", $body); 370 $body = preg_replace('/-\s*\n+\s*-/', "-\n-", $body); 371 $body = preg_replace('/\n{3,}/u', "\n\n", $body); 372 $body = preg_replace('/[ \t]+$/m', '', $body); 373 $clean_content = trim($body); 399 374 return $clean_content; 400 375 } 401 376 402 /**403 * Extract customer details from email messages404 * Enhanced version that handles email threads better405 *406 * @param string $message The email message content407 * @return array Array with 'name', 'email', and 'message' keys408 */409 377 function nexlifydesk_extract_customer_details($message) { 410 378 if (empty($message)) { 411 return ['name' => '', 'email' => '', 'message' => $message]; 412 } 413 414 // First decode the email content to handle special characters and emojis 379 return ['name' => '', 'email' => '', 'message' => '']; 380 } 381 415 382 $decoded_message = nexlifydesk_decode_email_content($message); 416 417 // Then extract clean content 418 $clean_message = nexlifydesk_extract_clean_email_content($decoded_message, ''); 419 420 if (strlen($clean_message) < 50 && strlen($decoded_message) > 100) { 421 $clean_message = $decoded_message; 422 } 383 $clean_message = nexlifydesk_extract_clean_email_content($decoded_message); 423 384 424 385 $name = ''; 425 386 $email = ''; 426 427 if (preg_match('/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/', $clean_message, $matches)) { 428 $email = $matches[1]; 429 } 430 431 if (preg_match('/From:\s*([^<\n]+)\s*<([^>]+)>/', $clean_message, $matches)) { 432 $name = trim($matches[1]); 433 $email = trim($matches[2]); 434 } elseif (preg_match('/Name:\s*([^\n]+)/', $clean_message, $matches)) { 435 $name = trim($matches[1]); 436 } 437 387 $actual_message = $clean_message; 388 389 // Handle structured format with [Customer Details] and [Message]/[Reply] sections 390 if (preg_match('/\[Customer Details\]\s*(.*?)\s*\[(?:Message|Reply)\]\s*(.*)/s', $clean_message, $matches)) { 391 $customer_section = trim($matches[1]); 392 $actual_message = trim($matches[2]); 393 394 // Extract name and email from customer details section 395 if (preg_match('/Name:\s*([^\n\r]+)/i', $customer_section, $name_matches)) { 396 $name = trim($name_matches[1]); 397 } 398 if (preg_match('/Email:\s*([^\n\r]+)/i', $customer_section, $email_matches)) { 399 $email = trim($email_matches[1]); 400 } 401 } else { 402 // Fallback to original logic for other formats 403 if (preg_match('/From:\s*([^<\n]+)\s*<([^>]+)>/', $clean_message, $matches)) { 404 $name = trim($matches[1]); 405 $email = trim($matches[2]); 406 } elseif (preg_match('/Name:\s*([^\n]+)/', $clean_message, $matches)) { 407 $name = trim($matches[1]); 408 } 409 410 if (empty($email) && preg_match('/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/', $clean_message, $matches)) { 411 $email = $matches[1]; 412 } 413 } 414 415 // Generate name from email if needed 438 416 if (empty($name) && !empty($email)) { 439 417 $email_parts = explode('@', $email); … … 447 425 'name' => $name, 448 426 'email' => $email, 449 'message' => $ clean_message427 'message' => $actual_message // Now returns clean message without customer details 450 428 ]; 451 429 } … … 536 514 } 537 515 538 // Generate a new key for demonstration539 516 $sample_key = nexlifydesk_generate_encryption_key(); 540 517 … … 580 557 } 581 558 582 // Add AJAX handler for generating encryption keys583 559 add_action('wp_ajax_nexlifydesk_generate_encryption_key', 'nexlifydesk_ajax_generate_encryption_key'); 584 560 -
nexlifydesk/trunk/includes/nexlifydesk-functions.php
r3330741 r3333095 3 3 exit; 4 4 } 5 6 // Load necessary interfaces and classes for text analysis 7 require_once __DIR__ . '/textanalysis/Interfaces/IDistance.php'; 8 require_once __DIR__ . '/textanalysis/Interfaces/ISimilarity.php'; 9 require_once __DIR__ . '/textanalysis/Interfaces/ITokenTransformation.php'; 10 require_once __DIR__ . '/textanalysis/Interfaces/IStemmer.php'; 11 require_once __DIR__ . '/textanalysis/Interfaces/IExtractStrategy.php'; 12 require_once __DIR__ . '/textanalysis/Documents/DocumentAbstract.php'; 13 require_once __DIR__ . '/textanalysis/Documents/TokensDocument.php'; 14 require_once __DIR__ . '/textanalysis/Tokenizers/TokenizerAbstract.php'; 15 require_once __DIR__ . '/textanalysis/Tokenizers/WhitespaceTokenizer.php'; 16 require_once __DIR__ . '/textanalysis/Tokenizers/GeneralTokenizer.php'; 17 require_once __DIR__ . '/textanalysis/Comparisons/CosineSimilarityComparison.php'; 5 18 6 19 /** … … 132 145 return ''; 133 146 } 147 148 /** 149 * Extract order numbers from subject or message using flexible patterns 150 * @param string $text 151 * @return array Array of detected order numbers (as strings) 152 */ 153 function nexlifydesk_extract_order_numbers($text) { 154 $order_numbers = array(); 155 if (empty($text)) return $order_numbers; 156 // Match patterns like Order #1234, Order number 1234, #1234, 1234 157 $patterns = array( 158 '/order\s*(number)?\s*#?\s*(\d{4,})/i', // Order #1234, Order number 1234 159 '/#(\d{4,})/', // #1234 160 '/\b(\d{4,})\b/' // 1234 (standalone, 4+ digits) 161 ); 162 foreach ($patterns as $pattern) { 163 if (preg_match_all($pattern, $text, $matches)) { 164 foreach ($matches[2] ?? $matches[1] ?? array() as $num) { 165 if ($num && !in_array($num, $order_numbers)) { 166 $order_numbers[] = $num; 167 } 168 } 169 } 170 } 171 return $order_numbers; 172 } 173 174 /** 175 * Apply keyword mapping to normalize semantic tokens 176 * @param array $tokens Array of tokens to normalize 177 * @param array $map Keyword mapping array 178 * @return array Normalized tokens 179 */ 180 function nexlifydesk_apply_keyword_map($tokens, $map) { 181 foreach ($tokens as &$token) { 182 if (isset($map[$token])) { 183 $token = $map[$token]; 184 } 185 } 186 return $tokens; 187 } 188 189 /** 190 * Get common English stopwords for text processing 191 * @return array Array of stopwords 192 */ 193 function nexlifydesk_get_stopwords() { 194 return array('the','and','is','it','to','of','in','on','for','with','a','an','as','at','by','from','this','that','can','be','has','have','was','were','but','or','not','so','do','does','did','will','would','should','could','may','might','must','you','your','i','me','my','we','us','our','he','she','him','her','they','them','their'); 195 } 196 197 /** 198 * - Registered users: Check any existing tickets for order numbers or subject matches 199 * - If order numbers are found, check for existing tickets with matching order numbers 200 * - Unregistered users: Consolidate by email address only, no content matching needed 201 * 202 * @param array $ticket_data (user_id, email, subject, message, source) 203 * @return object|null Existing ticket if found, null otherwise 204 */ 205 function nexlifydesk_find_duplicate_ticket($ticket_data) { 206 global $wpdb; 207 $user_id = isset($ticket_data['user_id']) ? absint($ticket_data['user_id']) : 0; 208 $email = isset($ticket_data['email']) ? sanitize_email($ticket_data['email']) : ''; 209 $subject = isset($ticket_data['subject']) ? sanitize_text_field($ticket_data['subject']) : ''; 210 $message = isset($ticket_data['message']) ? sanitize_text_field($ticket_data['message']) : ''; 211 $table_name = $wpdb->prefix . 'nexlifydesk_tickets'; 212 213 // Registered users 214 if ($user_id > 0) { 215 $cache_key = 'nexlifydesk_user_has_tickets_' . $user_id; 216 $user_has_tickets = wp_cache_get($cache_key); 217 218 if (false === $user_has_tickets) { 219 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe. 220 $user_ticket_count = $wpdb->get_var( $wpdb->prepare(// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table name is safe and required direct query. 221 "SELECT COUNT(*) FROM `{$table_name}` WHERE user_id = %d AND status IN ('open','pending','in_progress')", 222 $user_id 223 ) ); 224 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe. 225 $user_has_tickets = (int)$user_ticket_count > 0; 226 wp_cache_set($cache_key, $user_has_tickets, '', 300); // Cache for 5 minutes 227 } 228 229 if (!$user_has_tickets) { 230 return null; 231 } 232 233 $order_numbers = array(); 234 if (!empty($subject)) { 235 $order_numbers = array_merge($order_numbers, nexlifydesk_extract_order_numbers($subject)); 236 } 237 if (!empty($message)) { 238 $order_numbers = array_merge($order_numbers, nexlifydesk_extract_order_numbers($message)); 239 } 240 $order_numbers = array_unique($order_numbers); 241 242 if (!empty($order_numbers)) { 243 $order_regex = implode('|', array_map('preg_quote', $order_numbers)); 244 $cache_key = 'nexlifydesk_user_order_match_' . md5($user_id . $order_regex); 245 $existing_ticket = wp_cache_get($cache_key); 246 247 if (false === $existing_ticket) { 248 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe. 249 $existing_ticket = $wpdb->get_row( $wpdb->prepare(// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table name is safe and required direct query. 250 "SELECT * FROM `{$table_name}` WHERE user_id = %d AND (subject REGEXP %s OR message REGEXP %s) AND status IN ('open','pending','in_progress') ORDER BY created_at DESC LIMIT 1", 251 $user_id, $order_regex, $order_regex 252 ) ); 253 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe. 254 wp_cache_set($cache_key, $existing_ticket, '', 60); 255 } 256 if ($existing_ticket) return $existing_ticket; 257 } 258 259 if (!empty($subject)) { 260 $cache_key = 'nexlifydesk_user_subject_match_' . md5($user_id . $subject); 261 $existing_ticket = wp_cache_get($cache_key); 262 263 if (false === $existing_ticket) { 264 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe. 265 $existing_ticket = $wpdb->get_row( $wpdb->prepare(// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table name is safe and required direct query. 266 "SELECT * FROM `{$table_name}` WHERE user_id = %d AND subject = %s AND status IN ('open','pending','in_progress') ORDER BY created_at DESC LIMIT 1", 267 $user_id, $subject 268 ) ); 269 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe. 270 wp_cache_set($cache_key, $existing_ticket, '', 60); 271 } 272 if ($existing_ticket) return $existing_ticket; 273 } 274 275 if (class_exists('TextAnalysis\\Comparisons\\CosineSimilarityComparison') && class_exists('TextAnalysis\\Tokenizers\\GeneralTokenizer')) { 276 $similarity_threshold = 0.12; 277 $cache_key = 'nexlifydesk_user_tickets_semantic_' . $user_id; 278 $user_tickets = wp_cache_get($cache_key); 279 280 if (false === $user_tickets) { 281 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe. 282 $user_tickets = $wpdb->get_results( $wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table name is safe and required direct query. 283 "SELECT * FROM `{$table_name}` WHERE user_id = %d AND status IN ('open','pending','in_progress') ORDER BY created_at DESC LIMIT 10", 284 $user_id 285 ) ); 286 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepated -- Table name is safe. 287 wp_cache_set($cache_key, $user_tickets, '', 60); 288 } 289 290 if (!empty($user_tickets)) { 291 $keyword_map = array( 292 'profile' => 'account', 'dashboard' => 'account', 'user' => 'account', 'panel' => 'account', 293 'restoring' => 'unlock', 'restored' => 'unlock', 'recovered' => 'unlock', 'reset' => 'unlock', 294 'unlock' => 'unlock', 'activate' => 'unlock', 'recover' => 'unlock', 'recovery' => 'unlock', 295 'lockedout' => 'locked', 'locked' => 'locked', 'blocked' => 'locked', 'out' => 'locked', 296 'credentials' => 'login', 'login' => 'login', 'signin' => 'login', 'sign' => 'login', 'access' => 'login', 297 'password' => 'password', 'passcode' => 'password', 'pwd' => 'password', 298 'help' => 'help', 'support' => 'help', 'assistance' => 'help', 'team' => 'help', 299 'issue' => 'problem', 'problem' => 'problem', 'error' => 'problem', 'failed' => 'problem', 300 'unable' => 'problem', 'cant' => 'problem', 'cannot' => 'problem', 'rejecting' => 'problem', 'not' => 'problem', 'anymore' => 'problem' 301 ); 302 303 $tokenizer = new TextAnalysis\Tokenizers\GeneralTokenizer(); 304 $stopwords = nexlifydesk_get_stopwords(); 305 $incoming_text = $subject . ' ' . $message; 306 $incoming_tokens = array_map(function($t) { 307 return strtolower(preg_replace('/[^a-z0-9]/i', '', $t)); 308 }, $tokenizer->tokenize($incoming_text)); 309 $incoming_tokens = array_diff($incoming_tokens, $stopwords); 310 $incoming_tokens = nexlifydesk_apply_keyword_map($incoming_tokens, $keyword_map); 311 312 foreach ($user_tickets as $ticket) { 313 $existing_text = $ticket->subject . ' ' . $ticket->message; 314 $existing_tokens = array_map(function($t) { 315 return strtolower(preg_replace('/[^a-z0-9]/i', '', $t)); 316 }, $tokenizer->tokenize($existing_text)); 317 $existing_tokens = array_diff($existing_tokens, $stopwords); 318 $existing_tokens = nexlifydesk_apply_keyword_map($existing_tokens, $keyword_map); 319 320 $similarity = (new TextAnalysis\Comparisons\CosineSimilarityComparison())->similarity( 321 $incoming_tokens, 322 $existing_tokens 323 ); 324 if ($similarity >= $similarity_threshold) { 325 return $ticket; 326 } 327 } 328 } 329 } 330 331 return null; 332 } 333 334 if ($user_id == 0 && !empty($email)) { 335 // For unregistered users, we simply check if there's ANY existing ticket for this email 336 $cache_key = 'nexlifydesk_email_consolidation_' . md5($email); 337 $existing_ticket = wp_cache_get($cache_key); 338 339 if (false === $existing_ticket) { 340 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe. 341 $existing_ticket = $wpdb->get_row( $wpdb->prepare(// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table name is safe and required direct query. 342 "SELECT t.* FROM `{$table_name}` t 343 LEFT JOIN `{$wpdb->prefix}postmeta` pm ON pm.post_id = t.id AND pm.meta_key = 'customer_email' 344 WHERE t.user_id = 0 AND pm.meta_value = %s AND t.status IN ('open','pending','in_progress','resolved') 345 ORDER BY t.created_at DESC LIMIT 1", 346 $email 347 ) ); 348 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe. 349 wp_cache_set($cache_key, $existing_ticket, '', 60); 350 } 351 352 return $existing_ticket; 353 } 354 355 return null; 356 } 357 358 /** 359 * Extract keywords from text for similarity comparison 360 * @param string $text Input text to process 361 * @return array Array of unique keywords 362 */ 363 function nexlifydesk_extract_keywords($text) { 364 $text = strtolower($text); 365 $text = preg_replace('/[^a-z0-9 ]/', ' ', $text); 366 $words = array_filter(explode(' ', $text)); 367 $stopwords = nexlifydesk_get_stopwords(); 368 $keywords = array_diff($words, $stopwords); 369 return array_unique($keywords); 370 } 371 372 /** 373 * Calculate similarity between two messages using keyword intersection 374 * @param string $msg1 First message 375 * @param string $msg2 Second message 376 * @return float Similarity score between 0 and 1 377 */ 378 function nexlifydesk_message_similarity($msg1, $msg2) { 379 $kw1 = nexlifydesk_extract_keywords($msg1); 380 $kw2 = nexlifydesk_extract_keywords($msg2); 381 if (empty($kw1) || empty($kw2)) return 0; 382 $intersection = array_intersect($kw1, $kw2); 383 $union = array_unique(array_merge($kw1, $kw2)); 384 return count($intersection) / count($union); 385 } -
nexlifydesk/trunk/nexlifydesk.php
r3330879 r3333095 3 3 * Plugin Name: NexlifyDesk 4 4 * Description: A modern, user-friendly support ticket system for WordPress with ticket submission, threaded replies, file attachments, agent assignment, and customizable. 5 * Version: 1.0. 25 * Version: 1.0.3 6 6 * Author URI: https://nexlifylabs.com 7 7 * Supported Versions: 6.2+ … … 48 48 // Signal that SDK was initiated. 49 49 do_action( 'nexlifydesk_loaded' ); 50 51 // Register Freemius uninstall handler 52 nexlifydesk()->add_action('after_uninstall', 'nexlifydesk_freemius_uninstall_cleanup'); 50 53 } 51 54 52 55 define('NEXLIFYDESK_PLUGIN_DIR', plugin_dir_path(__FILE__)); 53 56 define('NEXLIFYDESK_PLUGIN_URL', plugin_dir_url(__FILE__)); 54 define('NEXLIFYDESK_VERSION', '1.0. 2');57 define('NEXLIFYDESK_VERSION', '1.0.3'); 55 58 define('NEXLIFYDESK_TABLE_PREFIX', 'nexlifydesk_'); 56 59 define('NEXLIFYDESK_CAP_VIEW_ALL_TICKETS', 'nexlifydesk_view_all_tickets'); … … 91 94 } 92 95 add_action('plugins_loaded', 'nexlifydesk_init'); 96 add_action('admin_notices', 'nexlifydesk_show_template_update_notice'); 97 add_action('wp_ajax_nexlifydesk_update_email_templates', 'nexlifydesk_ajax_update_email_templates'); 98 99 function nexlifydesk_show_template_update_notice() { 100 // Only show to admins on NexlifyDesk pages 101 if (!current_user_can('manage_options')) { 102 return; 103 } 104 105 $screen = get_current_screen(); 106 if (!$screen || strpos($screen->id, 'nexlifydesk') === false) { 107 return; 108 } 109 110 $existing_templates = get_option('nexlifydesk_email_templates', array()); 111 $needs_update = false; 112 113 if (!empty($existing_templates)) { 114 foreach (['new_ticket', 'new_reply', 'status_changed', 'sla_breach'] as $template_type) { 115 if (isset($existing_templates[$template_type]) && 116 !empty($existing_templates[$template_type]) && 117 strpos($existing_templates[$template_type], '{user_name}') !== false) { 118 $needs_update = true; 119 break; 120 } 121 } 122 } 123 124 if ($needs_update) { 125 ?> 126 <div class="notice notice-warning is-dismissible" id="nexlifydesk-template-update-notice"> 127 <p> 128 <strong><?php esc_html_e('NexlifyDesk Email Templates Update Available', 'nexlifydesk'); ?></strong><br> 129 <?php esc_html_e('Your email templates are using the old format. Click below to update them to use the new professional template files with better styling and functionality.', 'nexlifydesk'); ?> 130 </p> 131 <p> 132 <button type="button" class="button button-primary" onclick="nexlifydesk_update_templates()"> 133 <?php esc_html_e('Update Email Templates', 'nexlifydesk'); ?> 134 </button> 135 <button type="button" class="button" onclick="nexlifydesk_dismiss_notice()"> 136 <?php esc_html_e('Dismiss (Keep Current Templates)', 'nexlifydesk'); ?> 137 </button> 138 </p> 139 </div> 140 <script> 141 function nexlifydesk_update_templates() { 142 if (confirm('<?php esc_html_e('This will clear your current email templates and use the new professional template files. You can customize them later in Email Templates settings. Continue?', 'nexlifydesk'); ?>')) { 143 var xhr = new XMLHttpRequest(); 144 xhr.open('POST', ajaxurl, true); 145 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 146 xhr.onreadystatechange = function() { 147 if (xhr.readyState === 4) { 148 if (xhr.status === 200) { 149 document.getElementById('nexlifydesk-template-update-notice').style.display = 'none'; 150 alert('<?php esc_html_e('Email templates updated successfully! Check the Email Templates page to see the changes.', 'nexlifydesk'); ?>'); 151 } else { 152 alert('<?php esc_html_e('Error updating templates. Please try again.', 'nexlifydesk'); ?>'); 153 } 154 } 155 }; 156 xhr.send('action=nexlifydesk_update_email_templates&nonce=<?php echo esc_attr( wp_create_nonce('nexlifydesk_update_templates') ); ?>'); 157 } 158 } 159 160 function nexlifydesk_dismiss_notice() { 161 document.getElementById('nexlifydesk-template-update-notice').style.display = 'none'; 162 } 163 </script> 164 <?php 165 } 166 } 167 168 function nexlifydesk_ajax_update_email_templates() { 169 // Verify nonce and permissions 170 if (!current_user_can('manage_options') || 171 !isset($_POST['nonce']) || 172 !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'nexlifydesk_update_templates')) { 173 wp_die('Unauthorized'); 174 } 175 176 $templates = array( 177 'new_ticket' => '', 178 'new_reply' => '', 179 'status_changed' => '', 180 'sla_breach' => '', 181 'email_auto_response' => get_option('nexlifydesk_email_templates', array())['email_auto_response'] ?? '', 182 ); 183 184 update_option('nexlifydesk_email_templates', $templates); 185 186 wp_send_json_success(array('message' => __('Email templates updated successfully!', 'nexlifydesk'))); 187 } 93 188 94 189 // Activation function … … 105 200 update_option('nexlifydesk_settings', $default_settings); 106 201 } 202 203 nexlifydesk_load_default_email_templates(); 204 } 205 206 /** 207 * Load email templates from template files and populate the admin interface 208 */ 209 function nexlifydesk_load_default_email_templates() { 210 $existing_templates = get_option('nexlifydesk_email_templates', array()); 211 212 $needs_update = empty($existing_templates) || 213 (isset($existing_templates['new_reply']) && 214 strpos($existing_templates['new_reply'], '{user_name}') !== false); 215 216 if ($needs_update) { 217 $templates = array( 218 'new_ticket' => '', 219 'new_reply' => '', 220 'status_changed' => '', 221 'sla_breach' => '', 222 'email_auto_response' => '<p>Thank you for contacting us. We have received your support request and have assigned it ticket ID <strong>#{ticket_id}</strong>.</p> 223 224 <p><strong>Subject:</strong> {subject}</p> 225 226 <p>Our support team will review your request and get back to you as soon as possible. You can reference this ticket ID in any future correspondence.</p> 227 228 <p>Best regards,<br> 229 {site_name} Support Team<br> 230 <a href="{site_url}">{site_url}</a></p>', 231 ); 232 233 update_option('nexlifydesk_email_templates', $templates); 234 } 107 235 } 108 236 … … 114 242 wp_clear_scheduled_hook('nexlifydesk_auto_close_tickets'); 115 243 116 // Default email templates117 $default_templates = array(118 'new_ticket' => '<p>Dear {user_name},</p><p>Your ticket <strong>#{ticket_id}</strong> has been received.</p><p>Subject: {subject}</p><p>Message: {message}</p><p>We will get back to you soon.</p>',119 'new_reply' => '<p>Dear {user_name},</p><p>You have a new reply on ticket <strong>#{ticket_id}</strong>.</p><p>Reply: {reply_message}</p>',120 'status_changed' => '<p>Dear {user_name},</p><p>The status of your ticket <strong>#{ticket_id}</strong> has changed to <strong>{status}</strong>.</p>',121 'sla_breach' => '<p>Attention: Ticket <strong>#{ticket_id}</strong> has breached its SLA.</p>',122 );123 124 if (!get_option('nexlifydesk_email_templates')) {125 add_option('nexlifydesk_email_templates', $default_templates);126 }127 244 } 128 245 … … 289 406 }, 999); 290 407 291 292 408 /** 293 * Uninstall cleanup for NexlifyDesk. 294 * Removes options, transients, roles, capabilities, custom tables, and uploaded files. 409 * Freemius uninstall cleanup handler 295 410 */ 296 function nexlifydesk_uninstall_cleanup() { 297 global $wpdb; 298 299 // Get settings to check if data should be kept 300 $settings = get_option('nexlifydesk_settings', array()); 301 $keep_data = isset($settings['keep_data_on_uninstall']) ? (bool)$settings['keep_data_on_uninstall'] : true; 302 303 // Always remove scheduled hooks and transients 304 wp_clear_scheduled_hook('nexlifydesk_sla_check'); 305 wp_clear_scheduled_hook('nexlifydesk_auto_close_tickets'); 306 wp_clear_scheduled_hook('nexlifydesk_check_orphaned_tickets'); 307 delete_transient('nexlifydesk_sla_tickets'); 308 delete_transient('nexlifydesk_resolved_tickets'); 309 wp_cache_flush(); 310 311 if ($keep_data) { 312 // Remove only version option if keeping data 313 delete_option('nexlifydesk_db_version'); 314 return; 315 } 316 317 // Remove all plugin options 318 delete_option('nexlifydesk_settings'); 319 delete_option('nexlifydesk_email_templates'); 320 delete_option('nexlifydesk_last_ticket_number'); 321 delete_option('nexlifydesk_db_version'); 322 323 // Remove custom database tables 324 $table_names = array( 325 $wpdb->prefix . 'nexlifydesk_tickets', 326 $wpdb->prefix . 'nexlifydesk_replies', 327 $wpdb->prefix . 'nexlifydesk_attachments', 328 $wpdb->prefix . 'nexlifydesk_categories' 329 ); 330 require_once ABSPATH . 'wp-admin/includes/upgrade.php'; 331 foreach ($table_names as $table_name) { 332 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange -- Plugin uninstall requires direct table deletion 333 $wpdb->query("DROP TABLE IF EXISTS " . esc_sql($table_name)); 334 } 335 336 // Remove custom roles 337 remove_role('nexlifydesk_agent'); 338 remove_role('nexlifydesk_supervisor'); 339 340 // Remove custom capabilities from administrator 341 $admin_role = get_role('administrator'); 342 if ($admin_role) { 343 $capabilities = array( 344 'nexlifydesk_manage_tickets', 345 'nexlifydesk_view_all_tickets', 346 'nexlifydesk_assign_tickets', 347 'nexlifydesk_manage_categories', 348 'nexlifydesk_view_reports' 349 ); 350 foreach ($capabilities as $cap) { 351 $admin_role->remove_cap($cap); 411 function nexlifydesk_freemius_uninstall_cleanup() { 412 $uninstall_file = plugin_dir_path(__FILE__) . 'uninstall.php'; 413 if (file_exists($uninstall_file)) { 414 if (!defined('WP_UNINSTALL_PLUGIN')) { 415 define('WP_UNINSTALL_PLUGIN', true); 352 416 } 353 } 354 355 // Remove uploaded files directory 356 $upload_dir = wp_upload_dir(); 357 $plugin_upload_dir = trailingslashit($upload_dir['basedir']) . 'nexlifydesk/'; 358 if (is_dir($plugin_upload_dir)) { 359 nexlifydesk_delete_directory($plugin_upload_dir); 360 } 361 } 362 363 /** 364 * Recursively delete a directory and its files. 365 */ 366 function nexlifydesk_delete_directory($dir) { 367 if (!is_dir($dir)) { 368 return false; 369 } 370 $files = array_diff(scandir($dir), array('.', '..')); 371 foreach ($files as $file) { 372 $path = $dir . DIRECTORY_SEPARATOR . $file; 373 if (is_dir($path)) { 374 nexlifydesk_delete_directory($path); 375 } else { 376 wp_delete_file($path); 377 } 378 } 379 global $wp_filesystem; 380 if (empty($wp_filesystem)) { 381 require_once(ABSPATH . '/wp-admin/includes/file.php'); 382 WP_Filesystem(); 383 } 384 return $wp_filesystem->rmdir($dir, true); 385 } 386 387 register_uninstall_hook(__FILE__, 'nexlifydesk_uninstall_cleanup'); 417 include_once $uninstall_file; 418 } 419 } 420 421 register_uninstall_hook(__FILE__, 'nexlifydesk_freemius_uninstall_cleanup'); 388 422 389 423 add_action('wp_ajax_nexlifydesk_update_status', 'nexlifydesk_update_status_callback'); … … 424 458 }); 425 459 426 // AJAX handler for Purge Data button427 460 add_action('wp_ajax_nexlifydesk_purge_data', function() { 428 461 if (!current_user_can('manage_options')) { … … 432 465 global $wpdb; 433 466 $purged = array(); 434 // Example: Purge tickets older than 1 year and their replies/attachments467 435 468 $tickets_table = $wpdb->prefix . 'nexlifydesk_tickets'; 436 469 $replies_table = $wpdb->prefix . 'nexlifydesk_replies'; … … 438 471 $one_year_ago = gmdate('Y-m-d H:i:s', strtotime('-1 year')); 439 472 440 //Custom table maintenance requires direct queries without caching441 473 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is constructed from $wpdb->prefix and is safe 442 474 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table maintenance requires direct queries without caching … … 470 502 add_action('nexlifydesk_fetch_emails_event', 'nexlifydesk_fetch_emails'); 471 503 472 // Function to get the current fetch interval setting473 504 function nexlifydesk_get_fetch_interval() { 474 505 $settings = get_option('nexlifydesk_imap_settings', []); … … 483 514 } 484 515 485 // Function to reschedule email fetch with new interval486 516 function nexlifydesk_reschedule_email_fetch() { 487 517 $interval = nexlifydesk_get_fetch_interval(); 488 518 $schedule_name = "nexlifydesk_{$interval}_minutes"; 489 519 490 // Clear existing schedule491 520 wp_clear_scheduled_hook('nexlifydesk_fetch_emails_event'); 492 521 493 // Schedule with new interval494 522 if (!wp_next_scheduled('nexlifydesk_fetch_emails_event')) { 495 523 wp_schedule_event(time(), $schedule_name, 'nexlifydesk_fetch_emails_event'); … … 497 525 } 498 526 499 // Schedule email fetch on plugin activation500 527 register_activation_hook(__FILE__, function() { 501 528 nexlifydesk_reschedule_email_fetch(); 502 529 }); 503 530 504 // Reschedule when settings are updated505 531 add_action('update_option_nexlifydesk_imap_settings', function($old_value, $value, $option) { 506 532 $old_interval = isset($old_value['fetch_interval']) ? intval($old_value['fetch_interval']) : 5; 507 533 $new_interval = isset($value['fetch_interval']) ? intval($value['fetch_interval']) : 5; 508 534 509 // If interval changed, reschedule510 535 if ($old_interval !== $new_interval) { 511 536 nexlifydesk_reschedule_email_fetch(); … … 513 538 }, 10, 3); 514 539 515 // Fallback: schedule on 'init' for multisite/cron edge cases516 540 add_action('init', function() { 517 541 if (!wp_next_scheduled('nexlifydesk_fetch_emails_event')) { … … 520 544 }); 521 545 522 // Add custom intervals523 546 add_filter('cron_schedules', function($schedules) { 524 547 $schedules['nexlifydesk_2_minutes'] = array( … … 537 560 }); 538 561 539 // AJAX handler for instant email fetch540 562 add_action('wp_ajax_nexlifydesk_fetch_emails_now', function() { 541 563 check_ajax_referer('nexlifydesk_fetch_emails_now'); 542 564 543 // Use a more permissive permission check544 565 if (!current_user_can('nexlifydesk_manage_tickets') && !current_user_can('manage_options')) { 545 566 wp_send_json_error(__('You do not have permission.', 'nexlifydesk')); -
nexlifydesk/trunk/readme.txt
r3330879 r3333095 4 4 Requires at least: 6.2 5 5 Tested up to: 6.8 6 Stable tag: 1.0. 26 Stable tag: 1.0.3 7 7 License: GPLv2 or later 8 8 License URI: https://www.gnu.org/licenses/gpl-2.0.html 9 9 Requires PHP: 7.4 10 10 11 Hey there! NexlifyDesk is my awesome WordPress support ticketing system that turns your site into a pro customer service hub. It’s built for scalability and efficiency, giving you everything you need to wow your customers while keeping full control of their data. 11 Enterprise-grade WordPress helpdesk solution with intelligent ticket management, email piping, agent workflows, and WooCommerce integration. 12 13 == Description == 14 15 NexlifyDesk transforms your WordPress site into a comprehensive customer support platform. Featuring advanced ticket management, intelligent duplicate detection, multi-channel email integration, and seamless WooCommerce connectivity, it delivers enterprise-level support capabilities while maintaining complete data control and security. 12 16 13 17 **Documentation**: Check out the [Full Documentation & Setup Guide](https://nexlifylabs.com/nexlifydesk-documentation/getting-started/) 14 18 15 == Description == 16 17 NexlifyDesk is my go-to, enterprise-grade ticketing solution for WordPress. I designed it to make customer support a breeze while keeping things organized and secure. Here’s what I’ve packed into it: 18 19 - A complete system to manage tickets, agents, and workflows 20 - Internal notes for team chats 21 - SLA monitoring to keep things on track 22 - Seamless WooCommerce integration 23 24 It’s all about delivering top-notch support experiences—trust me, you’ll love it! 19 NexlifyDesk is a comprehensive, enterprise-grade helpdesk solution designed specifically for WordPress. Built with scalability, security, and efficiency at its core, it provides everything you need to deliver exceptional customer support while maintaining complete control over your data and workflows. 20 21 **Why Choose NexlifyDesk?** 22 23 Transform your customer support with a professional ticketing system that grows with your business. Whether you're a small business handling dozens of tickets or an enterprise managing thousands, NexlifyDesk provides the tools, automation, and insights you need to deliver outstanding customer experiences. 24 25 **Core Capabilities:** 26 - **Advanced Ticket Management** - Complete lifecycle management with intelligent routing and automation 27 - **Multi-Channel Email Integration** - Convert emails to tickets with IMAP/POP3, AWS WorkMail, and Google Workspace support 28 - **Intelligent Duplicate Detection** - AI-powered semantic analysis prevents ticket fragmentation 29 - **WooCommerce Integration** - Deep integration with order history and customer context 30 - **Enterprise Security** - Built-in spam protection, rate limiting, and secure file handling 25 31 26 32 == Key Features == 27 33 28 34 **Frontend Customer Experience** 29 - Ticket Submission & Management: I’ve added a clean, user-friendly interface where customers can submit tickets, check their history, and track progress. Super easy!30 - Real-time Updates: An AJAX-powered setup with live status updates and instant reply notifications—keeps everything snappy!31 - File Attachments: Supports multiple file types with configurable size limits and security checks. Safety first!32 - Responsive Design: Works like a charm on desktop, tablet, or mobile—optimized for all!35 - **Ticket Submission & Management** - Clean, user-friendly interface for ticket submission, history tracking, and progress monitoring 36 - **Real-time Updates** - AJAX-powered interface with live status updates and instant reply notifications 37 - **File Attachments** - Multiple file type support with configurable size limits and security validation 38 - **Responsive Design** - Optimized experience across desktop, tablet, and mobile devices 33 39 34 40 **Advanced Admin Dashboard** 35 - Centralized Ticket Management: A comprehensive admin interface with filtering, search, and bulk actions. I made it powerful yet simple!36 - Real-time Statistics: A live dashboard showing ticket counts, response times, and performance metrics. Love the insights!37 - Agent Assignment: Intelligent auto-assignment or manual options with load balancing—keeps the workload fair!38 - Status Management: Five ticket statuses (Open, In Progress, Pending, Resolved, Closed) with automatic workflows. Smooth sailing!41 - **Centralized Ticket Management** - Comprehensive admin interface with filtering, search, and bulk operations 42 - **Real-time Statistics** - Live dashboard with ticket counts, response times, and performance metrics 43 - **Agent Assignment** - Intelligent auto-assignment with manual override and load balancing capabilities 44 - **Status Management** - Five ticket statuses (Open, In Progress, Pending, Resolved, Closed) with automated workflows 39 45 40 46 **Agent Roles & Permissions System** 41 - Custom Agent Positions: I let you create unlimited agent roles with granular permission control—total flexibility!42 - Capability Management: Fine-tune what each agent can do (view all tickets, assign them, manage categories, etc.). You’re in charge!43 - Agent Performance Tracking: Monitor response times, resolution rates, and workload distribution. Great for team management!44 - Orphaned Ticket Management: Automatically reassigns tickets if an agent leaves—handled it for you!47 - **Custom Agent Positions** - Unlimited agent roles with granular permission control and capability management 48 - **Performance Tracking** - Monitor response times, resolution rates, and workload distribution analytics 49 - **Capability Management** - Fine-grained control over agent permissions (view tickets, assignments, categories, reports) 50 - **Orphaned Ticket Management** - Automatic ticket reassignment when agents are deactivated or removed 45 51 46 52 **Professional Communication Tools** 47 - Internal Notes: Private agent-to-agent chats within tickets (customers won’t see them—shh!). Perfect for teamwork!48 - Email Notifications: Fully customizable templates with dynamic placeholders for every scenario. I made it fun to tweak!49 - Notification Control: Granular settings for when and how notifications go out—your call!50 - Professional Templates: Pre-designed email templates for new tickets, replies, status changes, and SLA breaches. Ready to go!53 - **Internal Notes** - Private agent-to-agent communication within tickets (invisible to customers) 54 - **Email Notifications** - Fully customizable templates with dynamic placeholders and conditional delivery 55 - **Notification Control** - Granular settings for notification timing, recipients, and trigger conditions 56 - **Professional Templates** - Pre-designed email templates for all ticket lifecycle events and SLA notifications 51 57 52 58 **Intelligent Automation** 53 - Enhanced Duplicate Detection: My advanced multi-layer algorithms spot similar tickets and merge them automatically—way more accurate now!54 - Auto-Assignment: Smartly distributes tickets to available agents based on workload. Hands-off genius!55 - SLA Monitoring: Tracks and notifies on breaches to maintain service standards—keeps you on your toes!56 - Auto-Closure: Resolved tickets close after 48 hours of inactivity with system notifications. Neat, right?59 - **Enhanced Duplicate Detection** - Advanced multi-layer algorithms with semantic text analysis and automatic merging 60 - **Smart Auto-Assignment** - Intelligent ticket distribution based on agent availability and workload balancing 61 - **SLA Monitoring** - Automated tracking and breach notifications to maintain service level agreements 62 - **Auto-Closure** - Automatic ticket closure after 48 hours of inactivity with system notifications 57 63 58 64 **Email Piping & Integration** 59 - Multi-Provider Support: Turns emails into tickets with Custom IMAP/POP3, AWS WorkMail, and Google Workspace/Gmail. I’ve got you covered!60 - Flexible Email Management: Option to keep or delete emails after ticket creation—your choice!61 - Advanced Spam Protection: Built-in filtering, blocking, and rate limiting to stop abuse. Security’s my thing!62 - Intelligent Email Processing: Auto sender detection, thread management, and duplicate prevention. Smart stuff!65 - **Multi-Provider Support** - Convert emails to tickets with Custom IMAP/POP3, AWS WorkMail, and Google Workspace/Gmail integration 66 - **Flexible Email Management** - Configurable options to retain or delete emails after ticket creation 67 - **Advanced Spam Protection** - Built-in filtering, blocking, and rate limiting to prevent abuse and maintain security 68 - **Intelligent Email Processing** - Automatic sender detection, thread management, and duplicate prevention algorithms 63 69 64 70 **Enhanced Admin Experience** 65 - Real-time Ticket List: Live-updating interface with read/unread status indicators. I love the flow!66 - Smart Sorting: Unread tickets show up first—prioritizes what matters!67 - Instant Notifications: Real-time updates without refreshing—smooth as butter!68 - Bulk Operations: Enhanced bulk actions for quick ticket management. Saves time!71 - **Real-time Ticket Management** - Live-updating interface with read/unread status indicators and priority sorting 72 - **Smart Prioritization** - Unread tickets automatically surface first for immediate attention 73 - **Instant Notifications** - Real-time updates without page refresh for seamless workflow management 74 - **Bulk Operations** - Enhanced bulk actions for efficient ticket management and workflow optimization 69 75 70 76 **Categories & Organization** 71 - Unlimited Categories: Organize tickets with custom categories and descriptions. Go wild!72 - Priority Levels: Four levels (Low, Medium, High, Urgent) with visual indicators. Easy to spot!73 - Search & Filtering: Advanced search across all fields with multiple filter options. Find anything!74 - Bulk Operations: Manage multiple tickets at once—efficiency boost!77 - **Unlimited Categories** - Organize tickets with custom categories, descriptions, and hierarchical structures 78 - **Priority Management** - Four priority levels (Low, Medium, High, Urgent) with visual indicators and automated workflows 79 - **Advanced Search & Filtering** - Comprehensive search across all fields with multiple filter combinations 80 - **Bulk Operations** - Manage multiple tickets simultaneously for improved efficiency 75 81 76 82 **WooCommerce Integration** 77 - Order History: Built-in order lookup for WooCommerce stores. Handy!78 - Customer Context: Access order info directly from tickets—context is king!79 - Order-based Duplicate Detection: Links tickets to existing order conversations automatically. Clever, huh?83 - **Order History Access** - Built-in order lookup functionality for WooCommerce stores with comprehensive order details 84 - **Customer Context** - Direct access to order information from within tickets for enhanced customer support 85 - **Order-based Duplicate Detection** - Intelligent linking of tickets to existing order conversations for context preservation 80 86 81 87 **Reporting & Analytics** 82 - Performance Metrics: Reports on ticket volume, response times, and agent performance. Data nerd approved!83 - Visual Charts: Interactive charts for ticket trends, priority distribution, and monthly stats. Pretty cool!84 - Recent Activity: Real-time feed of all support activities across your team. Stay in the loop!88 - **Performance Metrics** - Comprehensive reports on ticket volume, response times, and agent performance analytics 89 - **Visual Analytics** - Interactive charts for ticket trends, priority distribution, and monthly statistical analysis 90 - **Activity Monitoring** - Real-time feed of all support activities across your team for operational transparency 85 91 86 92 **Developer & Advanced Features** 87 - Template Override System: Customize frontend templates by copying to your theme. I made it developer-friendly!88 - Shortcode System: Flexible shortcodes like `[nexlifydesk_ticket_form]` and `[nexlifydesk_ticket_list]` with customizable attributes.89 - Data Management: Configurable retention with optional purge functionality. Your data, your rules!90 - Rate Limiting: Built-in protection against spam and abuse. Safety first!91 - Caching System: Optimized performance with smart cache management. Fast and reliable!93 - **Template Override System** - Customize frontend templates by copying to your theme for complete design control 94 - **Shortcode System** - Flexible shortcodes like `[nexlifydesk_ticket_form]` and `[nexlifydesk_ticket_list]` with customizable attributes 95 - **Data Management** - Configurable retention policies with optional purge functionality for compliance requirements 96 - **Rate Limiting** - Built-in protection against spam and abuse with configurable thresholds 97 - **Caching System** - Optimized performance with intelligent cache management for enhanced responsiveness 92 98 93 99 == Duplicate Ticket Detection == 94 100 95 I’ve built a slick three-layer system to keep your support queues tidy: 96 97 - **Exact Subject Matching**: Spots identical subjects from the same user in the last 30 days and adds new messages as replies. No fragmentation! 98 - **Order Number Pattern Recognition**: Recognizes order/invoice numbers (like "Order #12345" or "#ABC123") and links all related chats to one thread. Smart! 99 - **Content Similarity Analysis**: Compares keywords between new and recent tickets from the same user, with a configurable 80% similarity threshold. Filters out stop words too! 101 NexlifyDesk features an advanced three-layer duplicate detection system designed to maintain organized support queues and prevent ticket fragmentation: 102 103 **Detection Layers:** 104 - **Exact Subject Matching** - Identifies identical subjects from the same user within the last 30 days and automatically adds new messages as replies to existing tickets 105 - **Order Number Pattern Recognition** - Recognizes order/invoice number patterns (such as "Order #12345" or "#ABC123") and intelligently links related communications to unified conversation threads 106 - **Semantic Content Analysis** - Employs advanced cosine similarity algorithms to compare keywords between new and recent tickets from the same user, with configurable 80% similarity threshold and intelligent stopword filtering 100 107 101 108 **User Experience** 102 When a duplicate pops up, users get a clear note that their message joined an existing convo—keeps things contextual!109 When duplicate tickets are detected, users receive clear notifications that their message has been added to an existing conversation, maintaining contextual continuity and communication history. 103 110 104 111 **Administrative Control** 105 - Turn duplicate detection on/off in NexlifyDesk > Settings 106 - Tweak the sensitivity threshold to fit your workflow 107 - Check duplicate stats in the reports dashboard 112 - Enable or disable duplicate detection in NexlifyDesk > Settings 113 - Adjust sensitivity thresholds to match your workflow requirements 114 - Monitor duplicate detection statistics and effectiveness in the reports dashboard 115 - Configure semantic analysis parameters for optimal accuracy 108 116 109 117 == Installation == 110 118 111 Here’s how I suggest you get started: 112 113 1. ** Upload the Plugin**: Pop the `nexlifydesk` folder into `/wp-content/plugins/` via FTP, or upload the ZIP directly in WordPress admin.114 2. **Activat e**: Turn it on via the 'Plugins' menu in WordPress.115 3. **Initial Configuration**: Head to NexlifyDesk > Settings to set up:116 - Email notification pref s117 - File upload limits and types118 - Default ticket priority and category 119 - SLA response time targets 120 - Auto-assignment rules 121 4. ** Create Frontend Pages**: Set up user pages:122 - **Ticket Submission Page**: Make a page and add`[nexlifydesk_ticket_form]`123 - **Ticket History Page**: Add `[nexlifydesk_ticket_list]` to another page124 - Link them in NexlifyDesk > Settings fornavigation119 **Quick Setup Process:** 120 121 1. **Plugin Installation**: Upload the `nexlifydesk` folder to `/wp-content/plugins/` via FTP, or upload the ZIP file directly through WordPress admin dashboard 122 2. **Activation**: Activate the plugin through the 'Plugins' menu in WordPress administration 123 3. **Initial Configuration**: Navigate to NexlifyDesk > Settings to configure: 124 - Email notification preferences and delivery settings 125 - File upload limits, allowed types, and security validation 126 - Default ticket priority and category assignments 127 - SLA response time targets and breach notifications 128 - Auto-assignment rules and agent load balancing 129 4. **Frontend Page Setup**: Create user-facing pages for ticket management: 130 - **Ticket Submission Page**: Create a new page and add the shortcode `[nexlifydesk_ticket_form]` 131 - **Ticket History Page**: Create another page and add the shortcode `[nexlifydesk_ticket_list]` 132 - Configure page links in NexlifyDesk > Settings for seamless navigation 125 133 5. **Email Piping Setup** (Optional): Configure email-to-ticket conversion: 126 - Pick yourprovider (Custom IMAP/POP3, AWS WorkMail, or Google Workspace)127 - Enter connection details and auth 128 - Set spam protection and filtering rules129 - Decide to keep or delete emails130 6. **Agent Setup** (Optional): Get your team ready:131 - Create user accounts foragents132 - Assign the "NexlifyDesk Agent" role 133 - Define custom positions withcapabilities134 - Set auto-assignment rules134 - Select your email provider (Custom IMAP/POP3, AWS WorkMail, or Google Workspace) 135 - Enter connection details and authentication credentials 136 - Configure spam protection rules and filtering criteria 137 - Set email retention preferences (keep or delete after processing) 138 6. **Agent Team Setup** (Optional): Prepare your support team: 139 - Create WordPress user accounts for support agents 140 - Assign the "NexlifyDesk Agent" role to team members 141 - Define custom agent positions with specific capabilities 142 - Configure automatic assignment rules for optimal workload distribution 135 143 136 144 == Frequently Asked Questions == 137 145 138 146 = How do I create a support ticket submission form? = 139 Create a new WordPress page and drop in `[nexlifydesk_ticket_form]`. Tweak it with:140 - `show_title="no"` to hide the title141 - Includes fields for subject, message, category, priority, and file attachments147 Create a new WordPress page and add the shortcode `[nexlifydesk_ticket_form]`. You can customize the form with additional attributes: 148 - `show_title="no"` to hide the page title 149 - The form includes fields for subject, message, category selection, priority level, and file attachments 142 150 143 151 = How can customers view their submitted tickets? = 144 Make a page with `[nexlifydesk_ticket_list]`. Logged-in users see their tickets, agents see assigned ones. Try:145 - `show_title="no"` to hide the title146 - `status="open"` to filter bystatus152 Create a page with the shortcode `[nexlifydesk_ticket_list]`. Logged-in customers will see their own tickets, while agents will see their assigned tickets. Customization options include: 153 - `show_title="no"` to hide the page title 154 - `status="open"` to filter tickets by specific status 147 155 148 156 = Can I customize the email notifications? = 149 Ye p! Go to NexlifyDesk > Email Templates to tweak:150 - New Ticket , New Reply, Status Changed, SLA Breach151 Use placeholders like `{ticket_id}`, `{user_name}`, `{subject}`, and more! 157 Yes! Navigate to NexlifyDesk > Email Templates to customize all notification emails: 158 - New Ticket notifications, New Reply alerts, Status Change notifications, and SLA Breach warnings 159 - Use dynamic placeholders like `{ticket_id}`, `{user_name}`, `{subject}`, `{ticket_content}`, and many more for personalized communications 152 160 153 161 = How do I set up support agents? = 154 **Basic Setup:**155 1. Go to Users > Add New foragent accounts156 2. Assign the "NexlifyDesk Agent" role 157 158 **Advanced Setup:**159 1. Visit NexlifyDesk > Agent Positions forcustom roles160 2. Set capabilities (e.g., "Level 1 Support")161 3. Assign agents via their profiles162 4. Configure auto -assignmentin Settings162 **Basic Agent Setup:** 163 1. Go to Users > Add New to create agent accounts 164 2. Assign the "NexlifyDesk Agent" role to new users 165 166 **Advanced Agent Configuration:** 167 1. Visit NexlifyDesk > Agent Positions to create custom roles 168 2. Define specific capabilities (e.g., "Level 1 Support", "Senior Agent") 169 3. Assign agents to positions through their user profiles 170 4. Configure automatic assignment rules in Settings 163 171 164 172 = What file types can be attached to tickets? = 165 Default : JPG, PNG, PDF, and common docs. Customizein NexlifyDesk > Settings:166 - Allowedextensions167 - Max file size168 - Security validation173 Default supported formats include JPG, PNG, PDF, and common document types. You can customize file handling in NexlifyDesk > Settings: 174 - Configure allowed file extensions 175 - Set maximum file size limits 176 - Enable security validation and virus scanning integration 169 177 170 178 = How does the SLA monitoring work? = 171 Set your response time in NexlifyDesk > Settings (in hours). It:172 - Track s when tickets hit or missSLA targets173 - Send s breach alerts to admins and agents174 - Shows SLA status in the dashboard175 - Include s metrics inreports179 Configure response time targets in NexlifyDesk > Settings (specified in hours). The system will: 180 - Track response times against SLA targets 181 - Send breach notification alerts to administrators and assigned agents 182 - Display SLA status indicators in the dashboard interface 183 - Include SLA performance metrics in comprehensive reports 176 184 177 185 = Can I integrate with WooCommerce? = 178 Absolutely! Built-in integration:179 - Order History page for lookups180 - Order-based duplicate detection181 - Customer context in tickets186 Absolutely! NexlifyDesk includes native WooCommerce integration features: 187 - Built-in Order History lookup page for comprehensive order details 188 - Automatic order-based duplicate detection for context preservation 189 - Direct customer context access within tickets for enhanced support quality 182 190 183 191 = How do I set up email piping? = 184 Turns emails into tickets. Setup varies:185 186 **Custom IMAP/POP3 :**187 1. Goto NexlifyDesk > Settings > Email Piping188 2. Pick "Custom IMAP/POP3"189 3. Enter server details (host, port, username, password)190 4. Set spam and filtering rules191 5. Choose to delete emails or not192 193 **AWS WorkMail :**194 1. En able SSL on yoursite195 2. Pick "AWS WorkMail"196 3. Enter region, org ID, andcredentials197 4. Optional SES setup198 199 **Google Workspace/Gmail :**200 1. Set up OAuthin Google Cloud Console201 2. Pick "Google Workspace"202 3. Authenticate via OAuth203 4. Configure processing prefs192 Email piping converts incoming emails into support tickets. Setup varies by provider: 193 194 **Custom IMAP/POP3 Configuration:** 195 1. Navigate to NexlifyDesk > Settings > Email Piping 196 2. Select "Custom IMAP/POP3" as your provider 197 3. Enter mail server details (host, port, username, password, encryption) 198 4. Configure spam filtering and processing rules 199 5. Choose email retention preferences (delete or keep emails after processing) 200 201 **AWS WorkMail Integration:** 202 1. Ensure SSL is enabled on your WordPress site 203 2. Select "AWS WorkMail" as your provider 204 3. Enter AWS region, organization ID, and authentication credentials 205 4. Optionally configure SES for enhanced email delivery 206 207 **Google Workspace/Gmail Setup:** 208 1. Create OAuth credentials in Google Cloud Console 209 2. Select "Google Workspace" as your provider 210 3. Complete OAuth authentication flow 211 4. Configure email processing preferences and filters 204 212 205 213 = How does automatic ticket assignment work? = 206 My smart system:207 1. Finds available agents208 2. Balances workloadwith fewest open tickets209 3. Falls back to admin s if needed210 4. Reassigns orphaned tickets214 The intelligent assignment system operates through multiple algorithms: 215 1. Identifies available agents based on online status and workload 216 2. Balances ticket distribution by assigning to agents with fewest open tickets 217 3. Falls back to administrator assignment if no agents are available 218 4. Automatically reassigns orphaned tickets when agents are deactivated 211 219 212 220 = What happens to closed tickets? = 213 - Agents can close manually 214 - Resolved tickets auto-close after 48 hours inactivity 215 - Admins can reopen 216 - Customers can’t reply to closed tickets—prompts new ones 217 218 = Is my data safe when uninstalling? = 219 By default, YES! Keeps all data (tickets, attachments, etc.) unless you change it in Settings to remove everything. Backup first! 221 Ticket closure follows a structured workflow: 222 - Agents can manually close tickets at any time 223 - Resolved tickets automatically close after 48 hours of customer inactivity 224 - Administrators can reopen closed tickets when necessary 225 - Customers cannot reply to closed tickets and are prompted to create new ones 226 227 = Is my data safe when uninstalling the plugin? = 228 By default, YES! All plugin data (tickets, attachments, categories, etc.) is preserved during uninstallation for data safety. You can modify this behavior in Settings to enable complete data removal. Always create a backup before uninstalling any plugin. 220 229 221 230 = How do internal notes work? = 222 Private agent chats: 223 - Only for team, not customers 224 - Great for context or escalation 225 - Add via "Add Internal Note" in admin 226 227 = Can I customize the appearance? = 228 Oh yes! 229 - CSS styling in your theme 230 - Template overrides 231 - Color and label tweaks in Settings 232 - Full email template control 231 Internal notes provide private agent-to-agent communication: 232 - Notes are completely invisible to customers 233 - Ideal for sharing context, escalation information, or troubleshooting details 234 - Add notes through the "Add Internal Note" tab in the admin ticket interface 235 - All internal notes are logged with timestamps and agent attribution 236 237 = Can I customize the plugin's appearance? = 238 Multiple customization options are available: 239 - Add custom CSS styling through your theme's stylesheet 240 - Override plugin templates by copying them to your active theme directory 241 - Customize colors, labels, and visual elements through Settings 242 - Full control over email template design and content 243 - Responsive design elements for mobile optimization 233 244 234 245 == Screenshots == 235 246 236 1. Frontend Ticket Submission Form: Clean form for customers with attachments and priority.237 2. Customer Ticket Dashboard: Easy interface for ticket history.238 3. Frontend Ticket Conversation: Threaded view with attachments.239 4. Admin Dashboard: Ticket management with stats.240 5. Admin Ticket Details: Single-ticket view with notes.241 6. Category Management: Organize with custom categories.242 7. Comprehensive Settings: Tons of config options.243 8. Agent Positions & Permissions: Granular role control.244 9. Email Template Editor: Customize notifications.245 10. Reports & Analytics: Charts and metrics galore.247 1. **Frontend Ticket Submission Form** - Clean, user-friendly form interface with file attachments and priority selection 248 2. **Customer Ticket Dashboard** - Intuitive interface for customers to view and manage their ticket history 249 3. **Frontend Ticket Conversation** - Threaded conversation view with file attachments and status updates 250 4. **Admin Dashboard Overview** - Comprehensive ticket management interface with real-time statistics 251 5. **Admin Ticket Details** - Single-ticket management view with internal notes and agent actions 252 6. **Category Management** - Organize and manage custom ticket categories with descriptions 253 7. **Comprehensive Settings Panel** - Extensive configuration options for all plugin features 254 8. **Agent Positions & Permissions** - Granular role control and capability management system 255 9. **Email Template Editor** - Customize all notification emails with dynamic placeholders 256 10. **Reports & Analytics Dashboard** - Visual charts and comprehensive metrics for performance analysis 246 257 247 258 == Usage == 248 259 249 === Shortcode s===250 251 **`[nexlifydesk_ticket_form]`** - Shows the ticketform252 - `show_title="no"` - Hide stitle253 - `category="5"` - Pre-select category254 - `priority="high"` - Set default priority 255 256 **`[nexlifydesk_ticket_list]`** - Shows ticket history257 - `show_title="no"` - Hide stitle258 - `status="open"` - Filter by status259 - `limit="10"` - Limit t ickets260 261 === Admin Menu Structure ===260 === Shortcode Reference === 261 262 **`[nexlifydesk_ticket_form]`** - Display the ticket submission form 263 - `show_title="no"` - Hide the page title 264 - `category="5"` - Pre-select a specific category by ID 265 - `priority="high"` - Set default priority level (low, medium, high, urgent) 266 267 **`[nexlifydesk_ticket_list]`** - Display ticket history and management 268 - `show_title="no"` - Hide the page title 269 - `status="open"` - Filter tickets by status (open, in-progress, pending, resolved, closed) 270 - `limit="10"` - Limit the number of tickets displayed per page 271 272 === Administrative Menu Structure === 262 273 263 274 **NexlifyDesk** (Main Menu) 264 - All Tickets: Full management with filters265 - Categories: Custom ticket categories266 - Settings: All config options267 - Reports: Analytics and charts268 - Agent Positions: Role and capability setup269 - Order History: WooCommerce lookups270 - Email Templates: Notification tweaks271 - Support: Get help from me!272 273 === Ticket Status es===274 275 Five statuses for smooth workflows:276 - Open: New tickets277 - In Progress: Being worked on278 - Pending: Waiting on customer279 - Resolved: Solved, awaitingconfirmation280 - Closed: Done (auto or manual)281 282 === Agent Capabilit ies===283 284 Custom roles with:285 - View All Tickets: Org-wide access286 - Assign Tickets: Delegate to others287 - Manage Categories: Organize tickets288 - View Reports: See analytics275 - **All Tickets** - Comprehensive ticket management with advanced filtering and bulk operations 276 - **Categories** - Create and manage custom ticket categories and hierarchies 277 - **Settings** - Complete configuration panel for all plugin features and integrations 278 - **Reports** - Analytics dashboard with charts, metrics, and performance insights 279 - **Agent Positions** - Role and capability management for team members 280 - **Order History** - WooCommerce order lookup and integration features 281 - **Email Templates** - Customize all notification templates with dynamic content 282 - **Support** - Direct access to plugin support and documentation resources 283 284 === Ticket Status Workflow === 285 286 Five distinct statuses provide complete ticket lifecycle management: 287 - **Open** - Newly submitted tickets awaiting initial agent response 288 - **In Progress** - Tickets actively being worked on by assigned agents 289 - **Pending** - Tickets waiting for customer response or additional information 290 - **Resolved** - Tickets marked as solved, awaiting customer confirmation 291 - **Closed** - Completed tickets (closed manually or automatically after 48 hours) 292 293 === Agent Capability System === 294 295 Custom agent roles support granular permission control: 296 - **View All Tickets** - Organization-wide ticket access across all agents and departments 297 - **Assign Tickets** - Ability to delegate tickets to other agents or departments 298 - **Manage Categories** - Create, edit, and organize ticket categories and hierarchies 299 - **View Reports** - Access to analytics, performance metrics, and statistical dashboards 289 300 290 301 == Customization == 291 302 292 Lots of ways to make it yours!293 294 303 **Settings Panel Configuration** 295 - Email Notifications: Control timing and delivery296 - Default Values: Set priority, category, etc.297 - File Upload Controls: Manage types and sizes298 - SLA Management: Set response targets299 - Automation Rules: Auto-assign, duplicates, closures304 - **Email Notifications** - Control delivery timing, recipients, and notification triggers 305 - **Default Values** - Configure default priority levels, categories, and agent assignments 306 - **File Upload Controls** - Manage allowed file types, size limits, and security validation 307 - **SLA Management** - Set response time targets, breach notifications, and escalation rules 308 - **Automation Rules** - Configure auto-assignment, duplicate detection, and closure policies 300 309 301 310 **Email Template Customization** 302 - Dynamic placeholders (e.g., `{ticket_id}`)303 - HTML support with preview304 - Separate templates per notification305 - Multi-language ready311 - **Dynamic Placeholders** - Use variables like `{ticket_id}`, `{user_name}`, `{subject}`, `{ticket_content}` for personalized communications 312 - **HTML Support** - Rich text formatting with live preview functionality 313 - **Multiple Templates** - Separate customization for each notification type and event 314 - **Multi-language Ready** - Support for internationalization and localization 306 315 307 316 **Visual Customization** 308 - CSS override in your theme 309 - Template system for structural changes 310 - Responsive design tweaks 317 - **CSS Override** - Add custom styles through your theme's stylesheet 318 - **Template System** - Override plugin templates for complete structural control 319 - **Responsive Design** - Mobile-first approach with tablet and desktop optimization 320 - **Color Schemes** - Customize visual elements to match your brand identity 311 321 312 322 **Advanced Integration** 313 - WooCommerce support314 - Multisite ready315 - Developer hooks316 - REST API future-proof323 - **WooCommerce Support** - Deep integration with order management and customer history 324 - **Multisite Compatibility** - Full support for WordPress multisite networks 325 - **Developer Hooks** - Extensive action and filter hooks for custom functionality 326 - **REST API Ready** - Prepared for future API integrations and third-party connections 317 327 318 328 == Performance & Security == 319 329 320 330 **Optimized Performance** 321 - Smart caching322 - AJAX interface323 - Optimized database324 - Background email processing331 - **Smart Caching** - Intelligent cache management for database queries and duplicate detection 332 - **AJAX Interface** - Seamless user experience with real-time updates and no page reloads 333 - **Optimized Database** - Efficient query structure and indexing for large ticket volumes 334 - **Background Processing** - Email handling and notifications processed asynchronously 325 335 326 336 **Security Features** 327 - Nonce verification328 - Data sanitization329 - File upload security330 - Rate limiting331 - Capability-based access337 - **Nonce Verification** - WordPress security tokens for all form submissions and AJAX requests 338 - **Data Sanitization** - Comprehensive input validation and output escaping 339 - **File Upload Security** - MIME type validation, file extension verification, and size limits 340 - **Rate Limiting** - Built-in protection against spam, abuse, and automated attacks 341 - **Capability-based Access** - Role-based permissions with granular access control 332 342 333 343 **Data Management** 334 - Configurable retention335 - Optional purging336 - Database tools337 - Full export options344 - **Configurable Retention** - Flexible data retention policies for compliance requirements 345 - **Optional Purging** - Safe data removal options with confirmation safeguards 346 - **Database Tools** - Maintenance utilities for optimization and cleanup 347 - **Full Export Options** - Complete data portability for migrations and backups 338 348 339 349 == Changelog == 340 350 351 = 1.0.3 = 352 - Fixed AWS Test Connection functionality for WorkMail and SES integration 353 - Added comprehensive AWS System Diagnostic tools for enhanced troubleshooting capabilities 354 - Improved frontend ticket page UI with mobile-friendly design and cleaner interface elements 355 - Enhanced duplicate detection with advanced semantic text analysis using cosine similarity algorithms 356 - Implemented intelligent keyword mapping and stopword filtering for more accurate duplicate ticket identification 357 - Optimized TextAnalysis library for improved performance and security compliance 358 - Added comprehensive caching system for duplicate detection queries to improve response times 359 - Minor improvements in core ticketing functionality and email piping reliability 360 341 361 = 1.0.2 = 342 - Fixed email delivery with SMTP plugins (RFC 5322 compliance). Update now if you use SMTP! 362 - Fixed critical email delivery issues with SMTP plugins (RFC 5322 compliance) 363 - Improved compatibility with popular SMTP plugins and mail delivery services 364 - Enhanced email header formatting for better deliverability and spam prevention 343 365 344 366 = 1.0.1 = 345 - Added email piping (IMAP/POP3, AWS, Google), better duplicate detection, real-time ticket list, and spam protection. Big update! 367 - Added comprehensive email piping support (IMAP/POP3, AWS WorkMail, Google Workspace) 368 - Enhanced duplicate detection algorithms with improved accuracy and performance 369 - Implemented real-time ticket list updates with live status indicators 370 - Added advanced spam protection and rate limiting for email processing 371 - Improved admin interface with better filtering and search capabilities 346 372 347 373 = 1.0.0 = 348 - Initial release with full ticketing, agent management, internal notes, WooCommerce, SLA, duplicate detection, emails, roles, categories, files, and shortcodes! 374 - Initial release with complete ticketing system functionality 375 - Full agent management with roles and permissions 376 - Internal notes system for private agent communication 377 - Native WooCommerce integration with order lookup 378 - SLA monitoring and breach notifications 379 - Advanced duplicate detection algorithms 380 - Customizable email templates and notifications 381 - Granular user roles and capability management 382 - Custom categories and priority levels 383 - File attachment support with security validation 384 - Comprehensive shortcode system for frontend integration 349 385 350 386 == Upgrade Notice == 351 387 388 = 1.0.3 = 389 Major update: Fixes AWS connectivity issues, adds comprehensive system diagnostics, improves mobile UI responsiveness, and introduces advanced Powerful duplicate detection with semantic text analysis for significantly more accurate and intelligent ticket management. 390 352 391 = 1.0.2 = 353 Critical fix for email issues with SMTP plugins. Update ASAP if you’re using one!392 Critical fix for email delivery issues when using SMTP plugins. Update immediately if you're using any SMTP delivery service to ensure proper email functionality. 354 393 355 394 = 1.0.1 = 356 Major update with email piping and enhancements. Highly recommended!395 Major feature update including email piping, enhanced duplicate detection, real-time interfaces, and spam protection. Highly recommended for all users to improve support workflow efficiency. 357 396 358 397 = 1.0.0 = 359 First release—no upgrade needed. 398 Initial release - no upgrade necessary. Welcome to NexlifyDesk! 360 399 361 400 == Support & Documentation == 362 401 363 **Getting Help **364 - Email: [email protected]365 - Website: https://nexlifylabs.com366 - Docs: Full guides on thesite367 - Community: WordPress.org forums368 369 **Premium Support **370 Go Pro for priority help and extra features! 371 372 **Feature Requests **373 I love feedback! Send ideas via support channels.402 **Getting Help and Support** 403 - **Email Support**: [email protected] for technical assistance and general inquiries 404 - **Official Website**: https://nexlifylabs.com for documentation, tutorials, and updates 405 - **Comprehensive Documentation**: Complete setup guides and feature documentation available on our website 406 - **Community Support**: WordPress.org plugin forums for community assistance and discussions 407 408 **Premium Support Services** 409 Upgrade to premium support for priority assistance, advanced features, and dedicated technical support with faster response times. 410 411 **Feature Requests and Feedback** 412 We value your input! Send feature suggestions and feedback through our support channels to help shape future development. 374 413 375 414 == Privacy & Data Protection == 376 415 377 **Data Storage **378 - Stored in your WordPress DB379 - No external servers380 - Full control381 382 **Email Handling **383 - Uses your mail system384 - No third-party services385 - Privacy-compliant386 387 **Data Portability **388 - Export all data389 - WordPress formats390 - GDPR-ready391 392 **Security Measures **393 - Encryption394 - Regular audits395 - Best practices396 397 See [Privacy Policy](https://nexlifylabs.com/privacy-policy) for details. 416 **Data Storage and Security** 417 - **Local Storage**: All ticket data is stored in your WordPress database with no external servers 418 - **Complete Control**: Maintain full control over your support data and customer information 419 - **No Third-party Dependencies**: Core functionality operates independently without external service requirements 420 421 **Email Handling and Privacy** 422 - **Your Mail System**: Uses your existing email infrastructure and SMTP settings 423 - **No External Services**: Email processing occurs on your server without third-party involvement 424 - **Privacy Compliant**: Designed to meet GDPR, CCPA, and other privacy regulation requirements 425 426 **Data Portability and Export** 427 - **Complete Export**: Export all ticket data, attachments, and configurations in standard formats 428 - **WordPress Compatibility**: Data exports use WordPress-standard formats for easy migration 429 - **GDPR Ready**: Built-in tools for data export, modification, and deletion to support privacy rights 430 431 **Security Measures and Compliance** 432 - **Data Encryption**: Sensitive data encryption for secure storage and transmission 433 - **Regular Security Audits**: Ongoing security reviews and updates for vulnerability protection 434 - **Best Practices**: Implementation follows WordPress security best practices and guidelines 435 436 For complete privacy policy details, visit: [Privacy Policy](https://nexlifylabs.com/privacy-policy) 398 437 399 438 == Uninstall Process == 400 439 401 **Data Retention (Default)** 402 - Keeps all data (tickets, attachments, etc.) on uninstall—your history stays safe! 403 404 **Complete Removal (Optional)** 405 1. Go to NexlifyDesk > Settings 406 2. Uncheck "Keep all tickets and data" 407 3. Save and uninstall 408 409 **Warning**: Deletes EVERYTHING—backup first! 440 **Data Retention (Default Behavior)** 441 - **Safe Preservation**: All plugin data (tickets, attachments, categories, settings) is preserved during uninstallation 442 - **Data Safety**: Your support history and customer data remain intact for future plugin reinstallation 443 - **WordPress Standard**: Follows WordPress plugin standards for data preservation 444 445 **Complete Data Removal (Optional)** 446 1. Navigate to NexlifyDesk > Settings before uninstalling 447 2. Uncheck "Preserve all tickets and plugin data during uninstallation" 448 3. Save settings and proceed with plugin uninstallation 449 4. All plugin data will be permanently removed from your database 450 451 **Important Warning**: Complete data removal is irreversible. Always create a full backup before enabling data removal or uninstalling the plugin. This ensures you can restore your support data if needed. -
nexlifydesk/trunk/templates/admin/imap-auth.php
r3330751 r3333095 278 278 <button type="button" id="test-aws-connection" class="button" <?php echo !$is_ssl_enabled ? 'disabled' : ''; ?>>Test AWS Connection</button> 279 279 <button type="button" id="test-aws-fetch-emails" class="button button-secondary" style="margin-left: 10px;" <?php echo !$is_ssl_enabled ? 'disabled' : ''; ?>>Test Email Fetch</button> 280 <button type="button" id="aws-diagnostics" class="button button-secondary" style="margin-left: 10px;">System Diagnostics</button> 280 281 <div id="aws-connection-result" style="margin-top: 10px;"></div> 281 282 <div id="aws-fetch-result" style="margin-top: 10px;"></div> 282 <p class="description"><?php esc_html_e('Test the connection to AWS WorkMail and manually fetch emails for testing.', 'nexlifydesk'); ?></p> 283 <div id="aws-diagnostics-result" style="margin-top: 10px;"></div> 284 <p class="description"><?php esc_html_e('Test the connection to AWS WorkMail, manually fetch emails for testing, or run system diagnostics.', 'nexlifydesk'); ?></p> 283 285 </td> 284 286 </tr> -
nexlifydesk/trunk/templates/admin/settings.php
r3330741 r3333095 35 35 } elseif (in_array($key, array('max_file_size', 'default_category', 'sla_response_time', 'ticket_page_id', 'ticket_id_start', 'duplicate_threshold'), true)) { 36 36 $settings[$key] = (int)$value; 37 } elseif (in_array($key, array('check_duplicates' ), true)) {37 } elseif (in_array($key, array('check_duplicates', 'keep_data_on_uninstall'), true)) { 38 38 $settings[$key] = $value ? 1 : 0; 39 39 } … … 296 296 </form> 297 297 </div> 298 299 <script type="text/javascript"> 300 jQuery(document).ready(function($) { 301 // Handle data retention warning 302 $('#keep_data_on_uninstall').on('change', function() { 303 if (!this.checked) { 304 $('#data-deletion-warning').show(); 305 } else { 306 $('#data-deletion-warning').hide(); 307 } 308 }); 309 310 // Show warning if already unchecked on page load 311 if (!$('#keep_data_on_uninstall').is(':checked')) { 312 $('#data-deletion-warning').show(); 313 } 314 }); 315 </script> -
nexlifydesk/trunk/templates/admin/ticket-single.php
r3330741 r3333095 15 15 $user_avatar_url = $user ? get_avatar_url($user->ID) : get_avatar_url(0); 16 16 17 // Extract customer details for non-registered users, but use clean message for display 18 // to avoid redundancy with the "About Customer" sidebar section 17 19 $customer_details = function_exists('nexlifydesk_extract_customer_details') ? 18 20 nexlifydesk_extract_customer_details($ticket->message) : … … 21 23 $customer_name = $user ? $user->display_name : ($customer_details['name'] ?: 'Guest'); 22 24 $customer_email = $user ? $user->user_email : ($customer_details['email'] ?: 'N/A'); 25 // Use clean message without embedded customer details for admin panel display 23 26 $display_message = $customer_details['message']; 24 27 ?> … … 88 91 <button class="tab-link active" data-tab="reply"><?php esc_html_e('Reply', 'nexlifydesk'); ?></button> 89 92 <button class="tab-link" data-tab="note"><?php esc_html_e('Add Internal Note', 'nexlifydesk'); ?></button> 93 <!--<button class="tab-link" data-tab="order"><?php esc_html_e('Order Lookup', 'nexlifydesk'); ?></button>--> 90 94 </div> 91 95 … … 150 154 151 155 if (!$reply_user) { 156 // For non-registered customers: Extract clean message without customer details 157 // to avoid redundancy with the "About Customer" sidebar section 152 158 $reply_customer_details = function_exists('nexlifydesk_extract_customer_details') ? 153 159 nexlifydesk_extract_customer_details($reply->message) : 154 160 ['name' => '', 'email' => '', 'message' => $reply->message]; 155 161 $reply_customer_name = $reply_customer_details['name'] ?: 'Guest'; 162 // Use clean message without embedded customer details for admin panel display 156 163 $reply_display_message = $reply_customer_details['message']; 157 164 } else { -
nexlifydesk/trunk/templates/admin/tickets-list.php
r3330741 r3333095 9 9 10 10 // Ticket statistics 11 11 12 $stats = array(); 12 13 if (class_exists('NexlifyDesk_Reports')) { … … 14 15 } 15 16 ?> 16 17 <div class="wrap"> 18 <h1 style="display: none;"></h1> 19 </div> 20 21 <!-- Main ticket list UI --> 17 22 <div class="wrap nexlifydesk-admin-ticket-list-ui"> 23 18 24 <div class="header"> 19 25 <h1><?php esc_html_e('Support Tickets', 'nexlifydesk'); ?></h1> -
nexlifydesk/trunk/templates/emails/new_reply.php
r3326104 r3333095 3 3 exit; 4 4 } 5 6 $user = get_userdata($ticket->user_id); 7 $customer_details = nexlifydesk_extract_customer_details($ticket->message); 8 $customer_name = $user ? $user->display_name : ($customer_details['name'] ?: 'Customer'); 9 10 // Get the reply information 11 $reply = null; 12 if ($reply_id) { 13 $replies = NexlifyDesk_Tickets::get_replies($ticket->id); 14 foreach ($replies as $r) { 15 if ($r->id == $reply_id) { 16 $reply = $r; 17 break; 18 } 19 } 20 } 21 22 $reply_customer_details = $reply ? nexlifydesk_extract_customer_details($reply->message) : array(); 23 $reply_message = $reply ? ($reply_customer_details['message'] ?: $reply->message) : ''; 24 $reply_user = $reply ? get_userdata($reply->user_id) : null; 25 $reply_user_name = $reply_user ? $reply_user->display_name : ($reply_customer_details['name'] ?: 'Guest'); 26 27 $ticket_url = add_query_arg( 28 array('ticket_id' => $ticket->ticket_id), 29 NexlifyDesk_Admin::get_ticket_page_url() 30 ); 31 ?> 32 <p>Hello <?php echo esc_html($customer_name); ?>,</p> 33 34 <p>A new reply has been added to your support ticket.</p> 35 36 <p><strong>Ticket Details:</strong></p> 37 <ul> 38 <li><strong>Ticket ID:</strong> #<?php echo esc_html($ticket->ticket_id); ?></li> 39 <li><strong>Subject:</strong> <?php echo esc_html($ticket->subject); ?></li> 40 <li><strong>Status:</strong> <?php echo esc_html(ucfirst($ticket->status)); ?></li> 41 <li><strong>Reply From:</strong> <?php echo esc_html($reply_user_name); ?></li> 42 </ul> 43 44 <?php if ($reply_message): ?> 45 <p><strong>New Reply:</strong></p> 46 <div style="background-color: #f9f9f9; padding: 15px; border-left: 4px solid #0073aa;"> 47 <?php echo wp_kses_post(wpautop($reply_message)); ?> 48 </div> 49 <?php endif; ?> 50 51 <p>You can view the full conversation and respond to this ticket by clicking the link below:</p> 52 53 <p><a href="<?php echo esc_url($ticket_url); ?>" style="background-color: #0073aa; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">View Ticket</a></p> 54 55 <p>Best regards,<br> 56 <?php echo esc_html(get_bloginfo('name')); ?> Support Team</p> -
nexlifydesk/trunk/templates/emails/new_ticket.php
r3326104 r3333095 3 3 exit; 4 4 } 5 6 $user = get_userdata($ticket->user_id); 7 $customer_details = nexlifydesk_extract_customer_details($ticket->message); 8 $customer_name = $user ? $user->display_name : ($customer_details['name'] ?: 'Customer'); 9 $customer_email = $user ? $user->user_email : ($customer_details['email'] ?: ''); 10 $clean_message = $customer_details['message'] ?: $ticket->message; 11 12 $ticket_url = add_query_arg( 13 array('ticket_id' => $ticket->ticket_id), 14 NexlifyDesk_Admin::get_ticket_page_url() 15 ); 16 ?> 17 <p>Hello <?php echo esc_html($customer_name); ?>,</p> 18 19 <p>Thank you for contacting us. Your support ticket has been created successfully.</p> 20 21 <p><strong>Ticket Details:</strong></p> 22 <ul> 23 <li><strong>Ticket ID:</strong> #<?php echo esc_html($ticket->ticket_id); ?></li> 24 <li><strong>Subject:</strong> <?php echo esc_html($ticket->subject); ?></li> 25 <li><strong>Priority:</strong> <?php echo esc_html(ucfirst($ticket->priority)); ?></li> 26 <li><strong>Status:</strong> <?php echo esc_html(ucfirst($ticket->status)); ?></li> 27 <li><strong>Created:</strong> <?php echo esc_html(gmdate(get_option('date_format') . ' ' . get_option('time_format'), strtotime($ticket->created_at))); ?></li> 28 </ul> 29 30 <p><strong>Your Message:</strong></p> 31 <div style="background-color: #f9f9f9; padding: 15px; border-left: 4px solid #ddd;"> 32 <?php echo wp_kses(wpautop(wp_kses_post($clean_message)), array( 33 'p' => array(), 34 'br' => array(), 35 'strong' => array(), 36 'em' => array(), 37 'ul' => array(), 38 'ol' => array(), 39 'li' => array(), 40 'a' => array( 41 'href' => array(), 42 'title' => array(), 43 'rel' => array(), 44 'target' => array(), 45 ), 46 )); ?> 47 </div> 48 49 <p>Our support team will review your request and get back to you as soon as possible. You can view and track your ticket status at any time by clicking the link below:</p> 50 51 <p><a href="<?php echo esc_url($ticket_url); ?>" style="background-color: #0073aa; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">View Ticket</a></p> 52 53 <p>Please keep this ticket ID (#<?php echo esc_html($ticket->ticket_id); ?>) for your records and reference it in any future correspondence.</p> 54 55 <p>Best regards,<br> 56 <?php echo esc_html(get_bloginfo('name')); ?> Support Team</p> -
nexlifydesk/trunk/templates/emails/sla_breach.php
r3326104 r3333095 3 3 exit; 4 4 } 5 6 $user = get_userdata($ticket->user_id); 7 $customer_details = nexlifydesk_extract_customer_details($ticket->message); 8 $customer_name = $user ? $user->display_name : ($customer_details['name'] ?: 'Customer'); 9 10 $ticket_admin_url = add_query_arg( 11 array( 12 'page' => 'nexlifydesk_tickets', 13 'ticket_id' => $ticket->id, 14 ), 15 admin_url('admin.php') 16 ); 17 ?> 18 <p><strong>SLA Breach Alert</strong></p> 19 20 <p>This is an urgent notification that a support ticket has breached its SLA (Service Level Agreement) response time.</p> 21 22 <p><strong>Ticket Details:</strong></p> 23 <ul> 24 <li><strong>Ticket ID:</strong> #<?php echo esc_html($ticket->ticket_id); ?></li> 25 <li><strong>Subject:</strong> <?php echo esc_html($ticket->subject); ?></li> 26 <li><strong>Customer:</strong> <?php echo esc_html($customer_name); ?></li> 27 <li><strong>Priority:</strong> <?php echo esc_html(ucfirst($ticket->priority)); ?></li> 28 <li><strong>Status:</strong> <?php echo esc_html(ucfirst($ticket->status)); ?></li> 29 <li><strong>Created:</strong> <?php echo esc_html(gmdate(get_option('date_format') . ' ' . get_option('time_format'), strtotime($ticket->created_at))); ?></li> 30 <li><strong>Last Updated:</strong> <?php echo esc_html(gmdate(get_option('date_format') . ' ' . get_option('time_format'), strtotime($ticket->updated_at))); ?></li> 31 </ul> 32 33 <p><strong>Immediate Action Required:</strong></p> 34 <p>This ticket requires immediate attention to prevent further SLA violations. Please review and respond promptly.</p> 35 36 <p><a href="<?php echo esc_url($ticket_admin_url); ?>" style="background-color: #d63638; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">View Ticket Now</a></p> 37 38 <p>This is an automated alert from the <?php echo esc_html(get_bloginfo('name')); ?> support system.</p> -
nexlifydesk/trunk/templates/emails/status_changed.php
r3326104 r3333095 3 3 exit; 4 4 } 5 6 $user = get_userdata($ticket->user_id); 7 $customer_details = nexlifydesk_extract_customer_details($ticket->message); 8 $customer_name = $user ? $user->display_name : ($customer_details['name'] ?: 'Customer'); 9 10 $ticket_url = add_query_arg( 11 array('ticket_id' => $ticket->ticket_id), 12 NexlifyDesk_Admin::get_ticket_page_url() 13 ); 14 ?> 15 <p>Hello <?php echo esc_html($customer_name); ?>,</p> 16 17 <p>The status of your support ticket has been updated.</p> 18 19 <p><strong>Ticket Details:</strong></p> 20 <ul> 21 <li><strong>Ticket ID:</strong> #<?php echo esc_html($ticket->ticket_id); ?></li> 22 <li><strong>Subject:</strong> <?php echo esc_html($ticket->subject); ?></li> 23 <li><strong>Previous Status:</strong> <?php echo esc_html(ucfirst($ticket->status)); ?></li> 24 <li><strong>Current Status:</strong> <?php echo esc_html(ucfirst($ticket->status)); ?></li> 25 <li><strong>Priority:</strong> <?php echo esc_html(ucfirst($ticket->priority)); ?></li> 26 <li><strong>Updated:</strong> <?php echo esc_html(gmdate(get_option('date_format') . ' ' . get_option('time_format'), strtotime($ticket->updated_at))); ?></li> 27 </ul> 28 29 <p>You can view your ticket and track its progress by clicking the link below:</p> 30 31 <p><a href="<?php echo esc_url($ticket_url); ?>" style="background-color: #0073aa; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">View Ticket</a></p> 32 33 <p>Thank you for your patience. Our support team is working to resolve your request.</p> 34 35 <p>Best regards,<br> 36 <?php echo esc_html(get_bloginfo('name')); ?> Support Team</p> -
nexlifydesk/trunk/templates/frontend/ticket-list.php
r3326104 r3333095 14 14 } 15 15 ?> 16 <div class="nexlifydesk-frontend-container"> 17 <div class="nexlifydesk-ticket-list-header"> 18 <div class="nexlifydesk-ticket-list-header__left"> 19 <h1 class="nexlifydesk-ticket-list-title"><?php esc_html_e('Your Support Tickets', 'nexlifydesk'); ?></h1> 20 <p class="nexlifydesk-ticket-list-desc"><?php esc_html_e('View and track your support requests', 'nexlifydesk'); ?></p> 16 <div class="nexlifydesk-table-container"> 17 <header class="nexlifydesk-table-header"> 18 <h1><?php esc_html_e('Support Tickets', 'nexlifydesk'); ?></h1> 19 <div class="nexlifydesk-header-actions"> 20 <?php if (!empty($submit_url) && $submit_url !== home_url()) : ?> 21 <a href="<?php echo esc_url($submit_url); ?>" class="nexlifydesk-btn-primary"> 22 <?php esc_html_e('New Ticket', 'nexlifydesk'); ?> 23 </a> 24 <?php endif; ?> 21 25 </div> 22 <?php if (!empty($submit_url) && $submit_url !== home_url()) : ?> 23 <a href="<?php echo esc_url($submit_url); ?>" class="btn btn-primary nexlifydesk-ticket-list-header__btn"> 24 <?php esc_html_e('Submit New Ticket', 'nexlifydesk'); ?> 25 </a> 26 <?php endif; ?> 27 </div> 26 </header> 28 27 29 28 <?php if (empty($user_tickets)) : ?> 30 <div class="nexlifydesk-no-tickets ">31 <div class="nexlifydesk-no-tickets __icon">📄</div>32 <h3 class="nexlifydesk-no-tickets__title"><?php esc_html_e('No tickets found', 'nexlifydesk'); ?></h3>33 <p class="nexlifydesk-no-tickets__desc"><?php esc_html_e('You haven\'t submitted any support tickets yet.', 'nexlifydesk'); ?></p>29 <div class="nexlifydesk-no-tickets-table"> 30 <div class="nexlifydesk-no-tickets-icon">📄</div> 31 <h3><?php esc_html_e('No tickets found', 'nexlifydesk'); ?></h3> 32 <p><?php esc_html_e('You haven\'t submitted any support tickets yet.', 'nexlifydesk'); ?></p> 34 33 <?php if (!empty($submit_url) && $submit_url !== home_url()) : ?> 35 <a href="<?php echo esc_url($submit_url); ?>" class=" btn btn-primary nexlifydesk-no-tickets__btn">34 <a href="<?php echo esc_url($submit_url); ?>" class="nexlifydesk-btn-primary"> 36 35 <?php esc_html_e('Submit Your First Ticket', 'nexlifydesk'); ?> 37 36 </a> … … 39 38 </div> 40 39 <?php else : ?> 41 <div class="nexlifydesk-ticket-list-grid"> 42 <?php foreach ($user_tickets as $ticket) : ?> 43 <div class="nexlifydesk-ticket-card"> 44 <div class="nexlifydesk-ticket-card__top"> 45 <span class="nexlifydesk-ticket-card__id">#<?php echo esc_html($ticket->ticket_id); ?></span> 46 <span class="nexlifydesk-ticket-card__status status-<?php echo esc_attr($ticket->status); ?>"> 47 <?php echo esc_html(ucfirst($ticket->status)); ?> 48 </span> 49 </div> 50 <div class="nexlifydesk-ticket-card__title"> 51 <a href="<?php echo esc_url(add_query_arg('ticket_id', $ticket->ticket_id, get_permalink())); ?>"> 40 <table class="nexlifydesk-ticket-table"> 41 <thead> 42 <tr> 43 <th><?php esc_html_e('ID', 'nexlifydesk'); ?></th> 44 <th><?php esc_html_e('Subject', 'nexlifydesk'); ?></th> 45 <th><?php esc_html_e('Status', 'nexlifydesk'); ?></th> 46 <th><?php esc_html_e('Priority', 'nexlifydesk'); ?></th> 47 <th><?php esc_html_e('Date', 'nexlifydesk'); ?></th> 48 <th><?php esc_html_e('Actions', 'nexlifydesk'); ?></th> 49 </tr> 50 </thead> 51 <tbody> 52 <?php foreach ($user_tickets as $ticket) : ?> 53 <tr> 54 <td data-label="<?php esc_attr_e('ID', 'nexlifydesk'); ?>">#<?php echo esc_html($ticket->ticket_id); ?></td> 55 <td data-label="<?php esc_attr_e('Subject', 'nexlifydesk'); ?>" class="nexlifydesk-subject-cell"> 52 56 <?php echo esc_html($ticket->subject); ?> 53 </a> 54 </div> 55 <div class="nexlifydesk-ticket-card__meta"> 56 <span class="nexlifydesk-ticket-card__priority priority-<?php echo esc_attr($ticket->priority); ?>"> 57 <?php echo esc_html(ucfirst($ticket->priority)); ?> 58 </span> 59 <span class="nexlifydesk-ticket-card__created"> 57 <?php if (!empty($ticket->message)) : ?> 58 <div class="nexlifydesk-ticket-preview"> 59 <?php echo esc_html(wp_trim_words($ticket->message, 15, '...')); ?> 60 </div> 61 <?php endif; ?> 62 </td> 63 <td data-label="<?php esc_attr_e('Status', 'nexlifydesk'); ?>"> 64 <span class="nexlifydesk-status <?php echo esc_attr(str_replace('_', '-', $ticket->status)); ?>"> 65 <?php echo esc_html(ucfirst(str_replace('_', ' ', $ticket->status))); ?> 66 </span> 67 </td> 68 <td data-label="<?php esc_attr_e('Priority', 'nexlifydesk'); ?>"> 69 <span class="nexlifydesk-priority <?php echo esc_attr($ticket->priority); ?>"> 70 <?php echo esc_html(ucfirst($ticket->priority)); ?> 71 </span> 72 </td> 73 <td data-label="<?php esc_attr_e('Date', 'nexlifydesk'); ?>"> 60 74 <?php echo esc_html(date_i18n(get_option('date_format'), strtotime($ticket->created_at))); ?> 61 </span> 62 </div> 63 <div class="nexlifydesk-ticket-card__actions"> 64 <a href="<?php echo esc_url(add_query_arg('ticket_id', $ticket->ticket_id, get_permalink())); ?>" class="btn btn-secondary"> 65 <?php esc_html_e('View Ticket', 'nexlifydesk'); ?> 66 </a> 67 </div> 68 </div> 69 <?php endforeach; ?> 70 </div> 75 </td> 76 <td data-label="<?php esc_attr_e('Actions', 'nexlifydesk'); ?>"> 77 <a href="<?php echo esc_url(add_query_arg('ticket_id', $ticket->ticket_id, get_permalink())); ?>" class="nexlifydesk-view-btn"> 78 <?php esc_html_e('View', 'nexlifydesk'); ?> 79 </a> 80 </td> 81 </tr> 82 <?php endforeach; ?> 83 </tbody> 84 </table> 85 86 <footer class="nexlifydesk-table-footer"> 87 <p> 88 <?php 89 printf( 90 /* translators: %s: Number of tickets */ 91 esc_html__('Showing %s tickets', 'nexlifydesk'), 92 esc_html(count($user_tickets)) 93 ); 94 ?> 95 </p> 96 </footer> 71 97 <?php endif; ?> 72 98 </div> -
nexlifydesk/trunk/templates/frontend/ticket-single.php
r3330741 r3333095 44 44 45 45 $current_user = wp_get_current_user(); 46 $is_agent = in_array('nexlifydesk_agent', $current_user->roles) || current_user_can('manage_options'); 46 // SECURITY FIX: Frontend access should ONLY be for ticket owners 47 // Agents and administrators should ONLY access tickets from admin panel 47 48 $is_ticket_owner = ($ticket && (int)$ticket->user_id === (int)$current_user->ID); 48 $can_view_ticket = $is_agent || $is_ticket_owner; 49 50 // Handle unregistered user tickets (user_id = 0) by checking customer_email 51 if (!$is_ticket_owner && $ticket && (int)$ticket->user_id === 0) { 52 $customer_email = get_post_meta($ticket->id, 'customer_email', true); 53 $is_ticket_owner = ($customer_email && $customer_email === $current_user->user_email); 54 } 55 56 $can_view_ticket = $is_ticket_owner; 49 57 50 58 if (!$ticket || !$can_view_ticket) { … … 67 75 $initial_attachments = NexlifyDesk_Tickets::get_attachments($ticket->id); 68 76 69 // Extract customer details from ticket content for non-registered users70 77 $customer_details = nexlifydesk_extract_customer_details($ticket->message); 71 78 $customer_name = $user ? $user->display_name : ($customer_details['name'] ?: 'Guest');
Note: See TracChangeset
for help on using the changeset viewer.