Changeset 3333214
- Timestamp:
- 07/24/2025 01:01:52 AM (7 months ago)
- Location:
- nexlifydesk
- Files:
-
- 346 added
- 12 edited
-
tags/1.0.4 (added)
-
tags/1.0.4/assets (added)
-
tags/1.0.4/assets/css (added)
-
tags/1.0.4/assets/css/nexlifydesk-admin.css (added)
-
tags/1.0.4/assets/css/nexlifydesk.css (added)
-
tags/1.0.4/assets/images (added)
-
tags/1.0.4/assets/images/dashboard-icon.png (added)
-
tags/1.0.4/assets/images/file-types (added)
-
tags/1.0.4/assets/images/file-types/document.png (added)
-
tags/1.0.4/assets/images/file-types/image.png (added)
-
tags/1.0.4/assets/images/file-types/pdf.png (added)
-
tags/1.0.4/assets/images/nexlifydesk-logo-small.png (added)
-
tags/1.0.4/assets/images/nexlifydesk-logo.png (added)
-
tags/1.0.4/assets/images/priority (added)
-
tags/1.0.4/assets/images/priority/high.png (added)
-
tags/1.0.4/assets/images/priority/low.png (added)
-
tags/1.0.4/assets/images/priority/medium.png (added)
-
tags/1.0.4/assets/images/status (added)
-
tags/1.0.4/assets/images/status/closed.png (added)
-
tags/1.0.4/assets/images/status/open.png (added)
-
tags/1.0.4/assets/images/status/pending.png (added)
-
tags/1.0.4/assets/images/status/resolved.png (added)
-
tags/1.0.4/assets/images/support-icon.png (added)
-
tags/1.0.4/assets/images/ticket-icon.png (added)
-
tags/1.0.4/assets/js (added)
-
tags/1.0.4/assets/js/admin-ticket-list.js (added)
-
tags/1.0.4/assets/js/nexlifydesk.js (added)
-
tags/1.0.4/email-source (added)
-
tags/1.0.4/email-source/nexlifydesk-email-pipe.php (added)
-
tags/1.0.4/email-source/providers (added)
-
tags/1.0.4/email-source/providers/aws-ses (added)
-
tags/1.0.4/email-source/providers/aws-ses/aws-handler.php (added)
-
tags/1.0.4/email-source/providers/google (added)
-
tags/1.0.4/email-source/providers/google/google-handler.php (added)
-
tags/1.0.4/email-source/providers/outlook (added)
-
tags/1.0.4/includes (added)
-
tags/1.0.4/includes/class-nexlifydesk-admin.php (added)
-
tags/1.0.4/includes/class-nexlifydesk-ajax.php (added)
-
tags/1.0.4/includes/class-nexlifydesk-database.php (added)
-
tags/1.0.4/includes/class-nexlifydesk-rate-limiter.php (added)
-
tags/1.0.4/includes/class-nexlifydesk-reports.php (added)
-
tags/1.0.4/includes/class-nexlifydesk-shortcodes.php (added)
-
tags/1.0.4/includes/class-nexlifydesk-tickets.php (added)
-
tags/1.0.4/includes/class-nexlifydesk-users.php (added)
-
tags/1.0.4/includes/class-support.php (added)
-
tags/1.0.4/includes/helpers.php (added)
-
tags/1.0.4/includes/nexlifydesk-functions.php (added)
-
tags/1.0.4/includes/textanalysis (added)
-
tags/1.0.4/includes/textanalysis/Comparisons (added)
-
tags/1.0.4/includes/textanalysis/Comparisons/CosineSimilarityComparison.php (added)
-
tags/1.0.4/includes/textanalysis/Console (added)
-
tags/1.0.4/includes/textanalysis/Console/Commands (added)
-
tags/1.0.4/includes/textanalysis/Console/Commands/NltkPackageInstallCommand.php (added)
-
tags/1.0.4/includes/textanalysis/Corpus (added)
-
tags/1.0.4/includes/textanalysis/Corpus/WordnetCorpus.php (added)
-
tags/1.0.4/includes/textanalysis/Documents (added)
-
tags/1.0.4/includes/textanalysis/Documents/DocumentAbstract.php (added)
-
tags/1.0.4/includes/textanalysis/Documents/TokensDocument.php (added)
-
tags/1.0.4/includes/textanalysis/Interfaces (added)
-
tags/1.0.4/includes/textanalysis/Interfaces/IDistance.php (added)
-
tags/1.0.4/includes/textanalysis/Interfaces/IExtractStrategy.php (added)
-
tags/1.0.4/includes/textanalysis/Interfaces/ISimilarity.php (added)
-
tags/1.0.4/includes/textanalysis/Interfaces/IStemmer.php (added)
-
tags/1.0.4/includes/textanalysis/Interfaces/ITokenTransformation.php (added)
-
tags/1.0.4/includes/textanalysis/SECURITY.md (added)
-
tags/1.0.4/includes/textanalysis/Tokenizers (added)
-
tags/1.0.4/includes/textanalysis/Tokenizers/GeneralTokenizer.php (added)
-
tags/1.0.4/includes/textanalysis/Tokenizers/TokenizerAbstract.php (added)
-
tags/1.0.4/includes/textanalysis/Tokenizers/WhitespaceTokenizer.php (added)
-
tags/1.0.4/languages (added)
-
tags/1.0.4/languages/nexlifydesk-de_DE_formal.mo (added)
-
tags/1.0.4/languages/nexlifydesk-de_DE_formal.po (added)
-
tags/1.0.4/languages/nexlifydesk-es_ES.mo (added)
-
tags/1.0.4/languages/nexlifydesk-es_ES.po (added)
-
tags/1.0.4/languages/nexlifydesk-fr_FR.mo (added)
-
tags/1.0.4/languages/nexlifydesk-fr_FR.po (added)
-
tags/1.0.4/languages/nexlifydesk-it_IT.mo (added)
-
tags/1.0.4/languages/nexlifydesk-it_IT.po (added)
-
tags/1.0.4/languages/nexlifydesk-ja.mo (added)
-
tags/1.0.4/languages/nexlifydesk-ja.po (added)
-
tags/1.0.4/languages/nexlifydesk-pt_BR.mo (added)
-
tags/1.0.4/languages/nexlifydesk-pt_BR.po (added)
-
tags/1.0.4/languages/nexlifydesk-pt_PT.mo (added)
-
tags/1.0.4/languages/nexlifydesk-pt_PT.po (added)
-
tags/1.0.4/languages/nexlifydesk-ru_RU.mo (added)
-
tags/1.0.4/languages/nexlifydesk-ru_RU.po (added)
-
tags/1.0.4/languages/nexlifydesk-zh_CN.mo (added)
-
tags/1.0.4/languages/nexlifydesk-zh_CN.po (added)
-
tags/1.0.4/languages/nexlifydesk.pot (added)
-
tags/1.0.4/license.txt (added)
-
tags/1.0.4/nexlifydesk.php (added)
-
tags/1.0.4/readme.txt (added)
-
tags/1.0.4/templates (added)
-
tags/1.0.4/templates/admin (added)
-
tags/1.0.4/templates/admin/imap-auth.php (added)
-
tags/1.0.4/templates/admin/partials (added)
-
tags/1.0.4/templates/admin/partials/single-reply.php (added)
-
tags/1.0.4/templates/admin/reports.php (added)
-
tags/1.0.4/templates/admin/settings.php (added)
-
tags/1.0.4/templates/admin/ticket-single.php (added)
-
tags/1.0.4/templates/admin/tickets-list.php (added)
-
tags/1.0.4/templates/emails (added)
-
tags/1.0.4/templates/emails/new_reply.php (added)
-
tags/1.0.4/templates/emails/new_ticket.php (added)
-
tags/1.0.4/templates/emails/sla_breach.php (added)
-
tags/1.0.4/templates/emails/status_changed.php (added)
-
tags/1.0.4/templates/frontend (added)
-
tags/1.0.4/templates/frontend/partials (added)
-
tags/1.0.4/templates/frontend/partials/single-reply.php (added)
-
tags/1.0.4/templates/frontend/ticket-form.php (added)
-
tags/1.0.4/templates/frontend/ticket-list.php (added)
-
tags/1.0.4/templates/frontend/ticket-single.php (added)
-
tags/1.0.4/uninstall.php (added)
-
tags/1.0.4/vendor (added)
-
tags/1.0.4/vendor/freemius (added)
-
tags/1.0.4/vendor/freemius/LICENSE.txt (added)
-
tags/1.0.4/vendor/freemius/README.md (added)
-
tags/1.0.4/vendor/freemius/assets (added)
-
tags/1.0.4/vendor/freemius/assets/css (added)
-
tags/1.0.4/vendor/freemius/assets/css/admin (added)
-
tags/1.0.4/vendor/freemius/assets/css/admin/account.css (added)
-
tags/1.0.4/vendor/freemius/assets/css/admin/add-ons.css (added)
-
tags/1.0.4/vendor/freemius/assets/css/admin/affiliation.css (added)
-
tags/1.0.4/vendor/freemius/assets/css/admin/checkout.css (added)
-
tags/1.0.4/vendor/freemius/assets/css/admin/clone-resolution.css (added)
-
tags/1.0.4/vendor/freemius/assets/css/admin/common.css (added)
-
tags/1.0.4/vendor/freemius/assets/css/admin/connect.css (added)
-
tags/1.0.4/vendor/freemius/assets/css/admin/debug.css (added)
-
tags/1.0.4/vendor/freemius/assets/css/admin/dialog-boxes.css (added)
-
tags/1.0.4/vendor/freemius/assets/css/admin/gdpr-optin-notice.css (added)
-
tags/1.0.4/vendor/freemius/assets/css/admin/index.php (added)
-
tags/1.0.4/vendor/freemius/assets/css/admin/optout.css (added)
-
tags/1.0.4/vendor/freemius/assets/css/admin/plugins.css (added)
-
tags/1.0.4/vendor/freemius/assets/css/customizer.css (added)
-
tags/1.0.4/vendor/freemius/assets/css/index.php (added)
-
tags/1.0.4/vendor/freemius/assets/img (added)
-
tags/1.0.4/vendor/freemius/assets/img/index.php (added)
-
tags/1.0.4/vendor/freemius/assets/img/nexlifydesk.png (added)
-
tags/1.0.4/vendor/freemius/assets/img/plugin-icon.png (added)
-
tags/1.0.4/vendor/freemius/assets/img/theme-icon.png (added)
-
tags/1.0.4/vendor/freemius/assets/index.php (added)
-
tags/1.0.4/vendor/freemius/assets/js (added)
-
tags/1.0.4/vendor/freemius/assets/js/index.php (added)
-
tags/1.0.4/vendor/freemius/assets/js/jquery.form.js (added)
-
tags/1.0.4/vendor/freemius/assets/js/nojquery.ba-postmessage.js (added)
-
tags/1.0.4/vendor/freemius/assets/js/postmessage.js (added)
-
tags/1.0.4/vendor/freemius/assets/js/pricing (added)
-
tags/1.0.4/vendor/freemius/assets/js/pricing/14fb1bd5b7c41648488b06147f50a0dc.svg (added)
-
tags/1.0.4/vendor/freemius/assets/js/pricing/178afa6030e76635dbe835e111d2c507.png (added)
-
tags/1.0.4/vendor/freemius/assets/js/pricing/27b5a722a5553d9de0170325267fccec.png (added)
-
tags/1.0.4/vendor/freemius/assets/js/pricing/4375c4a3ddc6f637c2ab9a2d7220f91e.png (added)
-
tags/1.0.4/vendor/freemius/assets/js/pricing/4529cac82a2d1f300d3c4702b7b5e8f3.svg (added)
-
tags/1.0.4/vendor/freemius/assets/js/pricing/5480ed23b199531a8cbc05924f26952b.png (added)
-
tags/1.0.4/vendor/freemius/assets/js/pricing/b4f3b958f4a019862d81b15f3f8eee3a.svg (added)
-
tags/1.0.4/vendor/freemius/assets/js/pricing/c03f665db27af43971565560adfba594.png (added)
-
tags/1.0.4/vendor/freemius/assets/js/pricing/cb5fc4f6ec7ada72e986f6e7dde365bf.png (added)
-
tags/1.0.4/vendor/freemius/assets/js/pricing/dd89563360f0272635c8f0ab7d7f1402.png (added)
-
tags/1.0.4/vendor/freemius/assets/js/pricing/e366d70661d8ad2493bd6afbd779f125.png (added)
-
tags/1.0.4/vendor/freemius/assets/js/pricing/f18006f6535a1a6e9c6bfbffafe6f18a.svg (added)
-
tags/1.0.4/vendor/freemius/assets/js/pricing/f3aac72a8e63997d6bb888f816457e9b.png (added)
-
tags/1.0.4/vendor/freemius/assets/js/pricing/f928f1be99776af83e8e6be4baf8ffe7.svg (added)
-
tags/1.0.4/vendor/freemius/assets/js/pricing/fde48e4609a6ddc11d639fc2421f2afd.png (added)
-
tags/1.0.4/vendor/freemius/assets/js/pricing/freemius-pricing.js (added)
-
tags/1.0.4/vendor/freemius/assets/js/pricing/freemius-pricing.js.LICENSE.txt (added)
-
tags/1.0.4/vendor/freemius/composer.json (added)
-
tags/1.0.4/vendor/freemius/config.php (added)
-
tags/1.0.4/vendor/freemius/includes (added)
-
tags/1.0.4/vendor/freemius/includes/class-freemius-abstract.php (added)
-
tags/1.0.4/vendor/freemius/includes/class-freemius.php (added)
-
tags/1.0.4/vendor/freemius/includes/class-fs-admin-notices.php (added)
-
tags/1.0.4/vendor/freemius/includes/class-fs-api.php (added)
-
tags/1.0.4/vendor/freemius/includes/class-fs-garbage-collector.php (added)
-
tags/1.0.4/vendor/freemius/includes/class-fs-lock.php (added)
-
tags/1.0.4/vendor/freemius/includes/class-fs-logger.php (added)
-
tags/1.0.4/vendor/freemius/includes/class-fs-options.php (added)
-
tags/1.0.4/vendor/freemius/includes/class-fs-plugin-updater.php (added)
-
tags/1.0.4/vendor/freemius/includes/class-fs-security.php (added)
-
tags/1.0.4/vendor/freemius/includes/class-fs-storage.php (added)
-
tags/1.0.4/vendor/freemius/includes/class-fs-user-lock.php (added)
-
tags/1.0.4/vendor/freemius/includes/customizer (added)
-
tags/1.0.4/vendor/freemius/includes/customizer/class-fs-customizer-support-section.php (added)
-
tags/1.0.4/vendor/freemius/includes/customizer/class-fs-customizer-upsell-control.php (added)
-
tags/1.0.4/vendor/freemius/includes/customizer/index.php (added)
-
tags/1.0.4/vendor/freemius/includes/debug (added)
-
tags/1.0.4/vendor/freemius/includes/debug/class-fs-debug-bar-panel.php (added)
-
tags/1.0.4/vendor/freemius/includes/debug/debug-bar-start.php (added)
-
tags/1.0.4/vendor/freemius/includes/debug/index.php (added)
-
tags/1.0.4/vendor/freemius/includes/entities (added)
-
tags/1.0.4/vendor/freemius/includes/entities/class-fs-affiliate-terms.php (added)
-
tags/1.0.4/vendor/freemius/includes/entities/class-fs-affiliate.php (added)
-
tags/1.0.4/vendor/freemius/includes/entities/class-fs-billing.php (added)
-
tags/1.0.4/vendor/freemius/includes/entities/class-fs-entity.php (added)
-
tags/1.0.4/vendor/freemius/includes/entities/class-fs-payment.php (added)
-
tags/1.0.4/vendor/freemius/includes/entities/class-fs-plugin-info.php (added)
-
tags/1.0.4/vendor/freemius/includes/entities/class-fs-plugin-license.php (added)
-
tags/1.0.4/vendor/freemius/includes/entities/class-fs-plugin-plan.php (added)
-
tags/1.0.4/vendor/freemius/includes/entities/class-fs-plugin-tag.php (added)
-
tags/1.0.4/vendor/freemius/includes/entities/class-fs-plugin.php (added)
-
tags/1.0.4/vendor/freemius/includes/entities/class-fs-pricing.php (added)
-
tags/1.0.4/vendor/freemius/includes/entities/class-fs-scope-entity.php (added)
-
tags/1.0.4/vendor/freemius/includes/entities/class-fs-site.php (added)
-
tags/1.0.4/vendor/freemius/includes/entities/class-fs-subscription.php (added)
-
tags/1.0.4/vendor/freemius/includes/entities/class-fs-user.php (added)
-
tags/1.0.4/vendor/freemius/includes/entities/index.php (added)
-
tags/1.0.4/vendor/freemius/includes/fs-core-functions.php (added)
-
tags/1.0.4/vendor/freemius/includes/fs-essential-functions.php (added)
-
tags/1.0.4/vendor/freemius/includes/fs-html-escaping-functions.php (added)
-
tags/1.0.4/vendor/freemius/includes/fs-plugin-info-dialog.php (added)
-
tags/1.0.4/vendor/freemius/includes/index.php (added)
-
tags/1.0.4/vendor/freemius/includes/l10n.php (added)
-
tags/1.0.4/vendor/freemius/includes/managers (added)
-
tags/1.0.4/vendor/freemius/includes/managers/class-fs-admin-menu-manager.php (added)
-
tags/1.0.4/vendor/freemius/includes/managers/class-fs-admin-notice-manager.php (added)
-
tags/1.0.4/vendor/freemius/includes/managers/class-fs-cache-manager.php (added)
-
tags/1.0.4/vendor/freemius/includes/managers/class-fs-checkout-manager.php (added)
-
tags/1.0.4/vendor/freemius/includes/managers/class-fs-clone-manager.php (added)
-
tags/1.0.4/vendor/freemius/includes/managers/class-fs-contact-form-manager.php (added)
-
tags/1.0.4/vendor/freemius/includes/managers/class-fs-debug-manager.php (added)
-
tags/1.0.4/vendor/freemius/includes/managers/class-fs-gdpr-manager.php (added)
-
tags/1.0.4/vendor/freemius/includes/managers/class-fs-key-value-storage.php (added)
-
tags/1.0.4/vendor/freemius/includes/managers/class-fs-license-manager.php (added)
-
tags/1.0.4/vendor/freemius/includes/managers/class-fs-option-manager.php (added)
-
tags/1.0.4/vendor/freemius/includes/managers/class-fs-permission-manager.php (added)
-
tags/1.0.4/vendor/freemius/includes/managers/class-fs-plan-manager.php (added)
-
tags/1.0.4/vendor/freemius/includes/managers/class-fs-plugin-manager.php (added)
-
tags/1.0.4/vendor/freemius/includes/managers/index.php (added)
-
tags/1.0.4/vendor/freemius/includes/sdk (added)
-
tags/1.0.4/vendor/freemius/includes/sdk/Exceptions (added)
-
tags/1.0.4/vendor/freemius/includes/sdk/Exceptions/ArgumentNotExistException.php (added)
-
tags/1.0.4/vendor/freemius/includes/sdk/Exceptions/EmptyArgumentException.php (added)
-
tags/1.0.4/vendor/freemius/includes/sdk/Exceptions/Exception.php (added)
-
tags/1.0.4/vendor/freemius/includes/sdk/Exceptions/InvalidArgumentException.php (added)
-
tags/1.0.4/vendor/freemius/includes/sdk/Exceptions/OAuthException.php (added)
-
tags/1.0.4/vendor/freemius/includes/sdk/Exceptions/index.php (added)
-
tags/1.0.4/vendor/freemius/includes/sdk/FreemiusBase.php (added)
-
tags/1.0.4/vendor/freemius/includes/sdk/FreemiusWordPress.php (added)
-
tags/1.0.4/vendor/freemius/includes/sdk/LICENSE.txt (added)
-
tags/1.0.4/vendor/freemius/includes/sdk/index.php (added)
-
tags/1.0.4/vendor/freemius/includes/supplements (added)
-
tags/1.0.4/vendor/freemius/includes/supplements/fs-essential-functions-1.1.7.1.php (added)
-
tags/1.0.4/vendor/freemius/includes/supplements/fs-essential-functions-2.2.1.php (added)
-
tags/1.0.4/vendor/freemius/includes/supplements/fs-migration-2.5.1.php (added)
-
tags/1.0.4/vendor/freemius/includes/supplements/index.php (added)
-
tags/1.0.4/vendor/freemius/index.php (added)
-
tags/1.0.4/vendor/freemius/languages (added)
-
tags/1.0.4/vendor/freemius/languages/freemius-cs_CZ.mo (added)
-
tags/1.0.4/vendor/freemius/languages/freemius-da_DK.mo (added)
-
tags/1.0.4/vendor/freemius/languages/freemius-de_DE.mo (added)
-
tags/1.0.4/vendor/freemius/languages/freemius-es_ES.mo (added)
-
tags/1.0.4/vendor/freemius/languages/freemius-fr_FR.mo (added)
-
tags/1.0.4/vendor/freemius/languages/freemius-he_IL.mo (added)
-
tags/1.0.4/vendor/freemius/languages/freemius-hu_HU.mo (added)
-
tags/1.0.4/vendor/freemius/languages/freemius-it_IT.mo (added)
-
tags/1.0.4/vendor/freemius/languages/freemius-ja.mo (added)
-
tags/1.0.4/vendor/freemius/languages/freemius-nl_NL.mo (added)
-
tags/1.0.4/vendor/freemius/languages/freemius-ru_RU.mo (added)
-
tags/1.0.4/vendor/freemius/languages/freemius-ta.mo (added)
-
tags/1.0.4/vendor/freemius/languages/freemius-zh_CN.mo (added)
-
tags/1.0.4/vendor/freemius/languages/freemius.pot (added)
-
tags/1.0.4/vendor/freemius/languages/index.php (added)
-
tags/1.0.4/vendor/freemius/require.php (added)
-
tags/1.0.4/vendor/freemius/start.php (added)
-
tags/1.0.4/vendor/freemius/templates (added)
-
tags/1.0.4/vendor/freemius/templates/account (added)
-
tags/1.0.4/vendor/freemius/templates/account.php (added)
-
tags/1.0.4/vendor/freemius/templates/account/billing.php (added)
-
tags/1.0.4/vendor/freemius/templates/account/index.php (added)
-
tags/1.0.4/vendor/freemius/templates/account/partials (added)
-
tags/1.0.4/vendor/freemius/templates/account/partials/activate-license-button.php (added)
-
tags/1.0.4/vendor/freemius/templates/account/partials/addon.php (added)
-
tags/1.0.4/vendor/freemius/templates/account/partials/deactivate-license-button.php (added)
-
tags/1.0.4/vendor/freemius/templates/account/partials/disconnect-button.php (added)
-
tags/1.0.4/vendor/freemius/templates/account/partials/index.php (added)
-
tags/1.0.4/vendor/freemius/templates/account/partials/site.php (added)
-
tags/1.0.4/vendor/freemius/templates/account/payments.php (added)
-
tags/1.0.4/vendor/freemius/templates/add-ons.php (added)
-
tags/1.0.4/vendor/freemius/templates/add-trial-to-pricing.php (added)
-
tags/1.0.4/vendor/freemius/templates/admin-notice.php (added)
-
tags/1.0.4/vendor/freemius/templates/ajax-loader.php (added)
-
tags/1.0.4/vendor/freemius/templates/api-connectivity-message-js.php (added)
-
tags/1.0.4/vendor/freemius/templates/auto-installation.php (added)
-
tags/1.0.4/vendor/freemius/templates/checkout (added)
-
tags/1.0.4/vendor/freemius/templates/checkout.php (added)
-
tags/1.0.4/vendor/freemius/templates/checkout/frame.php (added)
-
tags/1.0.4/vendor/freemius/templates/checkout/process-redirect.php (added)
-
tags/1.0.4/vendor/freemius/templates/checkout/redirect.php (added)
-
tags/1.0.4/vendor/freemius/templates/clone-resolution-js.php (added)
-
tags/1.0.4/vendor/freemius/templates/connect (added)
-
tags/1.0.4/vendor/freemius/templates/connect.php (added)
-
tags/1.0.4/vendor/freemius/templates/connect/index.php (added)
-
tags/1.0.4/vendor/freemius/templates/connect/permission.php (added)
-
tags/1.0.4/vendor/freemius/templates/connect/permissions-group.php (added)
-
tags/1.0.4/vendor/freemius/templates/contact.php (added)
-
tags/1.0.4/vendor/freemius/templates/debug (added)
-
tags/1.0.4/vendor/freemius/templates/debug.php (added)
-
tags/1.0.4/vendor/freemius/templates/debug/api-calls.php (added)
-
tags/1.0.4/vendor/freemius/templates/debug/index.php (added)
-
tags/1.0.4/vendor/freemius/templates/debug/logger.php (added)
-
tags/1.0.4/vendor/freemius/templates/debug/plugins-themes-sync.php (added)
-
tags/1.0.4/vendor/freemius/templates/debug/scheduled-crons.php (added)
-
tags/1.0.4/vendor/freemius/templates/email.php (added)
-
tags/1.0.4/vendor/freemius/templates/forms (added)
-
tags/1.0.4/vendor/freemius/templates/forms/affiliation.php (added)
-
tags/1.0.4/vendor/freemius/templates/forms/data-debug-mode.php (added)
-
tags/1.0.4/vendor/freemius/templates/forms/deactivation (added)
-
tags/1.0.4/vendor/freemius/templates/forms/deactivation/contact.php (added)
-
tags/1.0.4/vendor/freemius/templates/forms/deactivation/form.php (added)
-
tags/1.0.4/vendor/freemius/templates/forms/deactivation/index.php (added)
-
tags/1.0.4/vendor/freemius/templates/forms/deactivation/retry-skip.php (added)
-
tags/1.0.4/vendor/freemius/templates/forms/email-address-update.php (added)
-
tags/1.0.4/vendor/freemius/templates/forms/index.php (added)
-
tags/1.0.4/vendor/freemius/templates/forms/license-activation.php (added)
-
tags/1.0.4/vendor/freemius/templates/forms/optout.php (added)
-
tags/1.0.4/vendor/freemius/templates/forms/premium-versions-upgrade-handler.php (added)
-
tags/1.0.4/vendor/freemius/templates/forms/premium-versions-upgrade-metadata.php (added)
-
tags/1.0.4/vendor/freemius/templates/forms/resend-key.php (added)
-
tags/1.0.4/vendor/freemius/templates/forms/subscription-cancellation.php (added)
-
tags/1.0.4/vendor/freemius/templates/forms/trial-start.php (added)
-
tags/1.0.4/vendor/freemius/templates/forms/user-change.php (added)
-
tags/1.0.4/vendor/freemius/templates/gdpr-optin-js.php (added)
-
tags/1.0.4/vendor/freemius/templates/index.php (added)
-
tags/1.0.4/vendor/freemius/templates/js (added)
-
tags/1.0.4/vendor/freemius/templates/js/index.php (added)
-
tags/1.0.4/vendor/freemius/templates/js/jquery.content-change.php (added)
-
tags/1.0.4/vendor/freemius/templates/js/open-license-activation.php (added)
-
tags/1.0.4/vendor/freemius/templates/js/permissions.php (added)
-
tags/1.0.4/vendor/freemius/templates/js/style-premium-theme.php (added)
-
tags/1.0.4/vendor/freemius/templates/partials (added)
-
tags/1.0.4/vendor/freemius/templates/partials/index.php (added)
-
tags/1.0.4/vendor/freemius/templates/partials/network-activation.php (added)
-
tags/1.0.4/vendor/freemius/templates/plugin-icon.php (added)
-
tags/1.0.4/vendor/freemius/templates/plugin-info (added)
-
tags/1.0.4/vendor/freemius/templates/plugin-info/description.php (added)
-
tags/1.0.4/vendor/freemius/templates/plugin-info/features.php (added)
-
tags/1.0.4/vendor/freemius/templates/plugin-info/index.php (added)
-
tags/1.0.4/vendor/freemius/templates/plugin-info/screenshots.php (added)
-
tags/1.0.4/vendor/freemius/templates/pricing.php (added)
-
tags/1.0.4/vendor/freemius/templates/secure-https-header.php (added)
-
tags/1.0.4/vendor/freemius/templates/sticky-admin-notice-js.php (added)
-
tags/1.0.4/vendor/freemius/templates/tabs-capture-js.php (added)
-
tags/1.0.4/vendor/freemius/templates/tabs.php (added)
-
trunk/assets/js/nexlifydesk.js (modified) (2 diffs)
-
trunk/email-source/nexlifydesk-email-pipe.php (modified) (9 diffs)
-
trunk/email-source/providers/aws-ses/aws-handler.php (modified) (15 diffs)
-
trunk/email-source/providers/google/google-handler.php (modified) (5 diffs)
-
trunk/includes/class-nexlifydesk-admin.php (modified) (1 diff)
-
trunk/includes/class-nexlifydesk-ajax.php (modified) (4 diffs)
-
trunk/includes/class-nexlifydesk-database.php (modified) (2 diffs)
-
trunk/includes/class-nexlifydesk-tickets.php (modified) (3 diffs)
-
trunk/includes/nexlifydesk-functions.php (modified) (10 diffs)
-
trunk/includes/textanalysis/Console (added)
-
trunk/includes/textanalysis/Console/Commands (added)
-
trunk/includes/textanalysis/Console/Commands/NltkPackageInstallCommand.php (added)
-
trunk/includes/textanalysis/Corpus (added)
-
trunk/includes/textanalysis/Corpus/WordnetCorpus.php (added)
-
trunk/nexlifydesk.php (modified) (16 diffs)
-
trunk/readme.txt (modified) (3 diffs)
-
trunk/uninstall.php (modified) (10 diffs)
Legend:
- Unmodified
- Added
- Removed
-
nexlifydesk/trunk/assets/js/nexlifydesk.js
r3333095 r3333214 1149 1149 1150 1150 var ajaxurl = (typeof nexlifydesk_vars !== 'undefined' && nexlifydesk_vars.ajaxurl) ? nexlifydesk_vars.ajaxurl : (typeof ajaxurl !== 'undefined' ? ajaxurl : window.ajaxurl); 1151 var nonce = (typeof nexlifydesk_ vars !== 'undefined' && nexlifydesk_vars.purge_nonce) ? nexlifydesk_vars.purge_nonce : '';1151 var nonce = (typeof nexlifydesk_admin_vars !== 'undefined' && nexlifydesk_admin_vars.nonce) ? nexlifydesk_admin_vars.nonce : ''; 1152 1152 1153 1153 if (confirm('Are you sure you want to purge old data? This action cannot be undone.')) { … … 1917 1917 }); 1918 1918 }); 1919 1920 function nexlifydesk_update_templates() { 1921 if (typeof nexlifydesk_admin_vars === 'undefined') { 1922 alert('Configuration error. Please refresh the page.'); 1923 return; 1924 } 1925 1926 if (confirm(nexlifydesk_admin_vars.template_update_confirm)) { 1927 var xhr = new XMLHttpRequest(); 1928 xhr.open('POST', nexlifydesk_admin_vars.ajaxurl, true); 1929 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 1930 xhr.onreadystatechange = function() { 1931 if (xhr.readyState === 4) { 1932 if (xhr.status === 200) { 1933 document.getElementById('nexlifydesk-template-update-notice').style.display = 'none'; 1934 alert(nexlifydesk_admin_vars.template_update_success); 1935 } else { 1936 alert(nexlifydesk_admin_vars.template_update_error); 1937 } 1938 } 1939 }; 1940 xhr.send('action=nexlifydesk_update_email_templates&nonce=' + nexlifydesk_admin_vars.template_update_nonce); 1941 } 1942 } 1943 1944 function nexlifydesk_dismiss_notice() { 1945 if (typeof nexlifydesk_admin_vars === 'undefined') { 1946 document.getElementById('nexlifydesk-template-update-notice').style.display = 'none'; 1947 return; 1948 } 1949 1950 var xhr = new XMLHttpRequest(); 1951 xhr.open('POST', nexlifydesk_admin_vars.ajaxurl, true); 1952 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 1953 xhr.onreadystatechange = function() { 1954 if (xhr.readyState === 4) { 1955 document.getElementById('nexlifydesk-template-update-notice').style.display = 'none'; 1956 } 1957 }; 1958 xhr.send('action=nexlifydesk_dismiss_template_notice&nonce=' + nexlifydesk_admin_vars.template_dismiss_nonce); 1959 } 1960 1961 // Make functions globally available 1962 window.nexlifydesk_update_templates = nexlifydesk_update_templates; 1963 window.nexlifydesk_dismiss_notice = nexlifydesk_dismiss_notice; -
nexlifydesk/trunk/email-source/nexlifydesk-email-pipe.php
r3333095 r3333214 52 52 53 53 /** 54 * Get ticket by ticket ID 54 * Gets ticket by ticket_id string (e.g., "T1102") 55 * 55 56 * @param string $ticket_id The ticket ID string 56 57 * @return object|null The ticket object if found, null otherwise … … 61 62 62 63 /** 63 * 64 * Clean email subject for better duplicate detection 64 65 * Removes Re:, Fwd:, etc. prefixes and normalizes the subject 65 66 * … … 90 91 $table_name = $wpdb->prefix . 'nexlifydesk_tickets'; 91 92 93 // For non-registered users (user_id = 0) 92 94 if ($user_id == 0 && !empty($email)) { 93 95 … … 162 164 163 165 function nexlifydesk_fetch_custom_emails() { 166 // Check if IMAP extension is available 164 167 if (!extension_loaded('imap')) { 165 168 add_action('admin_notices', function() { … … 170 173 } 171 174 }); 172 return ;175 return array('error' => 'IMAP extension is not available on this server.'); 173 176 } 174 177 175 178 $settings = get_option('nexlifydesk_imap_settings', array()); 176 179 177 $host = $settings['host'] ;178 $port = $settings['port'] ;179 $encryption = $settings['encryption'] ;180 $username = $settings['username'] ;180 $host = $settings['host'] ?? ''; 181 $port = $settings['port'] ?? ''; 182 $encryption = $settings['encryption'] ?? ''; 183 $username = $settings['username'] ?? ''; 181 184 $password = nexlifydesk_get_safe_password($settings['password'] ?? ''); 182 185 $protocol = isset($settings['protocol']) ? $settings['protocol'] : 'imap'; 183 186 $delete_emails = isset($settings['delete_emails_after_fetch']) ? $settings['delete_emails_after_fetch'] : 1; 187 188 // Validate required settings 189 if (empty($host) || empty($port) || empty($username) || empty($password)) { 190 return array('error' => 'Custom IMAP/POP3 credentials not configured. Please configure Host, Port, Username, and Password.'); 191 } 192 193 // Initialize tracking variables 194 $tickets_created = 0; 195 $replies_added = 0; 196 $emails_processed = 0; 184 197 185 198 if ($protocol === 'imap') { 186 $mailbox = "{" . $host . ":" . $port . "/imap/" . $encryption . "}INBOX"; 187 188 $inbox = @imap_open($mailbox, $username, $password); 189 if (!$inbox) { 190 return; 191 } 192 $emails = imap_search($inbox, 'UNSEEN'); 193 if ($emails) { 194 foreach ($emails as $email_number) { 195 $overview = imap_fetch_overview($inbox, $email_number, 0)[0]; 196 197 $message = nexlifydesk_extract_email_body($inbox, $email_number); 198 $subject = isset($overview->subject) ? $overview->subject : ''; 199 $from = isset($overview->from) ? $overview->from : ''; 200 $date = isset($overview->date) ? $overview->date : ''; 201 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 } 207 $from = nexlifydesk_decode_email_content($from); 208 209 $parsed_from = function_exists('nexlifydesk_parse_email_from') ? 210 nexlifydesk_parse_email_from($from) : ['name' => '', 'email' => $from]; 211 212 $email_address = $parsed_from['email']; 213 $sender_name = $parsed_from['name']; 214 215 $is_admin_or_agent = function_exists('nexlifydesk_is_admin_or_agent_email') && nexlifydesk_is_admin_or_agent_email($email_address); 216 217 $original_length = strlen($message); 218 if ($is_admin_or_agent) { 219 // Keep original message intact for admin/agent 220 } else { 221 if (function_exists('nexlifydesk_extract_clean_email_content')) { 222 $message = nexlifydesk_extract_clean_email_content($message, $email_address); 223 } else { 224 $message = nexlifydesk_strip_email_thread($message, $email_address); 225 } 226 } 227 $stripped_length = strlen($message); 228 229 if (!$is_admin_or_agent && $stripped_length < ($original_length * 0.1) && $original_length > 100) { 230 if (function_exists('nexlifydesk_strip_email_thread')) { 231 $fallback_message = nexlifydesk_strip_email_thread($message, $email_address); 232 if (strlen($fallback_message) > $stripped_length) { 233 $message = $fallback_message; 234 } 235 } 236 } 237 238 if (empty($email_address)) { 239 $email_address = $from; 240 if (preg_match('/<(.+?)>/', $from, $matches)) { 241 $email_address = $matches[1]; 242 } 243 } 244 245 if (!filter_var($email_address, FILTER_VALIDATE_EMAIL)) { 246 imap_setflag_full($inbox, $email_number, "\\Seen"); 247 continue; 248 } 249 250 if (function_exists('nexlifydesk_should_block_email') && 251 nexlifydesk_should_block_email($email_address, $subject, $message, $settings)) { 252 imap_setflag_full($inbox, $email_number, "\\Seen"); 253 if ($delete_emails) { 254 imap_delete($inbox, $email_number); 255 } 256 continue; 257 } 258 259 $user = get_user_by('email', $email_address); 260 $user_id = $user ? $user->ID : 0; 261 262 if (class_exists('NexlifyDesk_Rate_Limiter') && NexlifyDesk_Rate_Limiter::is_rate_limited($user_id, $email_address)) { 263 264 if ($delete_emails) { 265 imap_delete($inbox, $email_number); 266 } 267 continue; 268 } 269 270 $ticket_id_from_subject = function_exists('nexlifydesk_extract_ticket_id_from_subject') ? nexlifydesk_extract_ticket_id_from_subject($subject) : null; 271 $existing_ticket = null; 272 273 if ($ticket_id_from_subject && function_exists('nexlifydesk_get_ticket_by_ticket_id')) { 274 $existing_ticket = nexlifydesk_get_ticket_by_ticket_id($ticket_id_from_subject); 275 } 276 277 if (!$existing_ticket) { 278 $cleaned_subject = nexlifydesk_clean_email_subject($subject); 279 280 if (function_exists('nexlifydesk_check_email_duplicate')) { 281 $duplicate_data = array( 282 'user_id' => $user_id, 283 'subject' => $cleaned_subject, 284 'message' => $message, 285 'email' => $email_address, 286 'source' => 'email' 287 ); 288 $existing_ticket = nexlifydesk_check_email_duplicate($duplicate_data); 289 } else { 290 $ticket_data = array( 291 'user_id' => $user_id, 292 'subject' => $cleaned_subject, 293 'message' => $message, 294 'email' => $email_address, 295 'source' => 'email' 296 ); 297 $existing_ticket = NexlifyDesk_Tickets::check_for_duplicate_ticket($ticket_data); 298 } 299 } 300 301 if ($existing_ticket) { 302 $reply_data = array( 303 'ticket_id' => $existing_ticket->id, 304 'user_id' => $user_id, 305 'message' => $message, 306 'source' => 'email' 307 ); 308 309 if (!$user_id && (!empty($sender_name) || !empty($email_address))) { 310 $sender_info = []; 311 if (!empty($sender_name)) { 312 $sender_info[] = "Name: {$sender_name}"; 313 } 314 if (!empty($email_address)) { 315 $sender_info[] = "Email: {$email_address}"; 316 } 317 if (!empty($sender_info)) { 318 $reply_data['message'] = "[Customer Details]\n" . implode("\n", $sender_info) . "\n\n[Reply]\n" . $message; 319 } 320 } 321 322 NexlifyDesk_Tickets::add_reply($reply_data); 323 } else { 324 $cleaned_subject = nexlifydesk_clean_email_subject($subject); 325 326 $ticket_data = array( 327 'user_id' => $user_id, 328 'subject' => $cleaned_subject, 329 'message' => $message, 330 'source' => 'email', 331 'email' => $email_address 332 ); 333 334 if (!$user_id && (!empty($sender_name) || !empty($email_address))) { 335 $sender_info = []; 336 if (!empty($sender_name)) { 337 $sender_info[] = "Name: {$sender_name}"; 338 } 339 if (!empty($email_address)) { 340 $sender_info[] = "Email: {$email_address}"; 341 } 342 if (!empty($sender_info)) { 343 $ticket_data['message'] = "[Customer Details]\n" . implode("\n", $sender_info) . "\n\n[Message]\n" . $message; 344 } 345 } 346 347 $new_ticket_id = NexlifyDesk_Tickets::create_ticket($ticket_data); 348 if (!$user_id && !empty($email_address) && $new_ticket_id && !is_wp_error($new_ticket_id)) { 349 update_post_meta($new_ticket_id, 'customer_email', $email_address); 350 } 351 } 352 353 imap_setflag_full($inbox, $email_number, "\\Seen"); 354 if ($delete_emails) { 355 imap_delete($inbox, $email_number); 356 } 357 } 199 return nexlifydesk_process_imap_emails($host, $port, $encryption, $username, $password, $delete_emails); 200 } elseif ($protocol === 'pop3') { 201 return nexlifydesk_process_pop3_emails($host, $port, $encryption, $username, $password, $delete_emails); 202 } else { 203 return array('error' => 'Unsupported protocol. Please use IMAP or POP3.'); 204 } 205 } 206 207 function nexlifydesk_process_imap_emails($host, $port, $encryption, $username, $password, $delete_emails) { 208 $mailbox = "{" . $host . ":" . $port . "/imap/" . $encryption . "}INBOX"; 209 210 $inbox = @imap_open($mailbox, $username, $password); 211 if (!$inbox) { 212 return array('error' => 'Failed to connect to IMAP server. Please check your credentials and server settings.'); 213 } 214 215 $emails = imap_search($inbox, 'UNSEEN'); 216 if (!$emails) { 217 imap_close($inbox); 218 return array('success' => true, 'message' => 'No new emails found on IMAP server.', 'count' => 0); 219 } 220 221 $tickets_created = 0; 222 $replies_added = 0; 223 $emails_processed = count($emails); 224 225 foreach ($emails as $email_number) { 226 $overview = imap_fetch_overview($inbox, $email_number, 0)[0]; 227 228 $message = nexlifydesk_extract_email_body($inbox, $email_number); 229 $subject = isset($overview->subject) ? $overview->subject : ''; 230 $from = isset($overview->from) ? $overview->from : ''; 231 $date = isset($overview->date) ? $overview->date : ''; 232 233 // Decode MIME-encoded subject first, then apply general email content decoding 234 if (function_exists('nexlifydesk_decode_email_subject')) { 235 $subject = nexlifydesk_decode_email_subject($subject); 236 } else { 237 $subject = nexlifydesk_decode_email_content($subject); 238 } 239 $from = nexlifydesk_decode_email_content($from); 240 241 $parsed_from = function_exists('nexlifydesk_parse_email_from') ? 242 nexlifydesk_parse_email_from($from) : ['name' => '', 'email' => $from]; 243 244 $email_address = $parsed_from['email']; 245 $sender_name = $parsed_from['name']; 246 247 $is_admin_or_agent = function_exists('nexlifydesk_is_admin_or_agent_email') && nexlifydesk_is_admin_or_agent_email($email_address); 248 249 $original_length = strlen($message); 250 if ($is_admin_or_agent) { 251 // Keep original message intact for admin/agent 252 } else { 253 if (function_exists('nexlifydesk_extract_clean_email_content')) { 254 $message = nexlifydesk_extract_clean_email_content($message, $email_address); 255 } else { 256 $message = nexlifydesk_strip_email_thread($message, $email_address); 257 } 258 } 259 $stripped_length = strlen($message); 260 261 if (!$is_admin_or_agent && $stripped_length < ($original_length * 0.1) && $original_length > 100) { 262 if (function_exists('nexlifydesk_strip_email_thread')) { 263 $fallback_message = nexlifydesk_strip_email_thread($message, $email_address); 264 if (strlen($fallback_message) > $stripped_length) { 265 $message = $fallback_message; 266 } 267 } 268 } 269 270 if (empty($email_address)) { 271 $email_address = $from; 272 if (preg_match('/<(.+?)>/', $from, $matches)) { 273 $email_address = $matches[1]; 274 } 275 } 276 277 if (!filter_var($email_address, FILTER_VALIDATE_EMAIL)) { 278 imap_setflag_full($inbox, $email_number, "\\Seen"); 279 continue; 280 } 281 282 $settings = get_option('nexlifydesk_imap_settings', array()); 283 if (function_exists('nexlifydesk_should_block_email') && 284 nexlifydesk_should_block_email($email_address, $subject, $message, $settings)) { 285 imap_setflag_full($inbox, $email_number, "\\Seen"); 358 286 if ($delete_emails) { 359 imap_expunge($inbox); 360 } 361 } 362 imap_close($inbox); 363 } elseif ($protocol === 'pop3') { 364 $mailbox = "{" . $host . ":" . $port . "/pop3/" . $encryption . "}INBOX"; 365 366 $inbox = @imap_open($mailbox, $username, $password); 367 if (!$inbox) { 368 return; 369 } 370 $num_msgs = imap_num_msg($inbox); 371 for ($i = 1; $i <= $num_msgs; $i++) { 372 $header = imap_headerinfo($inbox, $i); 373 $overview = imap_fetch_overview($inbox, $i, 0)[0]; 374 375 $message = nexlifydesk_extract_email_body($inbox, $i); 376 $subject = isset($overview->subject) ? $overview->subject : (isset($header->subject) ? $header->subject : ''); 377 $from = isset($overview->from) ? $overview->from : (isset($header->from) ? $header->from : ''); 378 $date = isset($overview->date) ? $overview->date : (isset($header->date) ? $header->date : ''); 379 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 } 385 $from = nexlifydesk_decode_email_content($from); 386 387 $parsed_from = function_exists('nexlifydesk_parse_email_from') ? 388 nexlifydesk_parse_email_from($from) : ['name' => '', 'email' => $from]; 389 390 $email_address = $parsed_from['email']; 391 $sender_name = $parsed_from['name']; 392 393 $is_admin_or_agent = function_exists('nexlifydesk_is_admin_or_agent_email') && nexlifydesk_is_admin_or_agent_email($email_address); 394 395 $original_length = strlen($message); 396 if ($is_admin_or_agent) { 397 // Keep original message intact for admin/agent 398 } else { 399 if (function_exists('nexlifydesk_extract_clean_email_content')) { 400 $message = nexlifydesk_extract_clean_email_content($message, $email_address); 401 } else { 402 $message = nexlifydesk_strip_email_thread($message, $email_address); 403 } 404 } 405 $stripped_length = strlen($message); 406 407 if (!$is_admin_or_agent && $stripped_length < ($original_length * 0.1) && $original_length > 100) { 408 } 409 410 if (empty($email_address)) { 411 $email_address = $from; 412 if (preg_match('/<(.+?)>/', $from, $matches)) { 413 $email_address = $matches[1]; 414 } 415 } 416 417 if (!filter_var($email_address, FILTER_VALIDATE_EMAIL)) { 418 if ($delete_emails) { 419 imap_delete($inbox, $i); 420 } 421 continue; 422 } 423 424 if (function_exists('nexlifydesk_should_block_email') && 425 nexlifydesk_should_block_email($email_address, $subject, $message, $settings)) { 426 if ($delete_emails) { 427 imap_delete($inbox, $i); 428 } 429 continue; 430 } 431 432 $user = get_user_by('email', $email_address); 433 $user_id = $user ? $user->ID : 0; 434 435 if (class_exists('NexlifyDesk_Rate_Limiter') && NexlifyDesk_Rate_Limiter::is_rate_limited($user_id, $email_address)) { 436 437 if ($delete_emails) { 438 imap_delete($inbox, $i); 439 } 440 continue; 441 } 442 443 $ticket_id_from_subject = function_exists('nexlifydesk_extract_ticket_id_from_subject') ? nexlifydesk_extract_ticket_id_from_subject($subject) : null; 444 $existing_ticket = null; 445 446 if ($ticket_id_from_subject && function_exists('nexlifydesk_get_ticket_by_ticket_id')) { 447 $existing_ticket = nexlifydesk_get_ticket_by_ticket_id($ticket_id_from_subject); 448 } 449 450 if (!$existing_ticket) { 451 $cleaned_subject = nexlifydesk_clean_email_subject($subject); 452 453 if (function_exists('nexlifydesk_check_email_duplicate')) { 454 $duplicate_data = array( 455 'user_id' => $user_id, 456 'subject' => $cleaned_subject, 457 'message' => $message, 458 'email' => $email_address, 459 'source' => 'email' 460 ); 461 $existing_ticket = nexlifydesk_check_email_duplicate($duplicate_data); 462 } else { 463 $ticket_data = array( 464 'user_id' => $user_id, 465 'subject' => $cleaned_subject, 466 'message' => $message, 467 'email' => $email_address, 468 'source' => 'email' 469 ); 470 $existing_ticket = NexlifyDesk_Tickets::check_for_duplicate_ticket($ticket_data); 471 } 472 } 473 474 if ($existing_ticket) { 475 $reply_data = array( 476 'ticket_id' => $existing_ticket->id, 287 imap_delete($inbox, $email_number); 288 } 289 continue; 290 } 291 292 $user = get_user_by('email', $email_address); 293 $user_id = $user ? $user->ID : 0; 294 295 if (class_exists('NexlifyDesk_Rate_Limiter') && NexlifyDesk_Rate_Limiter::is_rate_limited($user_id, $email_address)) { 296 297 if ($delete_emails) { 298 imap_delete($inbox, $email_number); 299 } 300 continue; 301 } 302 303 $ticket_id_from_subject = function_exists('nexlifydesk_extract_ticket_id_from_subject') ? nexlifydesk_extract_ticket_id_from_subject($subject) : null; 304 $existing_ticket = null; 305 306 if ($ticket_id_from_subject && function_exists('nexlifydesk_get_ticket_by_ticket_id')) { 307 $existing_ticket = nexlifydesk_get_ticket_by_ticket_id($ticket_id_from_subject); 308 } 309 310 if (!$existing_ticket) { 311 $cleaned_subject = nexlifydesk_clean_email_subject($subject); 312 313 if (function_exists('nexlifydesk_check_email_duplicate')) { 314 $duplicate_data = array( 477 315 'user_id' => $user_id, 316 'subject' => $cleaned_subject, 478 317 'message' => $message, 318 'email' => $email_address, 479 319 'source' => 'email' 480 320 ); 481 482 if (!$user_id && (!empty($sender_name) || !empty($email_address))) { 483 $sender_info = []; 484 if (!empty($sender_name)) { 485 $sender_info[] = "Name: {$sender_name}"; 486 } 487 if (!empty($email_address)) { 488 $sender_info[] = "Email: {$email_address}"; 489 } 490 if (!empty($sender_info)) { 491 $reply_data['message'] = "[Customer Details]\n" . implode("\n", $sender_info) . "\n\n[Reply]\n" . $message; 492 } 493 } 494 495 NexlifyDesk_Tickets::add_reply($reply_data); 321 $existing_ticket = nexlifydesk_check_email_duplicate($duplicate_data); 496 322 } else { 497 $cleaned_subject = nexlifydesk_clean_email_subject($subject);498 499 323 $ticket_data = array( 500 324 'user_id' => $user_id, 501 325 'subject' => $cleaned_subject, 502 326 'message' => $message, 503 ' source' => 'email',504 ' email' => $email_address327 'email' => $email_address, 328 'source' => 'email' 505 329 ); 506 507 if (!$user_id && (!empty($sender_name) || !empty($email_address))) { 508 $sender_info = []; 509 if (!empty($sender_name)) { 510 $sender_info[] = "Name: {$sender_name}"; 511 } 512 if (!empty($email_address)) { 513 $sender_info[] = "Email: {$email_address}"; 514 } 515 if (!empty($sender_info)) { 516 $ticket_data['message'] = "[Customer Details]\n" . implode("\n", $sender_info) . "\n\n[Message]\n" . $message; 517 } 518 } 519 520 NexlifyDesk_Tickets::create_ticket($ticket_data); 521 } 330 $existing_ticket = NexlifyDesk_Tickets::check_for_duplicate_ticket($ticket_data); 331 } 332 } 333 334 if ($existing_ticket) { 335 $reply_data = array( 336 'ticket_id' => $existing_ticket->id, 337 'user_id' => $user_id, 338 'message' => $message, 339 'source' => 'email' 340 ); 341 342 if (!$user_id && (!empty($sender_name) || !empty($email_address))) { 343 $sender_info = []; 344 if (!empty($sender_name)) { 345 $sender_info[] = "Name: {$sender_name}"; 346 } 347 if (!empty($email_address)) { 348 $sender_info[] = "Email: {$email_address}"; 349 } 350 if (!empty($sender_info)) { 351 $reply_data['message'] = "[Customer Details]\n" . implode("\n", $sender_info) . "\n\n[Reply]\n" . $message; 352 } 353 } 354 355 NexlifyDesk_Tickets::add_reply($reply_data); 356 $replies_added++; 357 } else { 358 $cleaned_subject = nexlifydesk_clean_email_subject($subject); 359 360 $ticket_data = array( 361 'user_id' => $user_id, 362 'subject' => $cleaned_subject, 363 'message' => $message, 364 'source' => 'email', 365 'email' => $email_address 366 ); 367 368 if (!$user_id && (!empty($sender_name) || !empty($email_address))) { 369 $sender_info = []; 370 if (!empty($sender_name)) { 371 $sender_info[] = "Name: {$sender_name}"; 372 } 373 if (!empty($email_address)) { 374 $sender_info[] = "Email: {$email_address}"; 375 } 376 if (!empty($sender_info)) { 377 $ticket_data['message'] = "[Customer Details]\n" . implode("\n", $sender_info) . "\n\n[Message]\n" . $message; 378 } 379 } 380 381 $new_ticket_id = NexlifyDesk_Tickets::create_ticket($ticket_data); 382 if ($new_ticket_id && !is_wp_error($new_ticket_id)) { 383 $tickets_created++; 384 } 385 if (!$user_id && !empty($email_address) && $new_ticket_id && !is_wp_error($new_ticket_id)) { 386 update_post_meta($new_ticket_id, 'customer_email', $email_address); 387 } 388 } 389 390 imap_setflag_full($inbox, $email_number, "\\Seen"); 391 if ($delete_emails) { 392 imap_delete($inbox, $email_number); 393 } 394 } 395 396 if ($delete_emails) { 397 imap_expunge($inbox); 398 } 399 400 imap_close($inbox); 401 402 return array( 403 'success' => true, 404 'message' => "Successfully processed {$emails_processed} emails. Created {$tickets_created} tickets and added {$replies_added} replies.", 405 'tickets_created' => $tickets_created, 406 'replies_added' => $replies_added, 407 'emails_processed' => $emails_processed 408 ); 409 } 410 411 function nexlifydesk_process_pop3_emails($host, $port, $encryption, $username, $password, $delete_emails) { 412 $mailbox = "{" . $host . ":" . $port . "/pop3/" . $encryption . "}INBOX"; 413 414 $inbox = @imap_open($mailbox, $username, $password); 415 if (!$inbox) { 416 return array('error' => 'Failed to connect to POP3 server. Please check your credentials and server settings.'); 417 } 418 419 $num_msgs = imap_num_msg($inbox); 420 $tickets_created = 0; 421 $replies_added = 0; 422 $emails_processed = $num_msgs; 423 424 for ($i = 1; $i <= $num_msgs; $i++) { 425 $header = imap_headerinfo($inbox, $i); 426 $overview = imap_fetch_overview($inbox, $i, 0)[0]; 427 428 $message = nexlifydesk_extract_email_body($inbox, $i); 429 $subject = isset($overview->subject) ? $overview->subject : (isset($header->subject) ? $header->subject : ''); 430 $from = isset($overview->from) ? $overview->from : (isset($header->from) ? $header->from : ''); 431 $date = isset($overview->date) ? $overview->date : (isset($header->date) ? $header->date : ''); 432 433 if (function_exists('nexlifydesk_decode_email_subject')) { 434 $subject = nexlifydesk_decode_email_subject($subject); 435 } else { 436 $subject = nexlifydesk_decode_email_content($subject); 437 } 438 $from = nexlifydesk_decode_email_content($from); 439 440 $parsed_from = function_exists('nexlifydesk_parse_email_from') ? 441 nexlifydesk_parse_email_from($from) : ['name' => '', 'email' => $from]; 442 443 $email_address = $parsed_from['email']; 444 $sender_name = $parsed_from['name']; 445 446 $is_admin_or_agent = function_exists('nexlifydesk_is_admin_or_agent_email') && nexlifydesk_is_admin_or_agent_email($email_address); 447 448 $original_length = strlen($message); 449 if ($is_admin_or_agent) { 450 // Keep original message intact for admin/agent 451 } else { 452 if (function_exists('nexlifydesk_extract_clean_email_content')) { 453 $message = nexlifydesk_extract_clean_email_content($message, $email_address); 454 } else { 455 $message = nexlifydesk_strip_email_thread($message, $email_address); 456 } 457 } 458 $stripped_length = strlen($message); 459 460 if (!$is_admin_or_agent && $stripped_length < ($original_length * 0.1) && $original_length > 100) { 461 if (function_exists('nexlifydesk_strip_email_thread')) { 462 $fallback_message = nexlifydesk_strip_email_thread($message, $email_address); 463 if (strlen($fallback_message) > $stripped_length) { 464 $message = $fallback_message; 465 } 466 } 467 } 468 469 if (empty($email_address)) { 470 $email_address = $from; 471 if (preg_match('/<(.+?)>/', $from, $matches)) { 472 $email_address = $matches[1]; 473 } 474 } 475 476 if (!filter_var($email_address, FILTER_VALIDATE_EMAIL)) { 522 477 if ($delete_emails) { 523 478 imap_delete($inbox, $i); 524 479 } 480 continue; 481 } 482 483 $settings = get_option('nexlifydesk_imap_settings', array()); 484 if (function_exists('nexlifydesk_should_block_email') && 485 nexlifydesk_should_block_email($email_address, $subject, $message, $settings)) { 486 if ($delete_emails) { 487 imap_delete($inbox, $i); 488 } 489 continue; 490 } 491 492 $user = get_user_by('email', $email_address); 493 $user_id = $user ? $user->ID : 0; 494 495 if (class_exists('NexlifyDesk_Rate_Limiter') && NexlifyDesk_Rate_Limiter::is_rate_limited($user_id, $email_address)) { 496 497 if ($delete_emails) { 498 imap_delete($inbox, $i); 499 } 500 continue; 501 } 502 503 $ticket_id_from_subject = function_exists('nexlifydesk_extract_ticket_id_from_subject') ? nexlifydesk_extract_ticket_id_from_subject($subject) : null; 504 $existing_ticket = null; 505 506 if ($ticket_id_from_subject && function_exists('nexlifydesk_get_ticket_by_ticket_id')) { 507 $existing_ticket = nexlifydesk_get_ticket_by_ticket_id($ticket_id_from_subject); 508 } 509 510 if (!$existing_ticket) { 511 $cleaned_subject = nexlifydesk_clean_email_subject($subject); 512 513 if (function_exists('nexlifydesk_check_email_duplicate')) { 514 $duplicate_data = array( 515 'user_id' => $user_id, 516 'subject' => $cleaned_subject, 517 'message' => $message, 518 'email' => $email_address, 519 'source' => 'email' 520 ); 521 $existing_ticket = nexlifydesk_check_email_duplicate($duplicate_data); 522 } else { 523 $ticket_data = array( 524 'user_id' => $user_id, 525 'subject' => $cleaned_subject, 526 'message' => $message, 527 'email' => $email_address, 528 'source' => 'email' 529 ); 530 $existing_ticket = NexlifyDesk_Tickets::check_for_duplicate_ticket($ticket_data); 531 } 532 } 533 534 if ($existing_ticket) { 535 $reply_data = array( 536 'ticket_id' => $existing_ticket->id, 537 'user_id' => $user_id, 538 'message' => $message, 539 'source' => 'email' 540 ); 541 542 if (!$user_id && (!empty($sender_name) || !empty($email_address))) { 543 $sender_info = []; 544 if (!empty($sender_name)) { 545 $sender_info[] = "Name: {$sender_name}"; 546 } 547 if (!empty($email_address)) { 548 $sender_info[] = "Email: {$email_address}"; 549 } 550 if (!empty($sender_info)) { 551 $reply_data['message'] = "[Customer Details]\n" . implode("\n", $sender_info) . "\n\n[Reply]\n" . $message; 552 } 553 } 554 555 NexlifyDesk_Tickets::add_reply($reply_data); 556 $replies_added++; 557 } else { 558 $cleaned_subject = nexlifydesk_clean_email_subject($subject); 559 560 $ticket_data = array( 561 'user_id' => $user_id, 562 'subject' => $cleaned_subject, 563 'message' => $message, 564 'source' => 'email', 565 'email' => $email_address 566 ); 567 568 if (!$user_id && (!empty($sender_name) || !empty($email_address))) { 569 $sender_info = []; 570 if (!empty($sender_name)) { 571 $sender_info[] = "Name: {$sender_name}"; 572 } 573 if (!empty($email_address)) { 574 $sender_info[] = "Email: {$email_address}"; 575 } 576 if (!empty($sender_info)) { 577 $ticket_data['message'] = "[Customer Details]\n" . implode("\n", $sender_info) . "\n\n[Message]\n" . $message; 578 } 579 } 580 581 $new_ticket_id = NexlifyDesk_Tickets::create_ticket($ticket_data); 582 if ($new_ticket_id && !is_wp_error($new_ticket_id)) { 583 $tickets_created++; 584 } 525 585 } 526 586 if ($delete_emails) { 527 imap_expunge($inbox); 528 } 529 imap_close($inbox); 530 } 587 imap_delete($inbox, $i); 588 } 589 } 590 591 if ($delete_emails) { 592 imap_expunge($inbox); 593 } 594 595 imap_close($inbox); 596 597 return array( 598 'success' => true, 599 'message' => "Successfully processed {$emails_processed} emails. Created {$tickets_created} tickets and added {$replies_added} replies.", 600 'tickets_created' => $tickets_created, 601 'replies_added' => $replies_added, 602 'emails_processed' => $emails_processed 603 ); 531 604 } 532 605 … … 671 744 } 672 745 673 // Only proceed if password is properly encrypted674 746 if (nexlifydesk_is_encrypted($encrypted_password)) { 675 747 $decrypted = nexlifydesk_decrypt($encrypted_password); … … 678 750 } 679 751 } 680 // If not encrypted or decryption fails, return empty string752 681 753 return ''; 682 754 } … … 715 787 $message = $body; 716 788 $is_html = false; 717 break; // Prefer plain text over HTML789 break; 718 790 } elseif (strtoupper($part->subtype) == 'HTML' && empty($message)) { 719 791 $message = $body; … … 809 881 */ 810 882 function nexlifydesk_is_html_content($content) { 811 // Check for common HTML patterns812 883 $html_patterns = [ 813 884 '/<html[^>]*>/i', -
nexlifydesk/trunk/email-source/providers/aws-ses/aws-handler.php
r3333095 r3333214 3 3 if (!defined('ABSPATH')) exit; 4 4 5 // Ensure helpers are available for email decoding functions6 5 if (!function_exists('nexlifydesk_decode_email_subject')) { 7 6 require_once dirname(__FILE__) . '/../../includes/helpers.php'; 8 7 } 9 10 /**11 * AWS SES/WorkMail Email Handler for NexlifyDesk12 * Handles both IMAP email fetching and SES authentication13 */14 8 15 9 /** … … 17 11 */ 18 12 function nexlifydesk_fetch_aws_emails() { 19 // Check if IMAP extension is available with better detection20 13 if (!extension_loaded('imap') || !function_exists('imap_open')) { 21 14 … … 39 32 (isset($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] === 'on') || 40 33 (isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] === 'https') || 41 ((wp_parse_url(home_url(), PHP_URL_SCHEME) === 'https'))34 ((wp_parse_url(home_url(), ) === 'https')) 42 35 ); 43 36 … … 63 56 64 57 if (empty($organization_id) || empty($email) || empty($password)) { 65 return ;58 return array('error' => 'AWS WorkMail credentials not configured. Please configure Organization ID, Email, and Password.'); 66 59 } 67 60 … … 72 65 $mailbox = "{{$imap_host}:{$imap_port}/imap/{$encryption}}INBOX"; 73 66 74 // Clear any previous IMAP errors75 67 if (function_exists('imap_errors')) { 76 68 imap_errors(); … … 82 74 $inbox = @imap_open($mailbox, $email, $password, OP_SILENT); 83 75 if (!$inbox) { 84 return; 85 } 86 87 $mailbox_info = imap_mailboxmsginfo($inbox); 88 if ($mailbox_info) { 89 } else { 90 $total_msgs = imap_num_msg($inbox); 76 return array('error' => 'Failed to connect to AWS WorkMail IMAP server. Please check your credentials and region.'); 91 77 } 92 78 … … 104 90 $emails = imap_search($inbox, $search_criteria); 105 91 106 // If no emails found with date criteria, try just UNSEEN to see if there are any unread emails at all107 92 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 93 $emails = imap_search($inbox, "UNSEEN"); 94 95 if (!$emails) { 114 96 $total_msgs = imap_num_msg($inbox); 115 116 97 $manually_found = []; 117 98 for ($i = 1; $i <= $total_msgs; $i++) { … … 119 100 if (!empty($overview) && isset($overview[0])) { 120 101 $msg = $overview[0]; 121 // Check if message is unread (not seen)122 102 if (!isset($msg->seen) || $msg->seen == 0) { 123 103 $manually_found[] = $i; … … 125 105 } 126 106 } 127 128 107 if (!empty($manually_found)) { 129 108 $emails = $manually_found; … … 134 113 if (!$emails) { 135 114 imap_close($inbox); 136 return ;115 return array('success' => true, 'message' => 'No new emails found in AWS WorkMail.', 'count' => 0); 137 116 } 138 117 139 118 $processed_emails = get_option('nexlifydesk_aws_processed_emails', []); 140 119 $new_processed = []; 120 $tickets_created = 0; 121 $replies_added = 0; 122 $emails_processed = 0; 141 123 142 124 foreach ($emails as $email_number) { … … 147 129 } 148 130 131 $emails_processed++; 149 132 $new_processed[] = $uid; 150 133 … … 155 138 $subject = $overview->subject ?? '(No Subject)'; 156 139 157 // Decode MIME-encoded subject to fix encoding issues like "=?UTF-8?Q?..."158 140 if (function_exists('nexlifydesk_decode_email_subject')) { 159 141 $subject = nexlifydesk_decode_email_subject($subject); … … 267 249 268 250 $reply_result = NexlifyDesk_Tickets::add_reply($reply_data); 251 if ($reply_result) { 252 $replies_added++; 253 } 269 254 } else { 270 255 $cleaned_subject = nexlifydesk_clean_email_subject($subject); … … 309 294 } 310 295 update_option('nexlifydesk_aws_processed_emails', $all_processed); 296 297 return array( 298 'success' => true, 299 'message' => sprintf('Processed %d emails. Created %d tickets, added %d replies.', 300 $emails_processed, $tickets_created, $replies_added), 301 'count' => $emails_processed, 302 'tickets_created' => $tickets_created, 303 'replies_added' => $replies_added 304 ); 311 305 } 312 306 … … 359 353 } 360 354 361 // Check if the connection is ready for AWS SES362 355 public function is_connection_ready() { 363 356 $is_ssl_enabled = $this->check_ssl_enabled(); -
nexlifydesk/trunk/email-source/providers/google/google-handler.php
r3333095 r3333214 150 150 $delete_emails = isset($settings['delete_emails_after_fetch']) ? $settings['delete_emails_after_fetch'] : 1; 151 151 152 // Check if Google credentials are configured 153 if (empty($settings['google_client_id']) || empty($settings['google_client_secret']) || empty($settings['google_refresh_token'])) { 154 return array('error' => 'Google credentials not configured. Please authorize with Google first.'); 155 } 156 152 157 $access_token = nexlifydesk_get_google_access_token(); 153 158 if (!$access_token) { 154 return ;159 return array('error' => 'Failed to obtain Google access token. Please re-authorize with Google.'); 155 160 } 156 161 … … 180 185 181 186 if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) { 182 return ;187 return array('error' => 'Failed to connect to Gmail API. Please check your Google authorization.'); 183 188 } 184 189 185 190 $list_body = json_decode(wp_remote_retrieve_body($response), true); 186 191 if (empty($list_body['messages'])) { 187 return ;192 return array('success' => true, 'message' => 'No new emails found in Gmail.', 'count' => 0); 188 193 } 189 194 190 195 $message_count = count($list_body['messages']); 196 $tickets_created = 0; 197 $replies_added = 0; 191 198 192 199 foreach ($list_body['messages'] as $message_item) { … … 331 338 332 339 $reply_result = NexlifyDesk_Tickets::add_reply($reply_data); 340 if ($reply_result) { 341 $replies_added++; 342 } 333 343 } else { 334 344 $cleaned_subject = nexlifydesk_clean_email_subject($subject_header); … … 355 365 356 366 $ticket = NexlifyDesk_Tickets::create_ticket($ticket_data); 367 if ($ticket && !is_wp_error($ticket)) { 368 $tickets_created++; 369 } 357 370 } 358 371 … … 369 382 } 370 383 update_option('nexlifydesk_processed_emails', $all_processed); 384 385 return array( 386 'success' => true, 387 'message' => sprintf('Processed %d emails. Created %d tickets, added %d replies.', 388 $message_count, $tickets_created, $replies_added), 389 'count' => $message_count, 390 'tickets_created' => $tickets_created, 391 'replies_added' => $replies_added 392 ); 371 393 } 372 394 -
nexlifydesk/trunk/includes/class-nexlifydesk-admin.php
r3333095 r3333214 864 864 'assigned_to_header' => __('Assigned To', 'nexlifydesk'), 865 865 'agent_assigned_text' => __('Ticket assigned successfully!', 'nexlifydesk'), 866 'template_update_nonce' => wp_create_nonce('nexlifydesk_update_templates'), 867 'template_dismiss_nonce' => wp_create_nonce('nexlifydesk_dismiss_notice'), 868 'template_update_confirm' => __('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'), 869 'template_update_success' => __('Email templates updated successfully! Check the Email Templates page to see the changes.', 'nexlifydesk'), 870 'template_update_error' => __('Error updating templates. Please try again.', 'nexlifydesk'), 866 871 ) 867 872 ); -
nexlifydesk/trunk/includes/class-nexlifydesk-ajax.php
r3333095 r3333214 189 189 190 190 $current_user = wp_get_current_user(); 191 191 192 $can_reply = false; 192 193 … … 986 987 wp_send_json_error(array('message' => __('Invalid nonce.', 'nexlifydesk'))); 987 988 } 988 wp_send_json_success(array('message' => 'Custom email fetch test successful!')); 989 990 // Call the actual custom email fetch function to test it 991 $result = nexlifydesk_fetch_custom_emails(); 992 993 if (isset($result['error'])) { 994 wp_send_json_error(array('message' => $result['error'])); 995 } else { 996 wp_send_json_success(array('message' => $result['message'] ?? 'Custom email fetch test successful!')); 997 } 989 998 } 990 999 … … 1041 1050 if (function_exists('nexlifydesk_fetch_emails')) { 1042 1051 nexlifydesk_fetch_emails(); 1052 1053 if (function_exists('nexlifydesk_fetch_aws_emails')) { 1054 $result = nexlifydesk_fetch_aws_emails(); 1055 1056 if (is_array($result) && isset($result['error'])) { 1057 update_option('nexlifydesk_imap_settings', $current_settings); 1058 wp_send_json_error(array('message' => $result['error'])); 1059 return; 1060 } 1061 1062 if (is_array($result) && isset($result['success']) && $result['success']) { 1063 update_option('nexlifydesk_imap_settings', $current_settings); 1064 wp_send_json_success(array('message' => $result['message'])); 1065 return; 1066 } 1067 } 1043 1068 } else { 1044 1069 throw new Exception('Email fetch function not found'); … … 1471 1496 1472 1497 if (function_exists('nexlifydesk_fetch_google_emails')) { 1473 nexlifydesk_fetch_google_emails(); 1498 $result = nexlifydesk_fetch_google_emails(); 1499 1500 // Check if there was an error 1501 if (is_array($result) && isset($result['error'])) { 1502 wp_send_json_error(array('message' => $result['error'])); 1503 return; 1504 } 1505 1506 // If we have a success result with details 1507 if (is_array($result) && isset($result['success']) && $result['success']) { 1508 wp_send_json_success(array('message' => $result['message'])); 1509 return; 1510 } 1474 1511 } else { 1475 1512 throw new Exception('Google email fetch function not found'); 1476 1513 } 1477 1514 1515 // Fallback for legacy behavior - check for recent tickets 1478 1516 global $wpdb; 1479 1517 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe and not user input -
nexlifydesk/trunk/includes/class-nexlifydesk-database.php
r3333095 r3333214 193 193 194 194 public static function check_and_run_migrations() { 195 $current_version = get_option('nexlifydesk_db_version', '1.0. 3');195 $current_version = get_option('nexlifydesk_db_version', '1.0.4'); 196 196 $plugin_version = NEXLIFYDESK_VERSION; 197 197 198 if (version_compare($current_version, '1.0. 3', '<')) {198 if (version_compare($current_version, '1.0.4', '<')) { 199 199 self::migrate_to_1_0_1(); 200 update_option('nexlifydesk_db_version', '1.0. 3');201 } 202 203 if (version_compare($current_version, '1.0. 3', '<')) {200 update_option('nexlifydesk_db_version', '1.0.4'); 201 } 202 203 if (version_compare($current_version, '1.0.4', '<')) { 204 204 self::migrate_to_1_0_2(); 205 update_option('nexlifydesk_db_version', '1.0. 3');205 update_option('nexlifydesk_db_version', '1.0.4'); 206 206 } 207 207 … … 293 293 global $wpdb; 294 294 295 $current_version = get_option('nexlifydesk_version', '1.0. 3');296 297 if ($current_version === '1.0. 3' && !get_option('nexlifydesk_db_version')) {295 $current_version = get_option('nexlifydesk_version', '1.0.4'); 296 297 if ($current_version === '1.0.4' && !get_option('nexlifydesk_db_version')) { 298 298 return; 299 299 } -
nexlifydesk/trunk/includes/class-nexlifydesk-tickets.php
r3333095 r3333214 606 606 global $wpdb; 607 607 608 $allowed_statuses = array('open', 'pending', 'in_progress', 'resolved', 'closed'); 608 $allowed_statuses = array('open', 'pending', 'in_progress', 'resolved', 'closed'); // <-- Add in_progress here 609 609 if (!in_array($status, $allowed_statuses)) { 610 610 return false; … … 1635 1635 1636 1636 /** 1637 * Check for duplicate tickets using improved user-scoped logic: 1638 * - For registered users: Check ONLY within their own tickets, never cross-user matching 1639 * - For unregistered users: Consolidate by email address only, no content matching needed 1640 * 1637 1641 * @param array $data Ticket data to check for duplicates 1638 1642 * @return object|false Returns existing ticket if duplicate found, false otherwise 1639 1643 */ 1640 1644 public static function check_for_duplicate_ticket($data) { 1645 // Use the new global duplicate detection function for all channels and user types 1641 1646 if (!function_exists('nexlifydesk_find_duplicate_ticket')) { 1642 1647 require_once dirname(__FILE__) . '/nexlifydesk-functions.php'; … … 1768 1773 1769 1774 /** 1775 * Update ticket priority 1770 1776 * 1771 1777 * @param int $ticket_id Ticket ID -
nexlifydesk/trunk/includes/nexlifydesk-functions.php
r3333095 r3333214 196 196 197 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 198 * Global duplicate ticket detection for all channels 199 * Implements correct logic per user requirements: 200 * - Registered users: Check ONLY within their own tickets, never cross-user matching 200 201 * - Unregistered users: Consolidate by email address only, no content matching needed 201 202 * … … 211 212 $table_name = $wpdb->prefix . 'nexlifydesk_tickets'; 212 213 213 // Registered users 214 // ======================== 215 // A. REGISTERED USER LOGIC 216 // ======================== 214 217 if ($user_id > 0) { 218 // Step 1: Check if this registered user has ANY existing ticket (open, pending, in_progress) 215 219 $cache_key = 'nexlifydesk_user_has_tickets_' . $user_id; 216 220 $user_has_tickets = wp_cache_get($cache_key); … … 227 231 } 228 232 233 // Step 2: If user has no existing tickets, create new ticket (no duplicate possible) 229 234 if (!$user_has_tickets) { 230 return null; 231 } 232 235 return null; // No existing tickets = create new ticket 236 } 237 238 // Step 3: User has existing tickets - perform content-based duplicate detection ONLY within this user's tickets 233 239 $order_numbers = array(); 234 240 if (!empty($subject)) { … … 240 246 $order_numbers = array_unique($order_numbers); 241 247 248 // Check for order number matches within user's own tickets 242 249 if (!empty($order_numbers)) { 243 250 $order_regex = implode('|', array_map('preg_quote', $order_numbers)); … … 257 264 } 258 265 266 // Check for exact subject match within user's own tickets 259 267 if (!empty($subject)) { 260 268 $cache_key = 'nexlifydesk_user_subject_match_' . md5($user_id . $subject); … … 273 281 } 274 282 283 // Semantic similarity check within user's own tickets only 275 284 if (class_exists('TextAnalysis\\Comparisons\\CosineSimilarityComparison') && class_exists('TextAnalysis\\Tokenizers\\GeneralTokenizer')) { 276 $similarity_threshold = 0.12; 285 $similarity_threshold = 0.12; // Optimized threshold for better matching 277 286 $cache_key = 'nexlifydesk_user_tickets_semantic_' . $user_id; 278 287 $user_tickets = wp_cache_get($cache_key); … … 289 298 290 299 if (!empty($user_tickets)) { 300 // Manual keyword mapping for semantic matching 291 301 $keyword_map = array( 292 302 'profile' => 'account', 'dashboard' => 'account', 'user' => 'account', 'panel' => 'account', … … 323 333 ); 324 334 if ($similarity >= $similarity_threshold) { 325 return $ticket; 335 return $ticket; // Found duplicate within user's own tickets 326 336 } 327 337 } … … 329 339 } 330 340 341 // No duplicate found within user's tickets = create new ticket for this user 331 342 return null; 332 343 } 333 344 345 // ========================== 346 // B. UNREGISTERED USER LOGIC 347 // ========================== 334 348 if ($user_id == 0 && !empty($email)) { 335 349 // For unregistered users, we simply check if there's ANY existing ticket for this email 350 // Content matching is NOT required - we want to consolidate all communication by email 336 351 $cache_key = 'nexlifydesk_email_consolidation_' . md5($email); 337 352 $existing_ticket = wp_cache_get($cache_key); 338 353 339 354 if (false === $existing_ticket) { 355 // Find the most recent ticket for this email address (any status except closed) 340 356 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe. 341 357 $existing_ticket = $wpdb->get_row( $wpdb->prepare(// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table name is safe and required direct query. … … 350 366 } 351 367 352 return $existing_ticket; 368 return $existing_ticket; // Return existing ticket for email consolidation, or null to create new 353 369 } 354 370 371 // No valid user_id or email = create new ticket 355 372 return null; 356 373 } -
nexlifydesk/trunk/nexlifydesk.php
r3333095 r3333214 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. 35 * Version: 1.0.4 6 6 * Author URI: https://nexlifylabs.com 7 7 * Supported Versions: 6.2+ … … 55 55 define('NEXLIFYDESK_PLUGIN_DIR', plugin_dir_path(__FILE__)); 56 56 define('NEXLIFYDESK_PLUGIN_URL', plugin_dir_url(__FILE__)); 57 define('NEXLIFYDESK_VERSION', '1.0. 3');57 define('NEXLIFYDESK_VERSION', '1.0.4'); 58 58 define('NEXLIFYDESK_TABLE_PREFIX', 'nexlifydesk_'); 59 59 define('NEXLIFYDESK_CAP_VIEW_ALL_TICKETS', 'nexlifydesk_view_all_tickets'); … … 94 94 } 95 95 add_action('plugins_loaded', 'nexlifydesk_init'); 96 97 // Add plugin action links (donate button, etc.) 98 add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'nexlifydesk_plugin_action_links'); 99 add_filter('plugin_row_meta', 'nexlifydesk_plugin_row_meta', 10, 2); 100 101 function nexlifydesk_plugin_action_links($links) { 102 $donate_link = '<a href="https://paypal.me/devteejay" target="_blank" style="color: #e74c3c; font-weight: bold;">❤️ ' . __('Donate', 'nexlifydesk') . '</a>'; 103 array_unshift($links, $donate_link); 104 return $links; 105 } 106 107 function nexlifydesk_plugin_row_meta($links, $file) { 108 if (plugin_basename(__FILE__) === $file) { 109 $row_meta = array( 110 'support' => '<a href="' . esc_url('https://nexlifylabs.com/support') . '" target="_blank">' . __('Support', 'nexlifydesk') . '</a>', 111 'docs' => '<a href="' . esc_url('https://nexlifylabs.com/nexlifydesk-documentation/') . '" target="_blank">' . __('Documentation', 'nexlifydesk') . '</a>', 112 'donate' => '<a href="https://paypal.me/devteejay" target="_blank" style="color: #e74c3c; font-weight: bold;">❤️ ' . __('Support Development', 'nexlifydesk') . '</a>', 113 ); 114 return array_merge($links, $row_meta); 115 } 116 return $links; 117 } 118 119 // Add admin notice and action to update email templates for existing installations 96 120 add_action('admin_notices', 'nexlifydesk_show_template_update_notice'); 97 121 add_action('wp_ajax_nexlifydesk_update_email_templates', 'nexlifydesk_ajax_update_email_templates'); 122 add_action('wp_ajax_nexlifydesk_dismiss_template_notice', 'nexlifydesk_ajax_dismiss_template_notice'); 98 123 99 124 function nexlifydesk_show_template_update_notice() { … … 103 128 } 104 129 130 // Check if notice was dismissed 131 if (get_option('nexlifydesk_template_notice_dismissed', false)) { 132 return; 133 } 134 105 135 $screen = get_current_screen(); 106 136 if (!$screen || strpos($screen->id, 'nexlifydesk') === false) { … … 108 138 } 109 139 140 // Check if templates need updating (contain old placeholder format) 110 141 $existing_templates = get_option('nexlifydesk_email_templates', array()); 111 142 $needs_update = false; … … 127 158 <p> 128 159 <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 professionaltemplate files with better styling and functionality.', 'nexlifydesk'); ?>160 <?php esc_html_e('Your email templates are using the old format. Click below to update them to use the new template files with better styling and functionality.', 'nexlifydesk'); ?> 130 161 </p> 131 162 <p> … … 138 169 </p> 139 170 </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 171 <?php 165 172 } … … 174 181 } 175 182 183 // Clear existing templates to force use of template files 176 184 $templates = array( 177 185 'new_ticket' => '', … … 187 195 } 188 196 197 function nexlifydesk_ajax_dismiss_template_notice() { 198 // Verify nonce and permissions 199 if (!current_user_can('manage_options') || 200 !isset($_POST['nonce']) || 201 !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'nexlifydesk_dismiss_notice')) { 202 wp_die('Unauthorized'); 203 } 204 205 // Set the dismissal flag 206 update_option('nexlifydesk_template_notice_dismissed', true); 207 208 wp_send_json_success(array('message' => __('Notice dismissed successfully!', 'nexlifydesk'))); 209 } 210 189 211 // Activation function 190 212 function nexlifydesk_activate() { … … 201 223 } 202 224 225 // Load email templates from template files on activation 203 226 nexlifydesk_load_default_email_templates(); 204 227 } … … 208 231 */ 209 232 function nexlifydesk_load_default_email_templates() { 233 // Only load if no templates exist yet (fresh installation) 210 234 $existing_templates = get_option('nexlifydesk_email_templates', array()); 211 235 236 // Check if templates are empty or contain old placeholder-style templates 212 237 $needs_update = empty($existing_templates) || 213 238 (isset($existing_templates['new_reply']) && … … 215 240 216 241 if ($needs_update) { 242 // Set placeholders indicating that template files are being used 217 243 $templates = array( 218 244 'new_ticket' => '', … … 242 268 wp_clear_scheduled_hook('nexlifydesk_auto_close_tickets'); 243 269 270 // Note: Email templates are now handled by template files in templates/emails/ directory 244 271 } 245 272 … … 408 435 /** 409 436 * Freemius uninstall cleanup handler 437 * This is called by Freemius when the plugin is uninstalled through their system 410 438 */ 411 439 function nexlifydesk_freemius_uninstall_cleanup() { 440 // Include the uninstall script to handle cleanup 412 441 $uninstall_file = plugin_dir_path(__FILE__) . 'uninstall.php'; 413 442 if (file_exists($uninstall_file)) { 443 // Define the constant that uninstall.php expects 414 444 if (!defined('WP_UNINSTALL_PLUGIN')) { 415 445 define('WP_UNINSTALL_PLUGIN', true); … … 419 449 } 420 450 451 // Register the uninstall hook to use the uninstall.php file 421 452 register_uninstall_hook(__FILE__, 'nexlifydesk_freemius_uninstall_cleanup'); 422 453 … … 462 493 wp_send_json_error(__('You do not have permission to perform this action.', 'nexlifydesk')); 463 494 } 464 check_ajax_referer('nexlifydesk _purge_data');495 check_ajax_referer('nexlifydesk-ajax-nonce', '_ajax_nonce'); 465 496 global $wpdb; 466 497 $purged = array(); -
nexlifydesk/trunk/readme.txt
r3333105 r3333214 4 4 Requires at least: 6.2 5 5 Tested up to: 6.8 6 Stable tag: 1.0. 36 Stable tag: 1.0.4 7 7 License: GPLv2 or later 8 8 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 349 349 == Changelog == 350 350 351 = 1.0.4 = 352 - Enhanced email provider validation to prevent false success messages when credentials are not configured 353 - Fixed "Test Email Fetch" buttons now properly validate credentials before showing success/error status 354 - Improved AWS WorkMail connectivity across multiple server configurations and hosting environments 355 - Enhanced Google Workspace email integration with better error handling and credential validation 356 - Strengthened custom IMAP/POP3 email processing with comprehensive credential validation 357 - Improved JavaScript coding standards compliance by moving inline code to external files 358 - Enhanced AJAX handlers to check actual email fetch results instead of returning false positives 359 - Added proper return value validation for all email provider test functions 360 - Improved email piping reliability across different server setups and hosting providers 361 - Enhanced error messaging for better troubleshooting and configuration guidance 362 351 363 = 1.0.3 = 352 364 - Fixed AWS Test Connection functionality for WorkMail and SES integration … … 390 402 == Upgrade Notice == 391 403 404 = 1.0.4 = 405 Important update: Enhanced email provider validation prevents false success messages, improved AWS connectivity across multiple server configurations, and strengthened JavaScript coding standards compliance. 406 392 407 = 1.0.3 = 393 Major update: Fixes AWS connectivity issues, adds comprehensive system diagnostics, improves mobile UI responsiveness, introduces advanced duplicate detection with semantic text analysis, enhances production code quality, and provides comprehensive uninstall data removal options for better security and user control.408 Major update: Fixes AWS connectivity issues, adds system diagnostics, improves mobile UI, introduces semantic duplicate detection, and enhances uninstall data security. 394 409 395 410 = 1.0.2 = -
nexlifydesk/trunk/uninstall.php
r3333095 r3333214 23 23 global $wpdb; 24 24 25 // Get settings to check if data should be kept 25 26 $settings = get_option('nexlifydesk_settings', array()); 26 27 $keep_data = isset($settings['keep_data_on_uninstall']) ? (bool)$settings['keep_data_on_uninstall'] : true; 27 28 29 // Always remove scheduled hooks and transients (cleanup) 28 30 wp_clear_scheduled_hook('nexlifydesk_sla_check'); 29 31 wp_clear_scheduled_hook('nexlifydesk_auto_close_tickets'); … … 33 35 delete_transient('nexlifydesk_google_oauth_state'); 34 36 37 // Clear any cached data 35 38 wp_cache_flush(); 36 39 40 // If user wants to keep data, only do minimal cleanup 37 41 if ($keep_data) { 42 // Remove only version option but keep all user data 38 43 delete_option('nexlifydesk_db_version'); 39 44 return; 40 45 } 41 46 47 // User wants complete data removal - proceed with full cleanup 48 42 49 // Remove all plugin options and settings 43 50 $options_to_remove = array( … … 54 61 foreach ($options_to_remove as $option) { 55 62 delete_option($option); 63 // Also remove from network options if multisite 56 64 if (is_multisite()) { 57 65 delete_network_option(null, $option); … … 59 67 } 60 68 69 // Remove custom database tables 61 70 $table_names = array( 62 71 $wpdb->prefix . 'nexlifydesk_tickets', … … 71 80 } 72 81 82 // Remove custom user roles 73 83 remove_role('nexlifydesk_agent'); 74 84 remove_role('nexlifydesk_supervisor'); 75 85 86 // Remove custom capabilities from administrator role 76 87 $admin_role = get_role('administrator'); 77 88 if ($admin_role) { … … 89 100 } 90 101 102 // Remove uploaded files directory 91 103 $upload_dir = wp_upload_dir(); 92 104 $plugin_upload_dir = trailingslashit($upload_dir['basedir']) . 'nexlifydesk/'; … … 96 108 } 97 109 110 // Remove any user meta related to the plugin 98 111 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct deletion is required for uninstall cleanup 99 112 $wpdb->query($wpdb->prepare( … … 101 114 'nexlifydesk_%' 102 115 )); 103 116 // Optionally clear usermeta cache after deletion 104 117 wp_cache_flush(); 105 118 119 // Clean up any remaining cache entries and transients 106 120 $cache_keys_patterns = array( 107 121 'nexlifydesk_ticket_', … … 114 128 ); 115 129 130 // Attempt to clean up cache entries (this is a best-effort cleanup) 116 131 global $wp_object_cache; 117 132 if (isset($wp_object_cache->cache)) { … … 160 175 } 161 176 177 // Use WP_Filesystem for directory removal 162 178 global $wp_filesystem; 163 179 if (empty($wp_filesystem)) {
Note: See TracChangeset
for help on using the changeset viewer.