Compare commits

...

931 Commits

Author SHA1 Message Date
JediKev
8d38b06196 Add Release Notes for v1.18.4 2026-06-17 22:01:48 +00:00
JediKev
93c2d125e2 Merge branch '1.17.x' into develop 2026-06-17 22:00:04 +00:00
JediKev
a1114e5298 Add Release Notes for v1.17.8 2026-06-17 21:55:42 +00:00
JediKev
13122d1ec9 Merge pull request #6959 from JediKev/security/latest-patches-06-2026
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-06-17 16:48:29 -05:00
JediKev
6558b33225 security: FAQ Print
This addresses a report submitted by Michael Bommarito in regards to
external image handling in FAQ PDF exports.
2026-06-17 16:13:23 -05:00
JediKev
2a0c388fc8 security: User AJAX
This addresses a report submitted by LXY in regards to user profile and
preview access through staff AJAX.
2026-06-17 16:13:23 -05:00
JediKev
b4ede885cc security: Thread Entry Reuse
This addresses a report submitted by LXY in regards to thread entry reuse in
new-ticket workflows.
2026-06-17 16:13:23 -05:00
JediKev
119cefe516 security: Thread Actions
This addresses a report submitted by LXY in regards to thread action
authorization for ticket and task operations.
2026-06-17 16:13:23 -05:00
JediKev
5ff9795ef3 security: User Org AJAX
This addresses a report submitted by LXY in regards to user organization
updates through staff AJAX.
2026-06-17 16:13:23 -05:00
JediKev
5600f94962 security: Pwreset Token Expiration
This addresses a report submitted by Charton93 in regards to password reset
token expiration window handling.
2026-06-17 16:13:23 -05:00
JediKev
d457c146a5 security: Template Vars
This addresses a report submitted by luke.hebenstreit@outlook.com in regards
to template variable resolution for sensitive object values.
2026-06-17 16:13:23 -05:00
JediKev
b4cc0925c2 security: Login-Required Policy
This addresses a privately reported issue in regards to enforcing
login-required policy before ticket creation.
2026-06-17 16:13:23 -05:00
JediKev
eaebe0176e security: Note AJAX
This addresses a report submitted by sliao25@m.fudan.edu.cn in regards to
Note AJAX authorization.
2026-06-17 16:13:23 -05:00
JediKev
d590a9770d security: Ticket Field View
This addresses a report submitted by Juan Felipe Oz (@JF0x0r) in regards to
ticket field view authorization.
2026-06-17 16:13:23 -05:00
JediKev
596379768a security: Hash Comparison
This addresses a report submitted by Nozomu Sasaki (Paul) in regards to
session validation hash comparison.
2026-06-17 16:13:23 -05:00
JediKev
b535782a0a security: CSRF
This addresses a report submitted by Khaled M. Alshammri (@ik0z) in regards
to CSRF enforcement for state-changing request methods.
2026-06-17 16:13:23 -05:00
JediKev
580e1c87ec security: API Key Logging
This addresses a report submitted by Khaled M. Alshammri (@ik0z) in regards
to API key handling in system logs.
2026-06-17 16:13:23 -05:00
JediKev
ba6217a718 security: Staff Password Reset
This addresses a report submitted by Khaled M. Alshammri (@ik0z) in regards
to staff password reset response handling.
2026-06-17 16:13:23 -05:00
JediKev
7bbd8ab209 security: Request Method
This addresses a report submitted by Khaled M. Alshammri (@ik0z) in regards
to method override handling for state-changing routes.
2026-06-17 16:13:23 -05:00
JediKev
fd96bbabc9 security: Recipient Name
This addresses a report submitted by Khaled M. Alshammri (@ik0z) in regards
to collaborator and recipient name rendering.
2026-06-17 16:13:23 -05:00
JediKev
e52e01005b security: File Exception Handling
This addresses a report submitted by Khaled M. Alshammri (@ik0z) in regards
to file error handling and diagnostic output.
2026-06-17 16:13:23 -05:00
JediKev
98abb059b1 security: File URL Signatures
This addresses a report submitted by Khaled M. Alshammri (@ik0z) in regards
to file URL signature validation.
2026-06-17 16:13:23 -05:00
JediKev
078516ed03 security: Inline SVGs
This addresses a report submitted by Khaled M. Alshammri (@ik0z) in regards
to inline handling of SVG attachments.
2026-06-17 16:13:23 -05:00
JediKev
6eb6b98996 security: HTTP_REFERER Typo
This addresses a report submitted by Anindya Sankar Roy (fr3ak) in regards
to staff login referrer handling.
2026-06-17 16:13:23 -05:00
JediKev
feccb6a3a9 security: API Keys
This addresses a report submitted by Anindya Sankar Roy (fr3ak) in regards
to API key generation.
2026-06-17 16:13:23 -05:00
JediKev
1e39bf1cf7 security: Thread Entry Titles
This addresses hardening identified from a report submitted by Anindya
Sankar Roy (fr3ak) in regards to thread entry title rendering.
2026-06-17 16:13:23 -05:00
JediKev
c54a6ac79d security: Email Display Names
This addresses a report submitted by Anindya Sankar Roy (fr3ak) in regards
to rendering email display names in thread entries.
2026-06-17 16:13:23 -05:00
JediKev
5afdf5450f security: Bootstrap Tooltip
This addresses a report submitted by frgranier@deloitte.fr in regards to
tooltip behavior and data attributes in sanitized content.
2026-06-17 16:13:23 -05:00
JediKev
52c366f57b security: Full-Text Search
This addresses a report submitted by Huseyn (meKhatai) Gadashov in regards
to full-text search mode handling for user-crafted search terms.
2026-06-17 16:13:23 -05:00
JediKev
2570d69892 Add Release Notes for v1.18.3 2026-01-15 17:17:15 +00:00
JediKev
51f9249b8a Merge branch '1.17.x' into develop 2026-01-15 17:14:32 +00:00
JediKev
a18df93cdb Add Release Notes for v1.17.7 2026-01-15 17:12:24 +00:00
JediKev
c3819e4d21 Revert "issue: Guest Session TTL"
This reverts commit 416b548bd8.
2026-01-15 17:04:39 +00:00
JediKev
c59b067dbf Merge pull request #6911 from JediKev/security/latest-patches-01-2026
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 11:01:28 -06:00
JediKev
d51301dbcb Merge pull request #6910 from JediKev/htmlawed/upgrade-to-1.2.15
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 10:56:59 -06:00
JediKev
d832f24332 security: Pwreset Hardening
This mitigates a report submitted by Ruben Ferreira of CSA Cyber in
regards to password reset.
2026-01-15 10:44:00 -06:00
JediKev
0e599be671 Merge pull request #6909 from JediKev/issue/regex-or-operator-2
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 09:55:00 -06:00
JediKev
0ce88a7f0e Merge pull request #6908 from JediKev/rebase/pull-6034
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 09:54:39 -06:00
JediKev
6caaa640a2 Merge pull request #6878 from nicoletta-maia/1.17.x-banlist
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 09:54:18 -06:00
JediKev
914474f8cd Merge pull request #6906 from JediKev/rebase/pull-6861
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 09:54:00 -06:00
JediKev
a923946c75 Merge pull request #6904 from JediKev/rebase/pull-6872
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 09:53:41 -06:00
JediKev
ef6ed9a4d4 Merge pull request #6903 from JediKev/rebase/pull-6883
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 09:52:40 -06:00
JediKev
dbebe9d043 Merge pull request #6902 from JediKev/issue/empty-dept-error-on-topic-update
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 09:52:23 -06:00
JediKev
84b8baee1e Merge pull request #6901 from JediKev/issue/pgp-smime-mail-parsing
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 09:52:10 -06:00
JediKev
0d6cb83bab Merge pull request #6900 from JediKev/issue/microsoft-utf8-bom
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 09:51:55 -06:00
JediKev
fbe4bb99ef Merge pull request #6899 from JediKev/issue/trim-user-name
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 09:51:30 -06:00
JediKev
a0a7a3f3a8 Merge pull request #6898 from JediKev/issue/date-column-interval
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 09:51:10 -06:00
JediKev
0008042ce9 Merge pull request #6887 from JediKev/issue/pop-oauth2-error-regex
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 09:50:50 -06:00
JediKev
baf1e0a226 Merge pull request #6874 from JediKev/issue/svg-mime-type
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 09:50:28 -06:00
JediKev
ea7ecbb6e1 Merge pull request #6897 from JediKev/mpdf/upgrade-to-v8.2.7
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 09:50:00 -06:00
JediKev
36dee625f1 Merge pull request #6896 from JediKev/rebase/pull-6868
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 09:43:21 -06:00
JediKev
b46099431e Merge pull request #6865 from JediKev/mail/smtputf8-backwards-compatibility
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 09:42:52 -06:00
JediKev
2f17b02376 Merge pull request #6895 from JediKev/rebase/pull-6857
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 09:41:46 -06:00
JediKev
444fd2d013 Merge pull request #6894 from JediKev/issue/kazakh-flag-css
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 09:41:23 -06:00
JediKev
b53bd4316b Merge pull request #6893 from JediKev/rebase/pull-6851
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 09:40:58 -06:00
JediKev
331ca48d8d issue: Regex OR Operator
This addresses issue 6817 where when using an OR operator in the Regular
Expression validation textbox for a Short Answer field it gets removed
without warning. This is due to a malformed Regex statement within the
`Format::strip_emoticons()` function. This simply removes the brackets
from the OR comparison as brackets are only used for range comparisons
(eg. `/[\x{2310}-\x{231F}]/u`). Additionally, this replaces the
duplicated Copyright symbol with the "masculine" version of degree to
cover both types of degree symbols. There are more but these are the
most common.
2026-01-15 09:37:11 -06:00
JediKev
1552cfa3fa security: Request Handling
This tightens request handling in regards to a report submitted by
Khodor Barakat.
2026-01-15 09:29:21 -06:00
JediKev
9ce7d54ed0 Merge pull request #6808 from JediKev/issue/content-management-errors
Reviewed-By: JediKev <kevin@enhancesoft.com>
2026-01-15 08:58:52 -06:00
Thomas Oster
81ec256263 join cdata table earlier 2026-01-15 08:45:46 -06:00
JediKev
8db4d3778e security: HTML/PDF Hardening
This adds hardening in HTML sanitization and PDF rendering in regards to
reports submitted by Naveen Sunkavally at Horizon3.ai.
2026-01-14 15:03:35 -06:00
JediKev
6852e7197e issue: Ensure User Login Name
This ensures the actor's login name is of an acceptable format before
attempting authentication. This also updates `is_userid()` to accept a
new param of `$verify` that is passed through to `is_email()`.
2026-01-14 15:03:35 -06:00
JediKev
d1b634a9e7 security: mPDF Print
This addresses a report submitted by Naveen Sunkavally at Horizon3 in
regards to stream wrappers, and regex.
2026-01-14 15:03:08 -06:00
JediKev
877adf564a htmLawed: Upgrade To 1.2.15
This upgrades htmLawed to 1.2.15 (latest).
2026-01-14 14:18:25 -06:00
CKeckNorthIO
1425fe1e4f Update class.file.php
Added support for WEBP file format
2026-01-14 10:50:58 -06:00
Jamie Burchell
ca13d88887 Fixes for Implicitly marking parameters as nullable 2026-01-13 16:20:48 -06:00
ololukaszuk
ae4344189d Fix typo in 'Add New Instance' title
Fixed simple typo line 8
2026-01-13 16:18:06 -06:00
JediKev
f1ba934bd9 issue: Empty Dept Error On Topic Update
This addresses an issue where when updating a Help Topic and having a
disabled Department set for example, the system will return a generic
error stating to address the errors below; but none are shown. This is
due to a missing error div that displays the Department errors. This
simply adds the div so Department errors are visible.
2026-01-13 16:13:28 -06:00
JediKev
5074c0321f issue: Microsoft UTF-8 BOM
This addresses an issue where MS started adding UTF-8 BOM to beginning
of some content which causes parsing issues and errors. This removes the
UTF-8 BOM before using it in laminas to avoid parsing issues and errors.
2026-01-13 16:09:49 -06:00
JediKev
0fbafda14a issue: Trim User Name
This adds `trim()` around the user's name when saving and updating to
avoid errant spaces.
2026-01-13 16:08:41 -06:00
JediKev
c646c8cb86 security: Ensure Session ID
This ensures the session ID is of an acceptable format before sending to
`create()`.
2026-01-13 15:51:11 -06:00
JediKev
9aff6b0317 issue: PGP + S/MIME Mail Parsing
This addresses an issue with PGP and S/MIME mail parsing where sometimes
the content is completely empty or malformed. This is due to an old PGP
block that was added back to the code after a Pear upgrade. This removes
the block to allow PGP and S/MIME mail to be parsed correctly.
2026-01-13 15:41:29 -06:00
JediKev
1e38901e79 issue: Date Column Interval
This addresses an issue where having multiple Interval values for a
single Date column (like DueDate) throws a fatal error. This is due to
the system expecting the interval to be a string not an array. This adds
a cast to each place we use the interval to ensure it's a string to
avoid fatal errors.
2026-01-13 14:49:34 -06:00
JediKev
39cdd2b1fb mPDF: Upgrade To v8.2.7
This upgrades the entire mPDF/mPDF package to the latest (v8.2.7) to
support latest PHP, etc.
2026-01-13 14:29:41 -06:00
Jamie Burchell
573e849631 Fixes for Implicitly marking parameters as nullable 2026-01-13 13:41:50 -06:00
ntozier
8b6a0f05fd Update class.setup.php
/download/ page indicates that 1.18.2 needs 8.2+.
This updates this value to match
2026-01-13 13:35:13 -06:00
Sebastián Santisi
924380a970 Fixing Argentinian flag
On commit 3ee111122b the Argentinian flag was wrongly replaced by the Palestinian flag.
2026-01-13 13:29:44 -06:00
JediKev
624c71716c issue: Kazakh Flag CSS
This simply adds the missing Kazakh flag coordinates.
2026-01-13 13:25:13 -06:00
JediKev
10f38bd0cf issue: POP OAuth2 Error REGEX
This addresses an issue with POP OAuth2 error parsing where errors are
blank on screen. This is due to using `$matches[2]` (which doesn't
exist) vs `$matches[1]`. This updates the code to use the correct match
so that errors are correctly shown on screen.
2025-12-16 10:12:38 -06:00
Nicoletta Maia
a7f14bf241 Fix Banlist permission check 2025-11-11 13:37:41 +01:00
JediKev
a6309e2224 issue: SVG MIME Type
This addresses 6873 where SVG Image uploads are failing even with strict
MIME checking disabled. This is due to the `exif_imagetype()` call that
checks an image MIME type. This method does not work well with SVGs as
it only checks the first few bytes. This adds a specific SVG check using
`mime_content_type()` that can handle SVG MIME type checks.
2025-10-22 09:58:16 -05:00
JediKev
05b87199ac mail: SMTPUTF8 Backwards Compatibility
This adds backwards compatibility support for legacy mail servers that
don’t support SMTPUTF8 or ones that simply refuse to support it (get
with the times old man!). When the system adds an attachment or adds an
inline image to outbound mail we add the raw UTF-8 filename to the
`filename=` parameter contained within the `Content-Disposition` header.
This can be problematic with legacy mail servers or servers that refuse
to support SMTPUTF8 as non-ASCII characters contained within the
`filename=` parameter of the `Content-Disposition` header triggers
SMTPUTF8 requirement. If the server doesn’t support it but it’s required
then it bounces the mail, typically with an error message stating such.

This updates the `Message::addInlineImage()` and
`Message::addAttachment()` methods to not only provide an ASCII-safe
filename but to also utilize a new MIME Part `Rfc2231Part` (discussed
momentarily). To generate said ASCII-safe filenames for both inline
images and any attached files this adds a new method called
`Message::asciiFallback()` that converts raw UTF-8 filenames to ASCII as
well as removes any non-header safe characters. In addition, this adds a
new MIME utility namespace `osTicket\Mime` that houses a new MIME
utility called `Rfc2231Part`. This class extends `Laminas\Mime\Part` to
essentially modify the `Content-Disposition` header and tack on the
original UTF-8 filename using the `filename*=UTF-8''` parameter but
encoded with `rawurlencode()` to make it ASCII-safe.

In summary, this adds an ASCII-safe `filename=` parameter to the
`Content-Disposition` header to prevent legacy servers from triggering
SMTPUTF8 requirement whilst utilizing `Rfc2231Part` to additionally
provide an (ASCII-safe) UTF-8 `filename*=UTF-8''` parameter for modern
mail servers (preserving the original filename).
2025-10-14 14:49:04 -05:00
JediKev
2a4fe6ad4f issue: Content Management Errors
This addresses an issue where if you update the User Account
Confirmation Email template for example and it has validation errors the
system will throw a fatal error. This is due to a missing `$langs`
variable and a missing `$cfg` global. This adds the missing variable and
global back to avoid fatal errors.
2025-02-13 13:28:25 -06:00
JediKev
53339df0f5 Add Release Notes for v1.18.2 2025-02-04 09:57:30 -06:00
JediKev
b3d9e39a5d Merge branch '1.17.x' into develop 2025-02-04 09:46:36 -06:00
JediKev
3ff35ad534 Add Release Notes for v1.17.6 2025-02-04 09:36:50 -06:00
JediKev
06c893a5b1 Merge pull request #6802 from JediKev/security/latest-vulns-01-2025
Reviewed-By: JediKev <kevin@enhancesoft.com>
2025-02-04 09:34:49 -06:00
JediKev
dfb1d31fae Merge pull request #6801 from protich/1.17.x
Reviewed-By: JediKev <kevin@enhancesoft.com>
2025-02-04 08:44:37 -06:00
Peter Rotich
ec76a2032e Revert part of 0784359 commit
This commit reverts part of 0784359876
commit. Specifically the wrong / invalid test.
2025-02-04 01:17:43 -08:00
Peter Rotich
a8792c3db9 Merge pull request #6800 from JediKev/issue/plain-text-base64
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-03 23:01:57 +03:00
Peter Rotich
3a77257f50 Merge pull request #6799 from JediKev/issue/transfer-empty-comments-var
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-03 23:01:25 +03:00
Peter Rotich
8d63aa0cc0 Merge pull request #6798 from JediKev/field/variable-name-validation
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-03 23:00:36 +03:00
Peter Rotich
4858f93c2d Merge pull request #6797 from JediKev/issue/canned-response-access
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-03 22:59:42 +03:00
Peter Rotich
8b5b831430 Merge pull request #6796 from JediKev/issue/iframe-logins
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-03 22:58:35 +03:00
JediKev
c4b85fab1f Merge pull request #6766 from JediKev/patches/latest-11-18-2024
Reviewed-By: protich <peter@osticket.com>, JediKev <kevin@enhancesoft.com>
2025-02-03 13:17:42 -06:00
JediKev
245e755462 issue: Random Print
This removes a random `print $emails` from the `emailtest.php` page.
Looks like it was there by mistake all this time.
2025-02-03 13:16:56 -06:00
JediKev
416b548bd8 issue: Guest Session TTL
This addresses an issue where Guest User sessions would quickly timeout
even though the User session was configured for say 24 hours. This was
due to a chuck of code that used `min()` to get the minimum value from
the various TTL. This removes the `min()` part so it uses the `$ttl` if
available else defaults to `$maxlife`/`SESSION_TTL`.
2025-02-03 13:16:46 -06:00
JediKev
9f27584f97 Merge pull request #6724 from JediKev/issue/mimeDecode-eml-attachments
Reviewed-By: protich <peter@osticket.com>, JediKev <kevin@enhancesoft.com>
2025-02-03 13:15:39 -06:00
JediKev
7fc3d8c2ee issue: mimeDecode .eml Attachments
This reapplies a missing patch `af1c4a6` that added `message/rfc822`
attachment support.
2025-02-03 13:14:52 -06:00
JediKev
ab6672faa0 security: Syslog AJAX
This addresses a vulnerability reported by Muhammad Umer Adeem
(@UmerAdeemCheema) where normal Agents can potentially view system logs.
This also addresses additional access vulns with AJAX Content.
2025-02-03 13:09:10 -06:00
Peter Rotich
193f5fe007 Sanitize query to prevent SQL injection via parameter markers
The previous implementation used the regex `/:(\d+)/i` to remove a
single colon preceding digits. However, this approach did not handle
cases with multiple colons (e.g., "::1"), which could potentially allow
for injection attempts.

This commit updates the regex to `/:+(\d+)/` to match one or more colons
before the digits and replaces them with just the captured number. This
change ensures that all leading colons are removed, effectively
neutralizing any parameter marker injection while preserving the
intended numeric value.
2025-02-03 13:04:14 -06:00
JediKev
3a5da66bf6 issue: iFrame Logins
This addresses an issue with iFrame logins where the site simply would
refuse to honor cross site cookies. This adds a new check to see if the
system allows iFrames. If so, it will set the `samesite` for the cookie
to `None` which allows the cookie to be sent in all contexts. If the
site does not allow iFrames it will default to `Strict` for security
reasons.
2025-02-03 12:55:50 -06:00
JediKev
6ba1de123c Merge pull request #6795 from JediKev/i18n/crowdin-api-v2
Reviewed-By: protich <peter@osticket.com>, JediKev <kevin@enhancesoft.com>
2025-02-03 09:52:49 -06:00
JediKev
f57fc3f7c4 Merge pull request #6789 from JediKev/issue/excessive-fetching-errors
Reviewed-By: protich <peter@osticket.com>, JediKev <kevin@enhancesoft.com>
2025-02-03 09:52:09 -06:00
Peter Rotich
b1da571fed Merge pull request #6788 from JediKev/php/8.4-support
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-01 11:48:11 +03:00
Peter Rotich
6eb9bc2388 Merge branch '1.17.x' into php/8.4-support 2025-02-01 11:47:04 +03:00
Peter Rotich
6fb48cdec3 Merge pull request #6785 from JediKev/issue/referred-tickets-incorrect-queue-counts
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-01 11:42:45 +03:00
Peter Rotich
0176ef97d9 Merge pull request #6773 from remram44/fix-setting-force_https
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-01 11:32:52 +03:00
Peter Rotich
a8008b73f2 Merge pull request #6770 from JediKev/issue/dept-field-user-import
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-01 11:26:51 +03:00
Peter Rotich
28af87ef03 Merge pull request #6768 from JediKev/issue/user-lookup-umlaut
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-01 11:25:39 +03:00
Peter Rotich
3a519bd710 Merge pull request #6767 from JediKev/update/php-deprecations-and-warnings
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-01 11:24:22 +03:00
Peter Rotich
68fd23aaf3 Merge pull request #6765 from JediKev/issue/client-acl-staff-file-download
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-01 11:15:48 +03:00
Peter Rotich
3326138a52 Merge pull request #6763 from JediKev/queue/getTotal-incorrect-counts
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-01 11:14:06 +03:00
Peter Rotich
1b9fb37d05 Merge pull request #6762 from JediKev/issue/latest-sql-warnings
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-01 11:10:32 +03:00
Peter Rotich
587a97c2b9 Merge pull request #6761 from JediKev/update/composer-symfony-process
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-01 11:08:25 +03:00
Peter Rotich
c466519e48 Merge pull request #6753 from protich/feature/oauth2-strict-mode
OAuth2: Show Scopes and Enforce Strict Mode
2025-02-01 11:07:33 +03:00
Peter Rotich
61b0d6af09 Merge pull request #6751 from JediKev/api/duedate-and-other-errors
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-01 11:06:00 +03:00
Peter Rotich
ede9c01b38 Merge pull request #6742 from JediKev/user/list-import
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-01 11:03:35 +03:00
Peter Rotich
81d84b0c2f Merge pull request #6741 from JediKev/issue/image-annotation
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-01 11:02:30 +03:00
Peter Rotich
241c1af156 Merge pull request #6733 from JediKev/issue/multiselect-list-export
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-01 11:00:45 +03:00
Peter Rotich
8eb2dfae26 Merge pull request #6732 from JediKev/issue/safari-response-content-disposition
issue: Safari Response Content Disposition
2025-02-01 10:51:05 +03:00
Peter Rotich
9ce6e5c90a Merge pull request #6730 from JediKev/issue/email-remote-bk-name
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-01 10:48:44 +03:00
Peter Rotich
4b4fc10842 Merge pull request #6729 from dkrotich/feature/vscode_workspace
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-01 10:47:26 +03:00
Peter Rotich
0355664f6d Merge pull request #6727 from JediKev/php/iconv-recommendation
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-01 10:46:32 +03:00
Peter Rotich
ff331e9f8d Merge pull request #6725 from JediKev/mail/reapply-memory-optimization
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-01 10:45:20 +03:00
Peter Rotich
a9bd930217 Merge pull request #6722 from JediKev/issue/basic-search-selector
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-01 10:35:15 +03:00
Peter Rotich
ea1bdbbc27 Merge pull request #6721 from JediKev/issue/ticket-relations
Reviewed-By: Peter Rotich <peter@osticket.com>
2025-02-01 10:32:45 +03:00
JediKev
0ec6670d19 i18n: Crowdin API v2
This makes the changes needed to fully support Crowdin's latest API v2.
The main changes are the endpoints and the response types. Previously
the base URL was structured like `/api/project/{project}/{command}`
however now it is structured like
`/api/v2/projects/{project_id}/{command}`. Also previously the response
types were XML whereas now they are all JSON. There are other small
changes however their is a new function added called `getBranchId()`
that retrieves the given Branch's ID (to be used in URLs and the build
stage).
2025-01-30 10:26:41 -06:00
JediKev
216ded32ec issue: Plain-text Base64
This addresses an issue where Base64 encoding plain-text content leads
to raw encoded data being displayed in the mail client. This removes the
Base64 encoding from plain-text emails so the content displays properly.
To note, when you look at providers like Gmail, etc. they do not Base64
encode Plain-text nor HTML content.
2025-01-27 14:28:28 -06:00
JediKev
c546a16729 issue: Excessive Fetching Errors
This addresses issue 6647 where in certain scenarios/setups the system
throws the infamous `Excessive errors processing emails for` error. As
per the information from the referenced issue the main cause is a fatal
error where `json_decode()` is receiving an array instead of the
expected string. This adds a check on `$fieldClean[0]` to see if it's a
string and if so uses the `json_decode()` otherwise it uses itself.
2025-01-17 09:19:07 -06:00
JediKev
b930a68b70 issue: Canned Response Access
This addresses issue 6772 where you can access a Canned Response with a
Department set even if you don’t have the Premade Role Permission for
the Canned Response's Department. This is due to not checking access
before displaying the Canned Response. This adds a new function called
`staffCanAccess()` to class Canned that checks to see if the Canned
Response has a Department and if so ensures the Agent has a Role in that
Department and that the Role contains the Premade Permission. This adds
a new `if()` statement that calls `staffCanAccess()` if a Canned
Response is found.
2025-01-16 14:20:40 -06:00
JediKev
fb9a39ba3d update: README PHP Version
This updates the PHP version in the Requirements section from `8.1 - 8.3
(8.3 recommended)` to `8.1 - 8.4 (8.4 recommended)`.
2025-01-16 13:27:39 -06:00
JediKev
5e5a9ff5da deprecation: fgetcsv/fputcsv Default Escape Param
This addresses the deprecation of using the default value for the
`escape` parameter for both `fputcsv()` and `fgetcsv()`. Essentially,
you can no longer use the default value and instead have to explicitly
pass it in. This adds the needed defaults for `escape` as well as
`separator` and `enclosure` where needed to avoid warnings and errors.

References:
https://www.php.net/manual/en/migration84.deprecated.php#migration84.deprecated.standard
https://www.php.net/manual/en/function.fgetcsv.php#refsect1-function.fgetcsv-parameters
2025-01-16 12:55:45 -06:00
JediKev
b4938b9905 deprecation: E_STRICT Constant
This addresses the deprecation of the `E_STRICT` constant. The upstream
error level has been removed and now the constant is being deprecated.
Since we don't support PHP 5.x any longer and this is now
deprecated/removed this fully removes the last mention of the `E_STRICT`
constant. So long and farewell my friend.

Reference:
https://www.php.net/manual/en/errorfunc.constants.php#constant.e-strict
2025-01-16 12:37:58 -06:00
JediKev
a4c0f674ee deprecated: Implicitly Nullable Parameter
This addresses the deprecation of an implicitly nullable parameter
followed by a mandatory parameter in the old suggested format. This
updates the old suggested format of `function name(?string $var=null,
$var2)` to the newly suggested format of `function name(?string $var,
$var2)`. This is to avoid warnings but to also avoid errors in the
future.

Reference:
https://www.php.net/manual/en/migration84.deprecated.php#migration84.deprecated.core.implicitly-nullable-parameter
2025-01-16 12:35:11 -06:00
JediKev
b42cad6fe8 issue: Referred Tickets Incorrect Queue Counts
This addresses an issue where having a Ticket Referred to you and one of
your Departments increases the Queue count erroneously. In the
`getTicketsVisibility()` function the system adds criteria to find
Tickets referred to the Agent and then later adds separate criteria to
find Tickets referred to the Agent’s Department(s). The problem is the
two separate criteria will make the same Ticket appear twice in the
results. To fix the issue we simply need to make the results distinct
via `ticket_id`. Since `getTicketsVisibility()` is essentially
generating an array of criteria we can’t add the distinction there.
Plus, it could be used for things other than counts. So we move on to
`applyVisibility()` where we use the criteria and add the distinction
there. To note, this function is only used in `SavedQueue::counts()` and
`SavedQueue::getTotal()` which would only affect counts; just what we
want.
2025-01-13 13:25:32 -06:00
JediKev
4f137dc2bd queue: getTotal Incorrect Counts
This addresses an issue where when using `getTotal()` to get the total
counts for a Queue it mistakenly includes Children Tickets. This causes
a discrepancy between the number of Tickets shown within the Queue vs
the total shown for the count. Since we ignore Children Tickets within
the Queues themselves (due to them being merged into a Parent) we should
also ignore Children within the Queue Counts. This adds a new filter to
the `$query` in `getTotal()` that filters Tickets with `ticket_pid`
equal to `NULL` or Tickets with `flags` containing `Ticket::FLAG_LINKED`
(ie. it is a Link not Merge).
2025-01-07 14:30:47 -06:00
Remi Rampin
3805bbfdf3 Fix force_https auto-enabling on settings page 2024-12-09 14:12:32 -05:00
JediKev
5c2b6a5d2e issue: Department Field User Import
This addresses an issue where if you are importing Users via CSV,
one of the Users already exists in the system, their profile has a
Department Field with a current value set, and the CSV row for their
account has a Department Field value the system will fail with an error
of `Cannot access offset of type string on string`. This is due to
blindly assuming the `$value` is an array. This adds a check to see if
the value is an array, if so it will use the `$id` otherwise it defaults
to the `$value`.
2024-11-22 10:18:04 -06:00
JediKev
504b0bfb4f issue: User Lookup Umlaut
This addresses osTicket/osTicket-plugins#293 where attempting to lookup
and select a User from an external source like LDAP with an umlaut in
their name causes an error of "User not found". This is because the URL
is encoded with `htmlentities()` which makes the umlaut character
unrecognizable. This wraps the lookup string with `html_entity_decode()`
so that the umlaut is preserved and lookup succeeds.
2024-11-20 16:02:32 -06:00
JediKev
84c14ace46 update: PHP Deprecations & Warnings
This addresses 6574 and other deprecations and warnings encountered
during testing with `error_reporting` set to `E_ALL` and both
`display_errors` and `display_startup_errors` enabled. This includes
defining optional parameters before required parameters and function
return types not matching their parent.
2024-11-20 14:35:03 -06:00
JediKev
8255b2e721 issue: Client ACL Staff File Download
This addresses 6758 where enabling the ACL for Clients and attempting to
download a file as an Agent throws an Access Denied error. This adds a
check in the `check_acl` function to see if the backend is set to Client
Portal and if the current authenticated person is an Agent. If so, it
will skip the ACL check; otherwise it will enforce the check.
2024-11-18 14:26:13 -06:00
JediKev
59f2591809 update: Composer symfony/process
This is a rewrite of 6757 that updates the `symfony/process` package to
address a recent security advisory (11/2024). Both osTicket and
laminas-mail does not use this package in production (only in dev mode)
so this only updates the `require-dev` section of the main
`composer.json` file as well as the `composer.lock` file. This is so
that if anyone uses it in dev mode they will be using the latest patched
version of `symfony/process`.

Since this package is not in-use below are the specific instructions used to
update it without installing it:
```
cd include/laminas-mail/
composer require --dev --no-update symfony/process
vim composer.lock
  -- Update symfony/process version to latest (v6.4.14)
  -- Update the `reference` for the source and distribution to commit hash
  -- Update the `time`
composer update --no-install --lock
```
2024-11-14 10:39:55 -06:00
JediKev
ed87b25759 issue: Transfer Empty Comments Var
This addresses an issue where the `comments` variable is not replaced in
the Ticket Transfer Alert if there is no comment provided. This adds a
ternary operator to the `comments` variable assignment to see if there
is a value. If so it will use the value if not it will default to an
empty string. This is so the variable can be replaced with nothing which
is the intended end result.
2024-11-07 14:16:59 -06:00
JediKev
0724d1ada2 fields: Variable Name Validation
This addresses an issue where `-` characters are allowed in Field
Variable Names when they shouldn't be. Reason being is dashes are not
allowed in MySQL Column names. Since we use the Field Variable Name as
the Column name in the respective CDATA table we cannot allow the dashes
in the Variable Names.
2024-11-07 13:55:48 -06:00
JediKev
9bdfde5a4b issue: Latest SQL Warnings
This temporarily addresses 6661 where there are a few MariaDB warnings
after `sql_mode` changes. This only includes temporary changes via code
until we enforce defaults via SQL in the next major release. This
accomplishes the following:
- Ensures a `body` is set when creating Drafts
- Ensures a `logger` is set when adding system logs
- Ensures `updated` has a date-time set when creating a new ThreadEntry
- Ensures `staff_id`|`team_id` defaults to `0` if no object ID available
- Ensures the `lock_id` is set to `0` instead of `NULL`
2024-11-07 13:43:30 -06:00
Peter Rotich
390555db36 OAuth2: Add Resource Ownership Strict Mode Check
Enforce Strict Mode when updating / saving OAuth2 credentials. At the
moment we force the check at the plugin level which is not ideal and
will be removed in the near future.

In addition the change include setting the correct error tag so we can
show the error message in context.
2024-10-28 03:43:15 -07:00
Peter Rotich
a77cf535ed Token: Show Authorized Scopes
Show authorized scopes to Token Details tab, it's very useful when
debugging authentication issues.
2024-10-28 03:43:15 -07:00
JediKev
fec70c4ae9 user: List Import
This addresses an issue where attempting to import a User with a value
for a Custom List fails to import the list item. This is due to the
parsed (JSON) value being passed to `SelectionField::parse()` which
doesn't know how to parse it. This pull updates the `if()` statement
that checks if the given value is not an array and adds
`JsonDataParser::parse($value) ?:` before the `array($value)` to see if
we can successfully parse the string. If so, it will use the parsed
array. If not, it falls back to manually converting the value into an
array.
2024-08-13 13:23:11 -05:00
JediKev
aeeb285061 issue: Image Annotation
This addresses an issue where when annotating an image and sending it,
it disappears from the final reply. This is due to REGEX that attempts
to detect inline images and determines if it should strip them or not.
This REGEX statement is greedy which means it will find as many matches
as possible. This is a problem for image annotation as one of the
classes that gets added to the image is `data-orig-annotated-image-src`.
This gets picked up by the REGEX and as a result causes the image to be
rejected since it contains a file URL instead of the expected `cid`.
This pull simply changes the first matching group to "lazy" so that it
stops on the first match. This causes the real `src` value to be picked
up and parsed which contains the expected `cid`.
2024-08-12 14:39:48 -05:00
JediKev
f3bf8553cc issue: Multiselect List Export
This addresses an issue where attempting to export a multiselection List
Field with multiple selected items only shows the first selected item.
This is due to the Item IDs containing spaces (due to the earlier
`explode()` call) so the IDs do not match the available IDs. This adds a
`trim()` around the Item IDs so that they match appropriately.
2024-07-15 09:49:13 -05:00
JediKev
14814ae28a api: DueDate and Other Errors
This addresses an issue where adding a `duedate` to API request payloads
the system simply ignores it. This is due to an _old_ bit of code (1.7 -
circa 2013) that refuses the `duedate` from any source other than
`staff`. This updates the check from `strcasecmp()` to `in_array()` so
it can check for both `staff` and `api`. This will allow a `duedate` to
manually be set from Staff created Tickets as well as API created
Tickets.

In addition, this updates the `getRequestStructure()` function to change
the single `strcasecmp()` for `email` to a `switch {}`. This is so we
can check multiple types other than `email`. This adds the previous
`email` format as a case and adds two new cases for `json` and `xml`
formats. Within these two new cases `duedate`, `slaId`, and `staffId`
are added as available fields. This is so the system doesn’t throw an
`Unexpected data received in API request` error even though these fields
are valid and accepted with JSON and XML payloads.
2024-07-11 13:36:13 -05:00
JediKev
64a5df6881 issue: Safari Response Content Disposition
This addresses an issue where Safari is now refusing to load inline
images stored in S3. Upon investigation it was found the code had an old
check for Safari browser when generating the content disposition
filename string. If Safari was being used it left the filename alone as
"Safari and only Safari can handle the filename as is". Well this may be
true for Safari however S3 cannot handle this. This removes the Safari
specific check and forces even Safari to UTF8 encode the filename for
the content disposition string. This allows Safari to load inline
images from S3 again. Safari still loads other images, etc. just fine as
well.
2024-07-08 15:02:31 -05:00
JediKev
b54dd584b6 issue: Email Remote Backend Name
This addresses an issue reported by JensEB where the wrong remote
backend name was used in a strpos check. This updates the name from
`auth_bk` to the correct `mailbox_auth_bk`.
2024-07-02 11:42:03 -05:00
Dennis Rotich
0da2e0e0f2 Ignore VS Code Workspace Files
This commits adds *.code-workspace to .gitignore file
2024-06-21 17:47:48 +03:00
JediKev
33ecc3a0b9 php: iconv Recommendation
This updates the PHP Prerequisites to include the `iconv` extension.
Iconv methods are used for email parsing (in the pear/Mail_mimeDecode
package) as well as other places internally. This adds the extension to
the installer prerequisites as well as the system information page and
the `README.md`.
2024-06-20 15:05:55 -05:00
JediKev
ab76ffe804 issue: BC Removal
This removes a function called `Mail_mimeDecode` that was added in
`cbd78ac` (originally from pull #6681) for "backwards compatibility".
Upon investigation this is not needed; so it will be removed.
2024-06-18 13:21:33 -05:00
JediKev
eaaa64d9de mail: Reapply Memory Optimization
This reapplies the Memory Optimizations added with `5ae4fe6`, `c1d0b05`,
`2c00654`, `87277dd`, and `c57c22a`. This ensures larger emails are
parsed correctly and as optimal as possible. Without optimizations, the
system typically fails at parsing such large emails.
2024-06-18 13:13:42 -05:00
JediKev
74b8bd0e04 issue: Basic Search Selector
This addresses an issue where if you load the User Directory, click the
Organization tab, go back to the User Directory tab, and attempt a user
lookup via basic-search it attempts to hit the url `undefined?q=xxx`
which is obviously not valid and fails to return results. After
investigation it was found that when clicking through the tabs vs
directly loading them causes the Javascript/jQuery to load in reverse
sequence. This means core `scp/js/scp.js` code was taking precedence
over the inline-jQuery within the Users Staff Template. In most cases
this shouldn't matter however in this particular case the input selector
in the core `scp/js/scp.js` was looking for a generic
`input.basic-search` class. We use this class for all basic searches
however the ones for user lookup and organization lookpup respectively
have their own unique ID in addition that the inline-jQuery is supposed
to select. Since the core is loading after the inline in this case that
means the core takes precedence over the inline code causing wrong URL,
etc. This simply updates the core selector to have `:not([id])` so that
it doesn't try to match an iput with class of `input.basic-search` with
an ID. Tested and working with User/Org lookups and all other
basic-searches.
2024-06-14 10:47:08 -05:00
JediKev
9426b2dd82 issue: Ticket Relations
This addresses an issue where when viewing a Parent/Child Ticket that
also has Tasks and you first click the Tasks tab, then the Related
Tickets tab, then finally click a Related Ticket the browser redirects
to `/scp/ickets.php?=xxx` with URL Not Supported error. This is due to
reusing the class name `tasks` for the related Tickets table. This
changes the `tasks` class to `relations` so that it's unique.
2024-06-14 10:20:09 -05:00
JediKev
bb4c27dfe6 Merge pull request #6709 from JediKev/patches/latest-05-28-2024
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>
2024-06-13 11:49:39 -05:00
protich
d9fe3adad2 issue: Missing From Address
This addresses an issue where sometimes mail does not have a From Address
which causes issues in `Mail_Parse::parseAddressList()`. First, this adds a
check to see if the from is set in the headers, if not we return. Next, this
adds a check to see if the From header is malformed (ie. missing a bracket),
if so it adds one. Lastly, this continues to call `parseAddressList()` as
before however this time the From should always be set.
2024-06-13 11:49:06 -05:00
protich
854cf47ce5 issue: Quoted Table Name
This addresses an issue where the table name was not properly quoted. This
sends `true` to `quote()` to ensure the table name is properly quoted.
2024-06-13 11:49:06 -05:00
JediKev
36b4c94fb7 issue: Agent Permission Info
This addresses an issue where when exporting Agents and an Agent doesn't
have a Permission or Permission lookp fails the system throws a fatal error.
This is because we try to use `array_keys()` on `null`/`false`. This
updates class Role's `getInfo()` function to return an empty array if
the `perms` are not set/null/etc.
2024-06-13 11:47:15 -05:00
JediKev
4c57bf178e Merge pull request #6697 from JediKev/php/8.3-support
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>
2024-06-13 11:45:25 -05:00
JediKev
136d372ebc deprecated: assert_options()
This removes a call to a deprecated function `assert_options()` to match
PHP 8.3 changes.
2024-06-13 11:44:42 -05:00
JediKev
387a1c04dd update: System Prerequisites (PHP)
This updates the PHP version in the System Prerequisites (used in the
installer and upgrader) from 8.0 to 8.1. Since upstream PHP dropped support
for it we will too. RIP
2024-06-13 11:44:42 -05:00
JediKev
fea5e1e043 issue: Cannot Redeclare mb_str_pad()
This addresses an issue where in PHP 8.3 our declaration of `mb_str_pad()`
conflicts with the native `mb_str_pad()`. This adds a check to see if the
function already exists or not before declaring it.
2024-06-13 11:43:54 -05:00
JediKev
b38db3725a update: README PHP Version
This updates the PHP version in the Requirements section from `8.0 - 8.2
(8.2 recommended)` to `8.1 - 8.3 (8.3 recommended)`.
2024-06-13 11:43:54 -05:00
JediKev
a3931f4667 deprecation: get_class() No Parameters
In PHP 8.3 they have deprecated the use of `get_class()` with no parameters.
Instead you should use `get_class($this)`, `self::class`, or `__CLASS__`.
This updates all calls in the code to make it 8.3 compliant. These changes
are backwards compatible with PHP 8.1-8.2.
2024-06-13 11:43:54 -05:00
JediKev
a4e088b55e Merge pull request #6694 from JediKev/mailer/sendmail-failure
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>
2024-06-13 11:40:57 -05:00
JediKev
14e2057b0e mailer: sendmail() Failure
This addresses an issue where passing a full From string (eg. `"osTicket
Alerts" <user@noemail.com>`) as the `$from` parameter for
`Mailer::sendmail()` causes a fatal laminas-mail error. This is due to the
full string being reformatted after it's already been formatted. This
adds a check for `<` and if present we will not reformat; we will
instead use the full string as-is. This also updates one of the calls
and adds a space between the display name and the actual address.
2024-06-13 11:38:18 -05:00
JediKev
73bec0f89f Merge pull request #6720 from JediKev/changyy/develop
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>
2024-06-13 11:34:45 -05:00
Yuan-Yi Chang
cbd78ac8d6 Upgrade mimeDecode.php from v1.48 to v1.5.6 2016-08-28
Update the mimeDecode.php file, which was originally version 1.48 from 2006/12/03 and has been maintained up to now. Currently, we are directly using version 1.5.6. Adjustments were made to the syntax while retaining the original getHeader() function.

This is to fix #6680 issue.
2024-06-13 11:32:32 -05:00
JediKev
af9de90d0f Merge pull request #6719 from JediKev/ProbablePrime/fix/undelivered-mail-handling
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>
2024-06-13 11:29:24 -05:00
Richard Fox
0784359876 fix(email): fix case sensitivity in "Action" header retrieval
This fixes a casing issue in the handling of the Action header while
handling bounce notices from mail servers.

Prior to this change, no emails could be flagged as a bounce email
because the Upper-Case version of Action was used. This has been
lowercases to "action".

An accompanying unit test, tests that a bounce email is properly passed
and it's attributes are setup. I have redacted a sample DSN email as
much as I thought was necessary but am happy to re-review this fixture.

As all other header access appears to be lowercase, this just appears to
be a simple typo.

This fixes:
https://forum.osticket.com/d/104433-undelivered-mail-returned-to-sender-processing
too

A huge thank you to: Hikari Akimori from the Resonite community for
their sanity checks.
2024-06-13 11:27:41 -05:00
JediKev
45e29185c8 Merge pull request #6718 from JediKev/nicoletta-maia/issue/linked-icon-annotation
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>
2024-06-13 11:19:17 -05:00
Nicoletta Maia
6c096cce4b issue: Linked Icon annotation 2024-06-13 11:18:08 -05:00
JediKev
7cc70c8bd9 Merge pull request #6717 from JediKev/changyy/hotfix/FixGetRawEmail
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>
2024-06-13 11:15:16 -05:00
Yuan-Yi Chang
1eb71197bc Added trim() around headers in the getRawEmail method 2024-06-13 11:13:18 -05:00
Yuan-Yi Chang
c26d840c5f Fix raw mail content generation
Ensure proper splitting of mail headers and mail body using "\r\n\r\n" or "\n\n" as delimiters.
2024-06-13 11:13:02 -05:00
JediKev
c7b2968b24 Merge pull request #6716 from JediKev/upgrade/laminas-mail-2.25.1
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>, aydreeihn <adriane@enhancesoft.com>
2024-06-13 10:19:02 -05:00
JediKev
ef24ee9e75 Merge pull request #6707 from sergejostir/pr/fix-staff-mobile-field-class
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>, aydreeihn <adriane@enhancesoft.com>
2024-06-13 10:08:31 -05:00
JediKev
4993ba7de6 Merge pull request #6706 from JediKev/mysql/update-min-version
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>, aydreeihn <adriane@enhancesoft.com>
2024-06-13 10:06:53 -05:00
JediKev
498ea7f506 Merge pull request #6704 from JediKev/i18n/lang-url-param
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>, aydreeihn <adriane@enhancesoft.com>
2024-06-13 10:05:04 -05:00
JediKev
5f68020b07 Merge pull request #6703 from JediKev/php/update-prereqs
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>, aydreeihn <adriane@enhancesoft.com>
2024-06-13 10:03:36 -05:00
JediKev
2a6182bd36 Merge pull request #6702 from JediKev/issue/referred-closed-tickets
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>, aydreeihn <adriane@enhancesoft.com>
2024-06-13 10:00:50 -05:00
JediKev
3b1334d904 Merge pull request #6700 from JediKev/issue/task-last-respondent
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>, aydreeihn <adriane@enhancesoft.com>
2024-06-13 10:00:10 -05:00
JediKev
b5a3098c65 Merge pull request #6698 from JediKev/mysqli/multi-host-failure
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>, aydreeihn <adriane@enhancesoft.com>
2024-06-13 09:59:01 -05:00
JediKev
15b269dd45 Merge pull request #6699 from JediKev/i18n/schedules-getDays
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>, aydreeihn <adriane@enhancesoft.com>
2024-06-13 09:54:00 -05:00
JediKev
f131c54fe8 Merge pull request #6687 from JediKev/issue/position-styling-again
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>, aydreeihn <adriane@enhancesoft.com>
2024-06-13 09:31:00 -05:00
JediKev
1affaceb25 Merge pull request #6684 from amiraiman/hotfix/fix-square-brackets-when-print
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>, aydreeihn <adriane@enhancesoft.com>
2024-06-13 09:29:08 -05:00
JediKev
9a224d6dc0 Merge pull request #6648 from JediKev/issue/checkbox-template-variable
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>, aydreeihn <adriane@enhancesoft.com>
2024-06-13 09:13:41 -05:00
JediKev
8a01025266 Merge pull request #6630 from JediKev/i18n/tasks-dept-id-missing
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>, aydreeihn <adriane@enhancesoft.com>
2024-06-13 09:12:29 -05:00
JediKev
dfcd14df1b Merge pull request #6676 from sergejostir/patch-1
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>, aydreeihn <adriane@enhancesoft.com>
2024-06-13 09:04:25 -05:00
JediKev
d788786b2b Merge pull request #6675 from FlapJac-k/1.17.x
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>, aydreeihn <adriane@enhancesoft.com>
2024-06-13 09:02:57 -05:00
JediKev
9bef49b672 Merge pull request #6496 from nicorac/develop
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>, aydreeihn <adriane@enhancesoft.com>
2024-06-13 09:01:40 -05:00
JediKev
f658268d1f upgrade: Laminas-Mail (2.25.1)
This updates the laminas-mail package from 2.23.0 to 2.25.1 to fully support PHP 8.3.
2024-06-12 14:04:51 -05:00
JediKev
939a278cca todo: Remove Custom prepareHeaders()
This removes the custom `prepareHeaders()` method from class Sendmail as
this functionality was added upstream which is now available with the
latest update.
2024-06-12 13:56:43 -05:00
JediKev
c0a74162f0 update: Laminas-Mail
This is a rewrite of 6312 and updates the laminas-mail package from
2.10.1 to 2.23.0. This also remove unused dev dependencies.
2024-06-12 13:56:20 -05:00
JediKev
b843fb1503 issue: Sanitization Updates
This adds a couple of new sanitizations including:
- Replacing unicode spaces with normal spaces
- Stripping the Degrees and Copyright unicode symbols
2024-06-11 17:33:15 +00:00
protich
2d65cb7723 api: XML Array Key
This addresses an issue where if the `ticket` array key is missing in the
`$current` array it will throw a fatal error. This adds an `isset()` check
to ensure the array key exists and is set before using it.
2024-05-28 20:37:04 +00:00
Sergej Oštir
f8455c82ca Fix staff mobile field class 2024-05-25 22:55:19 +02:00
Sergej Oštir
6df6cd984d Fix ajax path for AddRemoteCollaborator 2024-05-25 20:10:45 +02:00
JediKev
317ab96700 mysql: Update Minimum Version
This updates the minimum required MySQL Version from 5.0 to 5.5. We've
stated "5.5 or better" for a while but never actually enforced it in our
installer/upgrader.
2024-05-22 21:20:17 +00:00
JediKev
fece6ff727 i18n: Lang URL Param
This addresses issue 6573 where the `lang` URL param is not sanity checked
and throws a fatal error if it contains anything but a string. This forces
the `lang` param to a string when passing to `strtolower()` to avoid fatal
errors. The `isLanguageInstalled()` function will still behave the same way
as before by returning `false` if passed an invalid `lang` code -or- if the
`lang` code is valid just doesn't exist in the available languages.
2024-05-22 20:24:58 +00:00
JediKev
d331a44c2d php: Update Prerequisites
This updates the PHP Prerequisites to include the `ctype` extension needed
for email fetching as part of the `laminas-mail` package. This adds the
extension to the installer prerequisites as well as the system information
page and the `README.md`. Lastly, this updates the installer prerequisites
wording and structure so they all match and look streamlined.
2024-05-21 19:32:35 +00:00
JediKev
a7b0711d65 issue: Referred Closed Tickets
This addresses an issue where Agents cannot see Referred Tickets in their
Closed Queues. This is due to the `Staff::getTicketsVisibility()` function
blindly using the `open` state instead of looking for `closed` as well. This
updates the function to include the `closed` state in the status condition.
This ensures the referred Tickets are shown in every Queue.
2024-05-20 18:43:42 +00:00
JediKev
f2facda3e6 i18n: Schedules getDays()
This addresses an issue where using a non-english language breaks the Ticket
view page for Tickets with no Due Date, SLA with grace period longer than 41
hours, and the SLA is attached to a Schedule with working days. This is due
to mistakenly translating the day names in `getDays()` function at times we
shouldn't be. PHP only likes English day names during actual date
calculations.  This adds a new method called `getTranslatedDays()` that will
return the translated day names when needed (it also caches the
translations). In addition this updates the existing `getDays()` to strictly
return the native, untranslated day names. Lastly, this updates the calls to
`getDays()` that need to be translated to `getTranslatedDays()`.
2024-05-16 14:58:07 +00:00
JediKev
5277c7ab46 issue: Task Last Respondent
This addresses an issue where `Task::getLastRespondent()` returns the wrong
Agent leading to the wrong people receiving alerts, etc. This updates the
`Task::getLastRespondent()` function to make it limit the last respondent
search to the Task's own Thread Entries rather than searching through all
entries.
2024-05-15 17:57:15 +00:00
JediKev
0a8475fbba mysqli: Multi-Host Failure
This is a rewrite of #6641. This addresses an issue where if PHP has MySQLi
Error Reporting enabled (enabled by default starting with PHP 8.1), there
are multiple configured database hosts, and one host fails to connect the
system throws a fatal error. We previously attempted to silence fatal errors
with `@` suppression however STRICT MySQLi Error Reporting bypasses this and
still throws an error. This updates the `db_connect()` and
`db_select_database()` calls in `bootstrap.php` and wraps them with a
try/catch. If either `db_connect()` or `db_select_database()` throws a fatal
exception we will catch it and set `$ferror` to the exception message. Doing
so ensures the system attempts to connect to all configured hosts without
exiting and only croaks when no hosts are reachable or the database cannot
be selected.
2024-05-15 17:35:20 +00:00
JediKev
7f03a64bd1 issue: Position Styling.. Again
This addresses an issue where the current REGEX for the "position" styling
attribute is causing issues when emails contain legit text like "position:
100 degrees". This updates the REGEX to only look for one of the possible
values instead of looking for seemingly anything.
2024-04-22 19:01:55 +00:00
Amir Aiman
2584403447 Properly set mPdf instance autoScriptToLang property to true
Based on mPdf migration guide: https://mpdf.github.io/installation-setup/installation-v6-x.html
2024-04-14 17:07:11 +08:00
Amir Aiman
872a649210 add mpdf init options to auto convert non-latin characters 2024-04-13 17:22:11 +08:00
Eslam Mohamed
cd4717a10b Fixed mailfetch error message 2024-03-12 11:37:15 +02:00
JediKev
09de958730 issue: Checkbox Template Variable
This addresses an issue where when using a Checkbox on an Email Template the
variable is replaced with `1` if checked and doesn't get replaced at all if
unchecked. This is due to the `BooleanField` class not having an `asVar()`
method so it uses the `FormField::asVar()` method which calls `to_php()`.
This method returns an integer for BooleanField which is not what we want.
This adds a new method called `asVar()` to the `BooleanField` class that
calls `toString()` which will return `Yes` if checked and `No` if unchecked.
2023-12-06 22:14:39 +00:00
JediKev
00bc6b1bf3 i18n: Tasks Department ID Missing
This addresses a small issue where the Department Name is not translated
on the Task Queues. This is due to the Task values missing the
Department ID used to generate the object_hash for the translation
table. This adds the `dept_id` to the Task Values so the translations
are properly found.
2023-10-30 09:22:46 -05:00
JediKev
4689926b2d cli: Class Not Found
This addresses a small issue where the osTicket class could not be found in
manage.php. This moves the boostrap.php include above the osTicket call to
ensure the class is loaded before use.
2023-10-25 15:45:33 +00:00
JediKev
03755768d9 Add Release Notes for v1.18.1 2023-10-25 15:34:06 +00:00
JediKev
0f08e5b0b7 Merge branch '1.17.x' into develop 2023-10-25 15:26:17 +00:00
JediKev
7c2003629d Add Release Notes for v1.17.5 2023-10-25 15:19:29 +00:00
JediKev
f878766863 Merge pull request #6627 from JediKev/format/viewable-images-bug
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2023-10-25 10:06:35 -05:00
JediKev
2a412883ae format: Viewable Images Bug
This addresses a small bug where `Format::viewableImages()` was using
unencoded quotes instead of encoded quotes to replace the `<img src=`
matches. This means when the content is added to a textarea’s
`draft-data-original` attribute the unencoded quotes break the attribute
and displays raw HTML content. This moves the `Format::htmlchars()` call
after the `preg_replace_callback()` so the quotes can be properly
encoded (if needed).
2023-10-25 10:04:01 -05:00
JediKev
4919051817 Merge pull request #6626 from JediKev/issue/user-account-creation
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2023-10-25 10:03:19 -05:00
JediKev
777e6f0b74 issue: User Account Creation
This addresses an issue with User account creation not rotating the
session properly.
2023-10-25 10:02:49 -05:00
JediKev
7c00e398a6 Merge pull request #6625 from JediKev/security/latest-vulns-09-2023
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2023-10-25 10:02:01 -05:00
JediKev
37cf8350ea security: User Ticket Creation
This addresses an issue with Topic ID param abuse on User Ticket
Creation via portal.
2023-10-25 09:59:53 -05:00
JediKev
c4ad48de5d security: Inline Image
This addresses an issue where we needed to harden inline image regex and
localize the inline images before sanitizing.
2023-10-25 09:59:53 -05:00
JediKev
88a87a3346 security: XSS List Properties
This mitigates a vulnerability reported by Vaibhav Kumar Srivastava
where XSS is possible via List Properties.
2023-10-25 09:59:53 -05:00
JediKev
04f4e61110 security: FAQ Manage Permissions
This addresses a vulnerability reported by @lujiefsi where Agents can
potentially bypass the FAQ restrictions.
2023-10-25 09:59:53 -05:00
JediKev
45d70301f6 Merge pull request #6624 from JediKev/issue/csp-headers
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2023-10-25 09:58:53 -05:00
JediKev
6228f640ac issue: CSP Headers
This adds a few more CSP Headers (`script-src`, `object-src`) to better
secure the helpdesk.
2023-10-25 09:57:58 -05:00
JediKev
d4e1c22d4f Merge pull request #6623 from JediKev/issue/canned-resp-inline-images
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2023-10-25 09:57:02 -05:00
JediKev
a503c1600e issue: Canned Response Inline Images
This addresses an issue where saving edits made to a canned response with two
or more inline images either breaks the page or causes 500 error. This is due
to incorrectly formatted array of IDs for the inline images causing further
issues with `$att->file->getId()` on render.

This adds a check in `keepOnlyFileIds()` to see if the `$value` from the array
of IDs is an array itself. If so, we will break apart the `$value` array into
`$id` (File ID) => `$value` (File Name) to match the expected format. If the
`$value` is not an array, it will use the existing format (as before). This is
important as we have two different array formats for the File IDs depending on
what method is called to generate them. We need to be able to account for both
formats. In addition, this adds the file name to the `$attachment` array in
`getAttachmentIds()` so we can save the new attachment name (if different).

Lastly, this fixes an issue where the Canned Response attachment upload field
was blindly including all attachments rather than excluding the inline images.
2023-10-25 09:55:48 -05:00
Peter Rotich
f489dde1b4 Merge pull request #6622 from protich/issue/lint
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-10-25 09:03:33 -05:00
Peter Rotich
895c7236b5 stubs: Add stubs to pass lint test 2023-10-24 19:12:56 -07:00
Peter Rotich
9be940a835 Merge pull request #6548 from fjf2002/patch-1
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-10-24 21:02:28 -05:00
Peter Rotich
6dc77c8cdb Merge pull request #6549 from fjf2002/patch-2
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-10-24 20:59:57 -05:00
Peter Rotich
9d0ba875a3 Merge pull request #6578 from JediKev/i18n/flags-position-and-codes
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-10-24 20:45:58 -05:00
Peter Rotich
842bc7e873 Merge pull request #6577 from JediKev/i18n/update-signing
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-10-24 20:45:16 -05:00
Peter Rotich
e1340dc70c Merge pull request #6566 from JediKev/issue/putenv-disabled
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-10-24 20:44:17 -05:00
Peter Rotich
177ece7c2d Merge pull request #6591 from protich/issue/php-cli
CLI: Make sure manage.php util can be executed via php-cgi CLI
2023-10-24 20:42:18 -05:00
Peter Rotich
ee7e6e5b84 Merge pull request #6609 from JediKev/mailer/undefined-smtpaccount-getname
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-10-24 20:41:48 -05:00
Peter Rotich
a37de02a16 Merge pull request #6612 from JediKev/faq/category-notes-images
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-10-24 20:40:50 -05:00
Peter Rotich
1a3326377e Merge pull request #6614 from JediKev/patches/latest-10-03-2023
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-10-24 20:40:09 -05:00
Peter Rotich
2b5fa48590 Merge pull request #6610 from JediKev/issue/systemsessionhandler
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-10-24 10:46:17 -05:00
Peter Rotich
3fd1c04fe1 Merge pull request #6611 from JediKev/issue/status-list-overflow
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-10-24 10:44:27 -05:00
Peter Rotich
c7657ae536 Merge pull request #6620 from JediKev/issue/choice-selection-field-searches
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-10-24 10:43:23 -05:00
Peter Rotich
736f3e8772 Merge pull request #6618 from JediKev/mail/novalidatecert
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-10-24 10:27:29 -05:00
Peter Rotich
89edac8cc8 Merge pull request #6616 from fillorkill/develop
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-10-24 09:02:48 -05:00
JediKev
00e22faddc issue: Choice/Selection Field Searches
This addresses a small issue where when searching for a Choice/Selection
Field value using `includes` criteria it sometimes shows records it's
not supposed to. This is due to the REGEX used to find the value. Let's
say you have two different List Options:
1. ID = 2  | Value = List Item 2
2. ID = 22 | Value = List Item 22

Now, let’s say you make a search criteria of `List => Includes => List
Item 2`. The REGEX we use is `\"?2(\"|,|$)` which will essentially match
`*2` instead of just `2`. This means it will include Tickets with with
values of `List Item 22` as it contains a `2` as well.

This updates the REGEX to add `(?<![0-9])` before the ID/Key to ensure
it does not match numbers that contain this ID/Key. Instead it will have
to be an exact match of the ID/Key.
2023-10-20 10:42:12 -05:00
JediKev
d3c140ce35 mail: NoValidateCert
This adds code to disable certificate validation for IMAP/POP3 and SMTP.
2023-10-16 09:39:01 -05:00
Jing Zhang
d4aeada1c5 Update raphael-min.js to 2.3.0
2.1.0 invokes unsafe-eval error and dashboard graph does not display if CSP turned on. This fixes the issue.
2023-10-11 11:59:37 +08:00
JediKev
1775ce7582 schedules: No Holidays
This fixes an issue where sometimes the holidays for schedules can be
empty. This adds checks to see if holidays is empty/not set in the POST
and if so we try to get the Schedule's existing Holidays and if they
don't exist we default to an empty array. This also adds a further check
to see if `$holidays` is set if not then we default to an empty array.
2023-10-04 16:37:51 -05:00
JediKev
070eefcba2 org: Domain Input Length
This is a small patch that increases the Auto Add Members From input max
length from 60 to 256 to allow more domains.
2023-10-04 16:36:16 -05:00
JediKev
64b8c81d09 mysqli: Retry Codes
This adds the concept of Retry Codes so we can attempt to reconnect to
MySQL in the event of certain error codes (2006 and 1213).
2006 - MySQL server has gone away
1213 - Deadlock found when trying to get lock; try restarting
transaction
2023-10-04 16:31:41 -05:00
JediKev
bd034712a5 logs: Email Loop Info
This adds code to append the offending mail headers when an Email Loop
is detected. This will provide more information on what email exactly is
causing the loop.
2023-10-04 16:30:13 -05:00
JediKev
4475e03ebd search: Not Includes No Value
This addresses an issue where sometimes an Agent will add Search
criteria with `!includes` but don't add a value. This adds a check to
make sure there is a value before attempting the search. If there is no
value we simply return without executing the search to avoid errors.
2023-10-04 16:28:22 -05:00
JediKev
a8d42a9de2 api: Data Array
This addresses an issue where sometimes the returned `$data` from the
parser can be a type other than an array. We expect the data to be an
array so to avoid issues/errors this adds a check to ensure `$data` is
an array before we use it.
2023-10-04 16:25:33 -05:00
JediKev
48e8501dd0 api: Check Attachments
This addresses an issue where sometimes attachments can sometimes not be
set in the `$data` array. This adds a check to ensure attachments key
isset in the `$data` array before attempting to use it.
2023-10-04 16:23:32 -05:00
JediKev
65e12297fa search: Store Saved Search Count
This adds code to store a Saved Search count on creation.
2023-10-04 16:21:29 -05:00
JediKev
ccca0f5954 faq: Category Notes Images
This addresses an issue where inline images were being broken in FAQ
Category Notes. This ensures we call `Format::viewableImages()` on both
the Category Description and Notes to ensure they are sanitized and
properly formatted.
2023-09-29 15:17:02 -05:00
JediKev
1deee342a5 issue: Status List Overflow
This addresses an issue where having more than 20 ticket statuses run
off the page when selecting ticket statuses from Changes Status action
button. This adds a check for how many status you have an if more than
20 we add styling so the list is scrollable.
2023-09-29 14:57:17 -05:00
JediKev
7e17daa68b issue: SystemSessionHandler
This addresses an issue where if using an old database such as v1.6 ST
and attempting to upgrade you get a fatal error of
`osTicket\Session\SystemSessionHandler: must implement
osTicket\Session\AbstractSessionhandler`. This is because the
`SystemSessionHandler` class does not extend `AbstractSessionHandler`
(rightfully so). We seemingly force `AbstractSessionHandler` without
taking the `SystemSessionHandler` into account. This adds a check to see
if using `SystemSessionHandler` and if so sets `$impl` as such; allowing
the check to pass and the session to be initiated. This also updates the
capitalization of the `AbstractSessionhandler` class in two places.
2023-09-29 14:48:12 -05:00
JediKev
560a4f1d63 mailer: Undefined SmtpAccount::getName()
This addresses an issue where when attempting to send mail from an
SmtpAccount we attempt to get the From name with `getName()` which
throws a fatal error. This updates class EmailAccount and adds a new
`getName()` method that gets the From name from the associated email
relation. This provides the needed name and prevents fatal error from
being thrown. In addition, this adds a further fail-safe where if no
name is returned we will use the email address as the name.
2023-09-29 14:07:42 -05:00
Peter Rotich
0caf58648f CLI: Make sure manage util can be executed via CLI
This commit changes manage.php util to use unified cli detection
routine. This ensures that cgi based CLI binaries execute the util.
2023-07-31 13:42:17 -07:00
JediKev
fcdeb542ab Merge pull request #6579 from JediKev/update/php-requirements-1.18.x
Reviewed-By: JediKev <kevin@enhancesoft.com>
2023-07-18 13:34:57 -05:00
JediKev
1c0c670b11 update: PHP Requirements 1.18.x
This updates the PHP requirements to match 1.18.x requirements.
2023-07-18 13:30:29 -05:00
JediKev
3079040e6b Merge branch '1.17.x' into develop 2023-07-18 13:25:25 -05:00
JediKev
3dd2ed35f3 Merge pull request #6562 from JediKev/update/readme-php-and-laminas
Reviewed-By: JediKev <kevin@enhancesoft.com>
2023-07-18 13:24:31 -05:00
Tinnitus97
30a4a6e900 update: External Project Links
This updates the links to all external projects used in osTicket.
2023-07-18 13:00:34 -05:00
JediKev
45f5b5b4c5 i18n: Flags Codes
This updates the `include/class.i18n.php` file to fix issues with lang
codes. When building packs for languages with codes that have locales
(eg. `sv-SE`) the pack name use underscores, not dashes. This updates
the places we lookup by lang code and replaces `-` with `_` so they are
found correctly.
2023-07-13 15:44:38 -05:00
JediKev
3ee111122b i18n: Flags Position
This updates the `css/flags.css` to correct the positioning of some
flags. This also adds `fil` code to the `include/i18n/langs.php` file to
account for Filipino language.
2023-07-13 15:42:48 -05:00
JediKev
776c0f2791 i18n: Build MANIFEST.php
This updates the code to fix issues with the `Phrases-Version`,
`Language`, `Last-Revision`, and `Version` not being added properly in
the `MANIFEST.php` file on build. This is due to importing the wrong
class as well as incorrect key names. This updates the include_once
statement as well as the key names.
2023-07-13 15:34:36 -05:00
JediKev
9870627423 i18n: Update Signing
This updates the i18n signing code to reflect the new aws-sdk-php v3
standards. This also rearranges the code to look a little nicer.
2023-07-10 15:19:07 -05:00
JediKev
88294e2d87 update: Upgrade Section & Extensions
This is a partial rewrite of 6365. This moves the Upgrade section to the
`UPGRADING.txt` file and updates the verbiage to link to the Upgrade
Guide in the docs as well as the `UPGRADING.txt` file. In addition this
updates the recommended extensions for PHP to reflect what's shown in
Admin Panel > Dashboard > Information. Lastly, this updates various
links in the `README.md` and `UPGRADING.txt` files.
2023-06-13 16:32:37 -05:00
JediKev
555c4a74b6 update: Crowdin Localization Badge
It's time we updated from the static localization badege to a more
real-time badge.
2023-06-13 16:02:57 -05:00
JediKev
3c0d5f477a issue: putenv() Disabled
This addresses issue 6564 where `putenv()` may be disabled on some
hosts. This adds a check to make sure the function exists before calling
it to avoid fatal errors.
2023-06-13 15:46:32 -05:00
JediKev
4baf9dc859 update: README.md PHP & Laminas
This updates the `README.md` file to include the newly supported PHP 8.2
as well as an update to add more verbiage to supported MySQL versions.
This also adds credit to `laminas-mail` in the OS projects listings.
2023-06-12 14:52:11 -05:00
JediKev
8f6c2ec794 Merge branch '1.18.x' into develop 2023-06-12 18:15:07 +00:00
JediKev
724de4563e Add Release Notes for v1.18 2023-06-12 18:11:05 +00:00
JediKev
3eac42e55f update: MAJOR_VERSION
This updates MAJOR_VERSION from 1.17 to 1.18.
2023-06-12 18:08:57 +00:00
JediKev
104b4bfc70 Add Release Notes for v1.18 2023-06-12 17:58:18 +00:00
JediKev
ee39fd796e Merge branch 'develop' into 1.18.x 2023-06-12 17:45:36 +00:00
JediKev
11b298680c Merge branch '1.17.x' into develop 2023-06-12 17:45:10 +00:00
JediKev
ea462cb1e6 Add Release Notes for v1.17.4 2023-06-12 17:40:33 +00:00
JediKev
39f1a4f480 Merge branch '1.17.x-develop' into 1.17.x 2023-06-12 17:22:55 +00:00
JediKev
7d60207bd0 Merge branch '1.17.x-1.16.x' into 1.17.x 2023-06-12 17:22:45 +00:00
JediKev
a22ff3b766 issue: iFrame Domain Bypass
This mitigates a vulnerability repoted by @endansdto where iFrame domain
can be bypassed due to bad regex. This updates the regex to ensure
proper filtering and checks.
2023-06-12 17:22:35 +00:00
JediKev
ca30637645 issue: XSS Image Caption
This mitigates a vulnerability reported by @cupc4k3 where XSS is possible
via image caption. This removes the possibilty for captions altogether.
2023-06-12 17:22:35 +00:00
JediKev
cecda2f2c0 issue: Multiple File Upload
This mitigates a vulnerability reported by @ManuelHu where files can
bypass checks if sent in the same request behind a valid file. This also
adds a space to fix an annoyance in the JS errors.
2023-06-12 17:22:35 +00:00
JediKev
d1f8cd0a6c issue: Draft & Inline Images
This is a two-part commit that includes a fix for inline images on
display and a patch for a possible vulnerability for Drafts. This
restructures the way we call Format::viewableImages() so that we call it
when displaying after we sanitize the data to ensure the images are not
broken. Secondly, this ensures Drafts do not skip the
`Format::htmlchars()` sanitization at all. This is a rewrite of #6511.
2023-06-12 17:22:35 +00:00
JediKev
7bbe0762d7 issue: Password Length
This addresses an issue reported by @manavparekh where allowing
excessively lengthy passwords is not a good idea. CVE-2023-30082
2023-06-12 17:22:35 +00:00
protich
f7dd4a7c24 installer: Change Email
This updates the email included in the intial installation Ticket to
`feedback@osticket.com`. This also updates the Ticket information to
include details about the documentation site.
2023-06-12 17:22:34 +00:00
JediKev
34b744b5ea issue: Email Only Attachment
This addresses an issue where emails that contain only attachment(s)
fail to process correctly. This is due to there being no `parts` for
`$part`. This adds a check for `!$part->parts` and if true (no parts) we
call `getAttachments()` on just the `$part` itself.
2023-06-12 17:22:34 +00:00
JediKev
c89454067c issue: glob() Empty Array
With PHP 8.2 `glob()` now returns an empty array if all paths are
restricted instead of `false`. This updates the only check we have on
this to check for empty array instead.
2023-06-12 17:22:34 +00:00
JediKev
544689d72b jquery: Update To 3.7.0
This updates jQuery to the latest stable release of 3.7.0.
2023-06-12 17:22:34 +00:00
JediKev
9c3ac48755 issue: strftime() Deprecation
This replaces the now deprecated `strftime()` with `date()`. This also
adds a TODO to class Format to replace an instance there. It will need
some refactoring though so we are leaving it out of this commit.
2023-06-12 17:22:34 +00:00
JediKev
8b12cfa96f issue: Relocate Typeahead JS/CSS Files
This is a rewrite of 2401 that relocates the Typeahead JS/CSS files for
people with setups that have things like HTTP Auth enabled.
2023-06-12 17:22:34 +00:00
JediKev
a0ba67febd issue: Duplicate User Copy/Paste Import
This addresses an issue where the Copy/Paste Import feature for Users
sometimes creates a duplicate account for an existing User. This occurs
when using a string like `User Name, email@domain.tld`, however, does
not occur when using a string like `User Name,email@domain.tld`. This is
due to the extra space after the comma as we are not trimming extra
spaces correctly. This adds `trim()` to the values before they are used
so that they correctly match existing values and avoids duplicates.
2023-06-12 17:22:34 +00:00
Peter Rotich
039a902252 FileUploadField: Custom File Upload Field
This commit fixes a bug where generic (ticket/task) file upload
configuration is mistakenly used on custom field file upload.
2023-06-12 17:21:30 +00:00
Peter Rotich
7fdc9949d6 File: Strict Mime Type Check
This commit adds config setting to FileUploadField to enable strict mime
type check when uploading files.
2023-06-12 17:21:30 +00:00
JediKev
b5b9e2b4a6 issue: File Upload Type
This addresses an issue where uploading a file throws an Invalid File
error even though the file is valid and there are no file restrictions.
This is due to doing `strcasecmp()` on a variable that is some times
empty. This adds a check to see if that variable has a value before
doing the comparison to avoid false positives. In addition, this adds a
new paramter called `$strict` that if true we run the mime type check.
By default `$strict` is false which skips the check. We will eventually
add a configurable setting to enable strict mime type check if preferred.
2023-06-12 17:21:30 +00:00
JediKev
2ed8adaef6 issue: i18n Audit Exports
This addresses an issue where attempting to use `All` Event type in any
other language than English the export will fail. This removes the
translation method from `All` so that the English version is used for
the option's `key`. This doesn't affect the visible word as it gets
translated downstream.
2023-06-12 17:21:30 +00:00
JediKev
8b1cd53ff1 Merge pull request #6561 from JediKev/security/latest-vulns-06-2023
Reviewed-By: JediKev <kevin@enhancesoft.com>
2023-06-12 12:14:11 -05:00
JediKev
9bb6c04464 Merge pull request #6560 from JediKev/installer/change-email
Reviewed-By: JediKev <kevin@enhancesoft.com>
2023-06-12 11:55:23 -05:00
JediKev
10e779b77d Merge pull request #6559 from JediKev/issue/email-only-attachment
Reviewed-By: JediKev <kevin@enhancesoft.com>
2023-06-12 11:55:11 -05:00
protich
a11aee294d installer: Change Email
This updates the email included in the intial installation Ticket to
`feedback@osticket.com`. This also updates the Ticket information to
include details about the documentation site.
2023-06-12 11:52:38 -05:00
JediKev
9e45f3fa95 issue: Email Only Attachment
This addresses an issue where emails that contain only attachment(s)
fail to process correctly. This is due to there being no `parts` for
`$part`. This adds a check for `!$part->parts` and if true (no parts) we
call `getAttachments()` on just the `$part` itself.
2023-06-12 11:47:48 -05:00
JediKev
0cfbc907b7 Merge pull request #6530 from empty88/patch-8
Reviewed-By: JediKev <kevin@enhancesoft.com>
2023-06-12 11:32:15 -05:00
JediKev
81cd39c84f Merge pull request #6557 from JediKev/php/8.2-support
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2023-06-12 10:48:36 -05:00
JediKev
e4bfb00de1 issue: iFrame Domain Bypass
This mitigates a vulnerability repoted by @endansdto where iFrame domain
can be bypassed due to bad regex. This updates the regex to ensure
proper filtering and checks.
2023-06-12 10:09:06 -05:00
JediKev
ae37925598 issue: XSS Image Caption
This mitigates a vulnerability reported by @cupc4k3 where XSS is possible
via image caption. This removes the possibilty for captions altogether.
2023-06-12 10:08:54 -05:00
JediKev
73b997a9de issue: Multiple File Upload
This mitigates a vulnerability reported by @ManuelHu where files can
bypass checks if sent in the same request behind a valid file. This also
adds a space to fix an annoyance in the JS errors.
2023-06-12 10:08:39 -05:00
JediKev
6924417599 issue: Draft & Inline Images
This is a two-part commit that includes a fix for inline images on
display and a patch for a possible vulnerability for Drafts. This
restructures the way we call Format::viewableImages() so that we call it
when displaying after we sanitize the data to ensure the images are not
broken. Secondly, this ensures Drafts do not skip the
`Format::htmlchars()` sanitization at all. This is a rewrite of #6511.
2023-06-12 10:08:03 -05:00
JediKev
86c2ba0220 issue: Password Length
This addresses an issue reported by @manavparekh where allowing
excessively lengthy passwords is not a good idea. CVE-2023-30082
2023-06-12 10:07:27 -05:00
JediKev
c64a2611b7 issue: glob() Empty Array
With PHP 8.2 `glob()` now returns an empty array if all paths are
restricted instead of `false`. This updates the only check we have on
this to check for empty array instead.
2023-06-12 09:30:54 -05:00
Peter Rotich
848d3bcf69 Merge remote-tracking branch 'upstream/develop' into 1.18.x 2023-06-07 09:16:17 -07:00
Peter Rotich
781dbe551b Merge pull request #6540 from JediKev/jquery/update-to-3.7.0
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-06-06 11:04:32 -05:00
Peter Rotich
e5cf0109cd Merge pull request #6553 from JediKev/issue/strftime-deprecation
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-06-06 10:55:02 -05:00
Peter Rotich
aac546d0db Database: Change Plugin Name to varchar(255)
This commit modifies name field in Plugin and Plugin Instance table to
varchar(255). This is necessary since plugin can have long arse
names.
2023-06-06 08:35:53 -07:00
JediKev
3fe132c452 issue: strftime() Deprecation
This replaces the now deprecated `strftime()` with `date()`. This also
adds a TODO to class Format to replace an instance there. It will need
some refactoring though so we are leaving it out of this commit.
2023-06-05 14:10:43 -05:00
Peter Rotich
f58bcc9bd7 Merge remote-tracking branch 'upstream/develop' into develop 2023-06-05 11:48:40 -07:00
Peter Rotich
f1d28b1c6d Merge pull request #6541 from JediKev/issue/relocate-typeahead-js-css-files
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-06-05 13:18:10 -05:00
Peter Rotich
32b0ed8cd8 Merge pull request #6543 from JediKev/update/laminas-mail
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-06-05 13:16:52 -05:00
Peter Rotich
6b53ff7950 Merge remote-tracking branch 'upstream/develop' into develop 2023-06-03 07:54:56 -07:00
fjf2002
8c8a7fd120 Update upgrade.inc.php
fix typo
2023-06-02 15:28:41 +02:00
fjf2002
9fd83eba3a Update upgrade.php
fix typo
2023-06-02 15:26:48 +02:00
Peter Rotich
9fd3cf5ffd Merge pull request #6520 from JediKev/issue/dup-user-copy-paste-import
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-05-30 17:36:30 -05:00
Peter Rotich
b7b31e721d Merge pull request #6542 from JediKev/issue/remove-old-login-code
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-05-30 17:34:37 -05:00
JediKev
bb45d37a54 todo: Remove Custom prepareHeaders()
This removes the custom `prepareHeaders()` method from class Sendmail as
this functionality was added upstream which is now available with the
latest update.
2023-05-30 17:22:41 -05:00
JediKev
66fa10af51 update: Laminas-Mail
This is a rewrite of 6312 and updates the laminas-mail package from
2.10.1 to 2.23.0. This also remove unused dev dependencies.
2023-05-30 17:22:36 -05:00
JediKev
e17ad463ec issue: Remove Old Login Code
This removes old and tired code from the login pages where we throw
`Http::response(422);` to "prevent browsers from suggesting saving the
username and password". This is no longer the case as browsers no longer
play by these rules. The only way to properly do this nowadays is to set
an attribute on the form tag to disable autocomplete. We don't want to
do this either as that would prevent password managers from autofilling
the auth info.

Throwing a 422 code prevents some setups from accepting logins due to
4XX codes being error codes in nature. Removing this code seemingly has
no negative impact whatsoever.

A short backstory, this was [originally a 401
code](a99b9ce81e)
to prevent browsers from suggesting saving the auth info. Later it was
[changed to a 422
code](772d5c59a9)
to make IE play nice.
2023-05-25 13:55:48 -05:00
JediKev
62cd40648b issue: Relocate Typeahead JS/CSS Files
This is a rewrite of 2401 that relocates the Typeahead JS/CSS files for
people with setups that have things like HTTP Auth enabled.
2023-05-25 13:46:03 -05:00
JediKev
ffa23dad20 jquery: Update To 3.7.0
This updates jQuery to the latest stable release of 3.7.0.
2023-05-22 12:47:20 -05:00
JediKev
b304cdbd9c issue: Duplicate User Copy/Paste Import
This addresses an issue where the Copy/Paste Import feature for Users
sometimes creates a duplicate account for an existing User. This occurs
when using a string like `User Name, email@domain.tld`, however, does
not occur when using a string like `User Name,email@domain.tld`. This is
due to the extra space after the comma as we are not trimming extra
spaces correctly. This adds `trim()` to the values before they are used
so that they correctly match existing values and avoids duplicates.
2023-05-11 13:32:29 -05:00
JediKev
4493b1267b issue: Inline-Images Canned Responses
This addresses an issue where inline images only used in canned
responses and saved before a draft could be generated would erroneously
be deleted by the orphan file cleanup routine. This caused the inline
images to appear broken on use as they no longer exist. This is due to a
check for a `draft_id` before creating an attachment record for each
inline file. We do not need to check for a `draft_id` as we always have
a `response` in the POST with the most current data (eg. there might be
content added before a new draft can be saved). This removes the checks
in both `create` and `update` cases so that attachment records can be
created (with `inline = 1`) to avoid being deleted by file cleanup.
2023-05-11 13:27:20 -05:00
Peter Rotich
41c1edd940 Merge remote-tracking branch 'upstream/develop' into develop 2023-05-11 08:42:53 -07:00
Peter Rotich
6c8b151ae3 Merge remote-tracking branch 'upstream/1.17.x' into develop 2023-05-11 08:42:47 -07:00
eMpTy
1105cde711 Make string translatable 2023-05-02 10:36:49 +02:00
Claudio Nicora
7805cd9428 Fix PR #6283: DB column value of "local backend" is 'client' and not 'local' 2023-03-21 12:04:54 +01:00
Peter Rotich
4e570b53d2 Merge pull request #6494 from JediKev/oauth2/strict-matching-bug
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-03-16 20:09:38 -05:00
JediKev
e014ffd24c oauth2: Strict Matching Bug
This addresses an issue where we were checking strict matching when
using basic authentication. This is due to a small oversight in the smtp
save checks. This adds a line to check if auth_bk is set to oauth2
before checking strict matching.
2023-03-16 15:23:19 -05:00
Peter Rotich
ef25ee7607 Merge pull request #6493 from JediKev/issue/thread-entry-actions-z-index
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-03-16 11:52:42 -05:00
JediKev
215a0ce262 issue: Thread Entry Actions z-index
This addresses a small annoyance where the thread entry actions dropdown
always appears below the subsequent thread entry. This can be very
annoying and even hide some of the options. This adds `z-index` of
`auto` to the `thread-entry` div so that the dropdown always appears on
top.
2023-03-16 11:03:41 -05:00
JediKev
8bd22d0942 Add Release Notes for v1.17.3 2023-03-08 19:58:52 +00:00
JediKev
ca95150105 Merge branch '1.16.x' into develop 2023-03-08 19:53:07 +00:00
JediKev
30ef1cc2ae Add Release Notes for v1.16.6 2023-03-08 19:46:13 +00:00
JediKev
402992f385 Merge pull request #6485 from JediKev/issue/getRealPath-stub
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2023-03-08 13:43:46 -06:00
JediKev
32a229aa98 issue: getRealPath() Stub
This addresses an issue where lint tests fails due to `getRealPath()`
not being defined. This is from SplFileObject so we just need to add a
stub to ignore it in the tests.
2023-03-08 13:41:40 -06:00
JediKev
ead4f742ba Merge pull request #6484 from JediKev/security/latest-patches-03-2023
Reviewed-By: JediKev <kevin@enhancesoft.com>
2023-03-08 13:27:30 -06:00
JediKev
263f71a38e security: Search Params
This mitigates a vulnerability with search params.
2023-03-08 13:22:20 -06:00
Peter Rotich
731be08600 Merge remote-tracking branch 'upstream/develop' into develop 2023-03-08 09:20:02 -08:00
Peter Rotich
55b4bde204 Merge remote-tracking branch 'upstream/1.16.x' into develop 2023-03-08 09:19:49 -08:00
JediKev
ee7ce3d9c3 plugins: Path Manipulation
This mitigates a vulnerability reported by xxx where path manipulation
is possible via Plugins.
2023-03-08 10:56:30 -06:00
JediKev
e2c8f7992c xss: Email Templates
This mitigates a vulnerability reported by xxx where XSS is possible via
Email Templates.
2023-03-08 10:56:04 -06:00
JediKev
8a55c29f87 xss: Queues
This mitigates a vulnerability reported by indevi0us where XSS is
possible via Queue conditions.
2023-03-08 10:54:19 -06:00
JediKev
343a2b47e1 xss: AJAX Paths
This mitigates a vulnerability reported by indevi0us where XSS is
possible via some AJAX paths.
2023-03-08 10:53:55 -06:00
JediKev
619ce0f620 xss: Dashboard period
This mitigates a vulnerability reported by @indevi0us where XSS is
possible via the `period` parameter. This sanitizes the parameter values
before using them anywhere.
2023-03-08 10:50:47 -06:00
JediKev
9fb01bc12f xss: Roles
This mitigates a vulnerability reported by indevi0us where XSS is
possible via the `name` parameter for Roles.
2023-03-08 10:50:07 -06:00
JediKev
daee20fdd8 xss: Organization Search q
This mitigates a vulnerability reported by @indevi0us where XSS is
possible via the `q` parameter in organization lookups. This sanitizes
the parameter value before use as well as htmlchars it before adding to
JSON output.
2023-03-08 10:36:28 -06:00
JediKev
ec6043935b xss: Search parent_id
This mitigates a vulnerability reported by @indevi0us where XSS is
possible via the parent_id parameter. This forces the parameter to an
INT so that there is no chance of XSS.
2023-03-08 10:35:56 -06:00
JediKev
091ddba965 xss: System Email
This mitigates a vulnerability reported by @edr4 where XSS is possible
via System Email. This adds sanitization to the email before
saving/displaying. This also forces the other options (topic,
department, etc.) to int so there is no chance of XSS.
2023-03-08 10:35:10 -06:00
JediKev
86f9693dc6 xss: Draft Files
This mitigates an XSS vulnerability with files uploaded through drafts.
2023-03-08 10:31:12 -06:00
Peter Rotich
5290e4997a Merge pull request #6482 from protich/issue/mailer+attachments
Reviewed-By: Jedikev <kevin@enhancesoft.com>, protich <peter@osticket.com>
2023-03-08 09:51:11 -06:00
Peter Rotich
e43cc799f0 Merge pull request #6483 from JediKev/issue/mPDF-font-files
Reviewed-By: Jedikev <kevin@enhancesoft.com>, protich <peter@osticket.com>
2023-03-08 09:50:53 -06:00
Peter Rotich
8c137fb700 Merge pull request #6386 from bestlong/patch-1
Reviewed-By: Jedikev <kevin@enhancesoft.com>, protich <peter@osticket.com>
2023-03-08 09:42:12 -06:00
Peter Rotich
516b6e71ec Merge pull request #6437 from JediKev/issue/file-upload-false
Reviewed-By: Jedikev <kevin@enhancesoft.com>, protich <peter@osticket.com>
2023-03-08 09:40:50 -06:00
Peter Rotich
a09a23e355 Merge pull request #6469 from protich/JediKev/oauth2/strict-matching
Reviewed-By: Jedikev <kevin@enhancesoft.com>, protich <peter@osticket.com>
2023-03-08 09:39:50 -06:00
JediKev
8dd2ab61df issue: FileUpload False
This addresses an issue where when doing full Ticket edit with a
FileUpload field and you don't supply a file the form_entry_value for
the corresponding field updates to `false`. This causes an issue with IS
NULL criteria so this adds a check to see if a value is present, if not,
then we return `NULL` instead of JSON encoding a `false` string value.
2023-03-08 09:37:03 -06:00
JediKev
427a8aa628 issue: mPDF Font Files
This addresses an issue where some of the mPDF font files don't get
included due to the way we use the `preg_split()`. This adds the
remaining path parts to the `list()` and adds statements to update the
`$path` if such parts exist.
2023-03-08 09:20:37 -06:00
Peter Rotich
64a8abf375 lint: Fix Case Mismatch 2023-03-07 17:53:26 -08:00
Peter Rotich
8815d0874f mailer: Inline Attachments
This commit addresses an issue where inline images also gets attached as
files.
2023-03-07 17:53:26 -08:00
Peter Rotich
7efbdfd2a3 Add FileObject Interface
This commit adds File Object Interface to force uniformity when using
mixed file types like AttachmentFile and FileObject
2023-03-07 17:53:26 -08:00
Peter Rotich
ca6c1778e5 Merge remote-tracking branch 'upstream/develop' into develop 2023-03-07 12:53:02 -08:00
Peter Rotich
5c239adb57 oauth2: Support Alias/Shared Mailboxes
This commit add logic to Mail Protocol to support shared / aliased
mailboxes by setting token User.
2023-03-07 12:29:22 -08:00
JediKev
76149d65ef Merge pull request #6466 from JediKev/oauth2/strict-matching
Reviewed-By: protich <peter@osticket.com>, JediKev <kevin@enhancesoft.com>
2023-03-07 12:48:49 -06:00
JediKev
e76e91a8ee Merge pull request #6462 from JensEB/patch-2
Reviewed-By: protich <peter@osticket.com>, JediKev <kevin@enhancesoft.com>
2023-03-07 12:46:21 -06:00
JediKev
cf83b3ad38 Merge pull request #6435 from JediKev/issue/queue-counts-incorrect
Reviewed-By: protich <peter@osticket.com>, JediKev <kevin@enhancesoft.com>
2023-03-07 12:21:25 -06:00
JediKev
23719a232f Merge pull request #6427 from JediKev/issue/does-not-have-a-value
Reviewed-By: protich <peter@osticket.com>, JediKev <kevin@enhancesoft.com>
2023-03-07 12:17:51 -06:00
JediKev
ce8e0b70e4 Merge pull request #6423 from empty88/patch-2
Reviewed-By: protich <peter@osticket.com>, JediKev <kevin@enhancesoft.com>
2023-03-07 12:12:58 -06:00
JediKev
13e75be91b Merge pull request #6421 from empty88/patch-6
Reviewed-By: protich <peter@osticket.com>, JediKev <kevin@enhancesoft.com>
2023-03-07 12:10:05 -06:00
JediKev
34d978a972 Merge pull request #6395 from protich/feature/dbproxies
Reviewed-By: protich <peter@osticket.com>, JediKev <kevin@enhancesoft.com>
2023-03-07 12:04:23 -06:00
JediKev
b20a2ba861 Merge pull request #6440 from JediKev/jquery/update-to-3.6.3
Reviewed-By: JediKev <kevin@enhancesoft.com>
2023-03-07 09:48:43 -06:00
JediKev
8d39a4cc59 jquery: Update To 3.6.3
This updates jQuery to the latest stable release of v3.6.3.
2023-03-07 09:47:48 -06:00
JediKev
58c6c2246b Merge pull request #6439 from JediKev/jquery/update-jquery-ui-1.13.2
Reviewed-By: JediKev <kevin@enhancesoft.com>
2023-03-07 09:43:33 -06:00
JediKev
37f6a3f970 Merge pull request #6434 from JediKev/session/user-logged-out
Reviewed-By: JediKev <kevin@enhancesoft.com>
2023-03-07 09:41:12 -06:00
JediKev
34f913c084 Merge pull request #6428 from JediKev/issue/disabled-queues-run-counts
Reviewed-By: JediKev <kevin@enhancesoft.com>
2023-03-07 09:39:23 -06:00
JediKev
bab98e3090 Merge pull request #6426 from JediKev/print/hidden-thread-events
Reviewed-By: JediKev <kevin@enhancesoft.com>
2023-03-07 09:38:39 -06:00
JediKev
e15e1d2929 Merge pull request #6410 from JediKev/issue/featured-faqs
Reviewed-By: JediKev <kevin@enhancesoft.com>
2023-03-07 09:37:37 -06:00
JediKev
1c316688a1 Merge pull request #6409 from JediKev/issue/ticketdenied-errno
Reviewed-By: JediKev <kevin@enhancesoft.com>
2023-03-07 09:36:51 -06:00
JediKev
2e0e768f1d Merge pull request #6408 from JediKev/issue/static-forTask
Reviewed-By: JediKev <kevin@enhancesoft.com>
2023-03-07 09:35:59 -06:00
JediKev
a9f3d9145c Merge pull request #6403 from JediKev/issue/ldap-multi-instance-fatal-error
Reviewed-By: JediKev <kevin@enhancesoft.com>
2023-03-07 09:35:18 -06:00
JediKev
41af1ab738 Merge pull request #6400 from protich/issue/mailfetch+default-dept
Reviewed-By: JediKev <kevin@enhancesoft.com>
2023-03-07 09:33:27 -06:00
JediKev
ea343b7cce Merge pull request #6399 from protich/issue/misc
Reviewed-By: JediKev <kevin@enhancesoft.com>
2023-03-07 09:32:40 -06:00
Peter Rotich
1a824770e1 Merge pull request #6477 from JediKev/i18n/make-pot
Peter Rotich <peter@enhancesoft.com>
2023-03-04 13:34:22 -06:00
Peter Rotich
96794feac8 Merge pull request #6480 from JediKev/issue/dept-export
Reviewed-By: Peter Rotich <peter@osticket.com>
2023-03-04 13:32:05 -06:00
Peter Rotich
607264dea9 Merge pull request #6481 from JediKev/update/readme-php-version
Reviewed-By:  Peter Rotich <peter@osticket.com>
2023-03-04 13:31:00 -06:00
JediKev
6087bc550c update: README PHP Version
This updates the PHP version in the Requirements section from `8.0` to
`8.0 - 8.1 (8.1 recommended)` to avoid confusion with the newly released
`8.2`.
2023-03-02 14:52:32 -06:00
JediKev
5418cf2092 issue: Department Export
This addresses a small issue where when attempting to export Agents in
any Department you get a fatal error of calling non-static method
statically. This updates the export method for the Department class to
static so that exports will function and no errors are thrown.
2023-03-02 09:51:02 -06:00
JediKev
74274b5cb1 i18n: make-pot
This addresses an issue where `make-pot` command is not working
properly. This is due to PHP 8 changes that were never fully resolved.
We had to move away from the now deprecated `each()` so we switched to
`foreach()`. However, `foreach()` does not keep current position in the
iteration which we rely on for the `make-pot` command. However, you can
get around this by using `while($var = current())` and passing by
reference which keeps the current position.
2023-02-27 15:40:40 -06:00
JediKev
c0cd8b16d0 oauth2: Delete Token Banner
This updates the Delete Token functionality to show a success/error
banner when deleting the token. If successful it will delete the Token
tab and content, load the IdP tab, and show a success banner with the
JSON content. On error it will simply show an error banner with the JSON
content.
2023-02-22 16:36:37 -06:00
JediKev
f531718a56 oauth2: Strict Matching Default True
This changes the default for Strict Matching from `false` to `true` so
that new emails are strict unless explicitly set otherwise. This also
adds some descriptive comments to the getter and setter.
2023-02-16 15:41:46 -06:00
JediKev
cb3625e875 oauth2: Bug Fixes
This addresses some bugs found during testing. This adds a check to
`SmtpAccount::getConfig()` that ensures we have an email and
MailboxAccount before attempting to call `getConfig()` and adds
descriptive comments. This also removes the `isActive()` check from the
SmtpAccount `save()` method as we only care about if it's currently set to
active. Lastly, this changes the variable name for the email address
from `$email` to `$addr` so it doesn't conflict with the original
`$email` variable.
2023-02-16 15:34:54 -06:00
JediKev
ca913ba29f oauth2: Delete Token
It's about damn time.. that we add ability to delete the OAuth2 Token.
This adds a new button to the Token tab that will allow you to delete
the current Token.
2023-02-16 15:30:37 -06:00
JediKev
b7718be42d oauth2: Add Strict To Backend ID
This adds the Strict Matching value to the Backend ID for now until we
add a column for it in the database (1.18.x).
2023-02-14 15:51:11 -06:00
JediKev
7275d67bc7 oauth2: Strict Matching
This is a rewrite of osTicket/osTicket-plugins#263 where it was
attempting to implement Strict Matching. This fully implements Strict
Matching and adds functionality to check for this when retrieving a
token and when saving SMTP with Same As Remote Mailbox. This also adds a
new disabled field to the General Settings to show what email will be
used as well as a help tip that further explains Strict Matching.
2023-02-14 14:53:27 -06:00
JensEB
af83896d92 Uncaught Error: Call to a member function getId() on bool in /.../include/class.session.php:314
To prevent following error:
PHP Fatal error: Uncaught Error: Call to a member function getId() on bool in /.../include/class.session.php:314

Logged back trace:

PHP Fatal error: Uncaught Error: Call to a member function getId() on bool in /.../include/class.session.php:314
Stack trace:
#0 /.../include/class.session.php(336): osTicket\\Session\\AbstractSessionStorageBackend->getRecord()
#1 /.../include/class.session.php(281): osTicket\\Session\\AbstractSessionStorageBackend->update()
#2 [internal function]: osTicket\\Session\\AbstractSessionHandler->write()
#3 [internal function]: session_write_close()
2023-02-03 12:55:23 +01:00
JediKev
d47fc2b1cc issue: Does Not Have A Value
This addresses an issue where using the `Does Not Have A Value` criteria
on things like Short Answer Fields shows incorrect results. In fact, it
shows all Tickets with and without values for the Field. This is due to
using an overall negative filter (`column IS NULL` or `NOT column IS NOT
NULL`) on a column from a LEFT JOIN causing the backend to return NULL
for all values in that column. This adds a new variable called
`$nullable` that is set to `false` if the `$method` is `nset`. This then
passes `$nullable` to `getOrmPath()` so it can determine wether or not
to force a normal JOIN instead of LEFT JOIN so that `xxx IS NULL`/`NOT
xxx IS NOT NULL` will return the proper results. If the search method is
not `nset` then it will use LEFT JOIN as before.
2023-01-19 13:35:20 -06:00
JediKev
b896f70de6 jquery: Update jQueryUI 1.13.2
This updates jQueryUI from `1.13.1` to `1.13.2`. This also includes the
updated CSS for jQueryUI `1.13.2`.
2023-01-18 16:20:52 -06:00
JediKev
2428cc5823 issue: Queue Counts Incorrect
This addresses an issue where when a Queue is filtering by a specific
field the counts are incorrect. This is due to using only the
`constraints` instead of both `constraints` and `path_constraints`. This
updates the `counts()` method to include the `path_constraints` if set
so that the counts are properly calculated.
2023-01-11 15:41:04 -06:00
JediKev
00d409d30e session: User Logged Out
This addresses an issue where Users login, do something, and get
randomly shown the login screen. If they refresh the page it shows
them authenticated as normal again. This is due to a Session ID mismatch
in `isvalidSession()`. When `refreshSession()` calls
`regenerateSession()` it's supposed regenerate the session and update
the session ID however it updates the local ID which `refreshSession()`
later does. This updates `regenerateSession()` to where it updates the
session ID and allows `refreshSession()` to update the local ID. This
deduplicates the local ID update and corrects the ID mismatch.
2023-01-09 14:38:55 -06:00
JediKev
3ff3ce93aa issue: Disabled Queues Run Counts
This addresses an issue where Disabled Queues are still being used in
counts. This is due to grabbing any CustomQueue that has the
`FLAG_QUEUE` flag and/or having `staff_id` equal to the current Staff
member's ID. This adds another filter `Q::not(…'flags__hasbit' =>
CustomQueue::FLAG_DISABLED,` that will ensure the Queue is not Disabled
before using in counts.
2023-01-04 11:19:36 -06:00
JediKev
f63fc67d23 print: Hidden Thread Events
This addresses an issue reported on the Forum where when a field is set
to not visible to Users it is hidden from the Staff Ticket Print PDF.
This is due to the ThreadEvent MODE that is being used (MODE_CLIENT).
This uses the User Ticket Print template instead of the Staff Ticket
Print template which hides some thread events and other data. This
updates the utilized MODE to MODE_STAFF so that the appropriate data is
printed. This also addresses an issue where if the Staff has Relative
time configured it shows in the Ticket Print which should not happen.
This adds a check for `$_REQUEST['a']` and if it's set we make sure it’s
not `print` to avoid printing relative times.
2023-01-03 13:50:52 -06:00
eMpTy
46c005a02b add translation for task status 2022-12-29 11:01:06 +01:00
eMpTy
cb52804450 Add translation for "last resort" standard column labels 2022-12-28 15:29:32 +01:00
JediKev
8df095817f issue: Featured FAQs
This addresses a small logic issue where we are adding Public and
Featured FAQs in the Featured sidebar when it should only be Featured
ones. This updates the ORM statement to only show FAQs that are set to
Featured and belong to a Public or Featured Category.
2022-12-13 15:12:41 -06:00
JediKev
6de7cf4c2c issue: TicketDenied errno
This addresses an issue where emails being rejected by Banlist is
causing excessive processing errors and refuses to move the mail. This
is due to our logic expecting `403` exception code for TicketDenied but
we are using `500` in `TicketApiController::createTicket()`. This
updates the `exerr()` line to use the `$errors['errno']` if it exists
otherwise default to `500`.
2022-12-13 13:33:24 -06:00
JediKev
1079011fc0 issue: Static forTask()
This addresses an issue where printing a Ticket with Tasks or printing a
Task itself will throw a fatal error of `Non-static method
DynamicFormEntry::forTask() cannot be called statically`. This updates
the `forTask()` method to be static to avoid fatal errors.
2022-12-12 12:10:37 -06:00
JediKev
f64860441e issue: LDAP Multi-Instance Fatal Error
This addresses an issue where if you have more than one instance of LDAP
plugin enabled and try to lookup a User it fails with a fatal error of
`Object of class StaffLDAPAuthentication could not be converted to
string`. This is due to using `array_unique()` which can't convert
objects to strings. This updates the method and adds the `SORT_REGULAR`
flag so that it can compare without converting objects to strings.

In addition this addresses an issue where we were using static ID for
StaffLDAPAuthentication backend. We updated the plugin code to reflect
the actual Backend ID but the current AJAX regex for this doesn’t match
this format. This updates the AJAX regex to match the updated format.
2022-12-05 17:09:35 -06:00
Peter Rotich
f410d4ec58 fetcher: Default Department
When fetching a mailbox, osTicket routes fetched emails to a department based
on `To` or `Delivered-To` header entries. This allows for ability to fetch
aliased emails from one primary account and route them based on configured
system emails. On failure to match, the email (ticket) is routed to the
department of the email account being fetched.

With the email backend overhaul in v1.17 - a bug was instroduced where
unmatched emails ended up being routed to default department instead of the
department of the email being fetched.

This commit addresses the issue...
2022-12-04 23:22:25 +00:00
Peter Rotich
389c669728 Merge remote-tracking branch 'upstream/develop' into develop 2022-11-30 08:42:52 +00:00
Peter Rotich
10af29a5d7 email: Account for possible Orphaned SMTP Accounts 2022-11-30 08:42:52 +00:00
Peter Rotich
ebbebaab38 Merge remote-tracking branch 'upstream/1.17.x' into develop 2022-11-30 08:42:52 +00:00
Peter Rotich
eac9960a51 cron: Local Cron as CLI 2022-11-30 08:42:52 +00:00
Peter Rotich
24fd5075d6 logs: System Logs Tooltip
This commit addresses a bug introduced in d4d9c4242 due to conflicting function
names in Api Controler.
2022-11-30 08:42:52 +00:00
Peter Rotich
e6beeb9baf api: API Request Format
This commit addresses a bug introduced by d4d9c4242 by making sure format is
taken into account when processing API Requests.
2022-11-30 08:42:52 +00:00
Peter Rotich
246aaa4dcf mysql: Support Sidecar Database Proxies
When running osTicket in a multi-node cluster setup, it makes sense to connect
to the database via a load balancer or a database proxy (assuming VIP with
failover support). It's great for redundancy -  however - this adds extra time
(miliseconds) because of the extra network hop latency.

To save a few miliseconds - when osTicket (the Application) is also hosted on
the same node or container running the database proxy - then it might make
sense to attempt a local connection first before attempting via via the VIP
(sidecar pattern).

This commit adds multiple database hosts support, comma separated, when defining
DBHOST in osTicket config file (`ost-config.php`). It also adds a new _Database
Option_ to define `DBCONNECT_TIME` used to set `MYSQLI_OPT_CONNECT_TIMEOUT`.
Connection timeout is important with multiple hosts so connection attempts
(done sequencially) can timeout relatively quickly.
2022-11-24 19:09:54 +00:00
JediKev
61263635d9 Merge branch '1.17.x' into develop 2022-11-23 18:33:45 +00:00
JediKev
8d9ab037b3 Merge branch '1.16.x' into 1.17.x 2022-11-23 18:33:35 +00:00
JediKev
460c5fd4a5 Merge pull request #6381 from protich/1.17.x
Reviewed-By: JediKev <kevin@enhancesoft.com>
2022-11-23 12:32:19 -06:00
JediKev
58fd151393 Merge pull request #6379 from protich/issue/session+regenerate
Reviewed-By: JediKev <kevin@enhancesoft.com>
2022-11-23 12:31:37 -06:00
Peter Rotich
3d7b2394be Merge pull request #6385 from JediKev/session/collab-view-ticket
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-11-23 12:27:02 -06:00
Peter Rotich
0a4498b22e Lint Fixes 2022-11-23 18:22:10 +00:00
Shao Yu-Lung (Allen)
a6d4761a85 fix: typo 2022-11-23 18:55:11 +08:00
JediKev
6afe0efa37 session: Collab View Ticket
This addresses an issue where if a Collaborator views a Ticket via the
Check Ticket Status button in the Client Portal and then tries to repeat
the process with an additional Ticket it will redirect to login page.
This is due to when instantiating the session we use `$user->getId()`
which causes a problem when the User is a Collaborator. When
instantiating a session for a Collaborator object `getId()` returns the
Collaborator ID, not the Collaborator's User ID. This updates the call
from `getId()` to `getUserId()` which gets the correct User ID from the
Collaborator object (and others).
2022-11-22 15:51:01 -06:00
Peter Rotich
d4d9c4242f fetcher: Mail Fetcher / Parser Error Handling
Prior to osTicket `v1.17.x` email fetching required and used php-imap module to
decode and parse emails, whereas locally or remotely piped emails (via API
endpoint) used a Raw Email Parser ("Mail Parser").

Using two separate backends meant duplicated code, logic and inconsistent email
processing.

Starting with osTicket `v1.17.0`, we overhauled the Email Backend to OAuth2
support and in the process we switched toxusing a unified Mail Parser for both
fetched and piped emails. To determined how the email got delivered in order to
handle errors for the interfaces accodingly, we relied on `PHP_SAP`I /
`php_sapi_name()`.

That was shortsighted because of the value of `PHP_SAPI` can be different in
the following cases;

1. Email Fetching can be triggered by autocron (Staff Activity) or by a remote
task (API)

2. `PHP_SAPI` cannot be relied on for php installations using php-fpm, php-cgi
or custom binaries for php CLI executable

3. Cron Job / Task entry "user-name" or "command path" might use separate php
executables (related to `#2`)

4. Triggering cron/php via php-fpm socket connection e.g using FastCGI Client

Since emails are fetched in batches - Email Fetching, unlike stateless API
requests, should be considered a CLI operation regardless of how it gets
trigerred, this necessary so the system can gracefully handle fetch and parse
errors.

This commit does just that and a few other minor logic / code cleanup.

~ Peter - with love!
2022-11-18 11:49:12 +00:00
Peter Rotich
d5853245f7 session: Regenerate Session Id
To avoid session fixation, osTicket regenerates Session Id on-login and on
every session timeout set for the User/Agent even with activity.

This commit changes the logic to make sure that the regenerate `TIME BOMB` only
goes off on a GET request - this is necessary so we don't invalidate data
in-flight on a POST request since osTicket Forms API backend uses session_id to
generate form field names as well as CSRF token used on forms.
2022-11-15 22:38:09 +00:00
JediKev
8fbc7eea6a Add Release Notes for v1.17.2 2022-11-14 22:16:50 +00:00
JediKev
f6c8d18495 Merge branch '1.16.x' into 1.17.x 2022-11-14 22:13:08 +00:00
JediKev
b48bf7eb0b Add Release Notes for v1.16.5 2022-11-14 22:08:06 +00:00
JediKev
5ae737b9bb Merge branch 'develop' into 1.16.x 2022-11-14 22:03:32 +00:00
JediKev
9530965f8c Merge pull request #6373 from protich/issue/session+max_ttl
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-14 16:00:20 -06:00
JediKev
7f5f8cb747 Merge pull request #6371 from protich/issue/email_validator
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-14 15:59:39 -06:00
JediKev
d2ae93b540 Merge pull request #6370 from protich/issue/user-create
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-14 15:58:56 -06:00
JediKev
0541faa826 Merge pull request #6369 from protich/issue/email
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-14 15:58:25 -06:00
JediKev
c17aa40852 Merge pull request #6368 from protich/issue/mailparse+filename
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-14 15:58:00 -06:00
JediKev
396d307913 Merge pull request #6366 from protich/1.17.x
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-14 15:56:52 -06:00
JediKev
8f5f563973 Merge pull request #6363 from JediKev/issue/html-typo
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-14 15:56:32 -06:00
JediKev
9c2d65ffe4 Merge pull request #6361 from sajtiii/develop
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-14 15:56:05 -06:00
JediKev
fd4acd4298 Merge pull request #6360 from rinkp/fix/sessiondata
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-14 15:48:22 -06:00
Peter Rotich
133585f8cd mailer: Use Namespaced Mailer
This commit instantiates the mailer using the correct namespace.
2022-11-14 21:24:43 +00:00
Peter Rotich
ea3e03d212 session: Memcache Max TTL
When storing session data in Memchache storage backend we need to make sure
the TTL is not longer than 30 days, maximum allowed.
2022-11-14 15:49:45 +00:00
Peter Rotich
55fbfcb57b user: Make sure User has a valid Email on create
Emailed tickets skips user validation on purpose with the assumption that the
user has at least a valid email. The assumption can be problematic when
osTicket has issues parsing the from address due to invalid format.
2022-11-13 21:43:28 +00:00
Peter Rotich
3c4a20a85f validator: Email Domain Verification
To truly verify an email, osTicket uses DNS lookup by checking MX record, if
that fails it defaults to checking A / AAAA record of the email domain.

This commit handle a case where a domain doesn't have A record.
2022-11-13 20:51:24 +00:00
Peter Rotich
c19a5f03e9 smtp: Only init SMTP if we have credentials 2022-11-13 18:52:37 +00:00
Peter Rotich
de620fe602 email: Clear Last Error On Save 2022-11-13 18:52:37 +00:00
Peter Rotich
c806721298 mailparse: Parse filenames with complex format 2022-11-13 18:36:04 +00:00
JediKev
50dfe93133 issue: Html Typo
This addresses a small typo where the class `Html` was used instead of
`Http`.
2022-11-10 09:23:05 -06:00
sajtiii
04c0bb3a00 Fix trusted proxy wildcard check 2022-11-10 11:28:15 +01:00
rinkp
cf8b8ce721 Fix old reference to SessionData 2022-11-10 11:13:50 +01:00
JediKev
58bf538df9 Add Release Notes for v1.17.1 2022-11-09 21:16:42 +00:00
JediKev
97c50c197b Merge branch 'develop' into 1.17.x 2022-11-09 21:14:00 +00:00
JediKev
1d11ee674f Add Release Notes for v1.16.4 2022-11-09 20:51:24 +00:00
JediKev
f7ab9f42d5 Merge branch 'develop' into 1.16.x 2022-11-09 20:49:10 +00:00
JediKev
095dc0118c Merge pull request #6358 from protich/issue/session_revisited
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-09 14:42:07 -06:00
Peter Rotich
4fc5f5cc17 session: Add routines to get and filter session records
This commit adds the base functions to get session records based on desired
criteria. It also fixes errant error_log as well as a typo.
2022-11-09 05:05:12 +00:00
Peter Rotich
49d91b0551 session: On-demand Session Destroy
This commit adds ability to ask backend to destroy a session by session_id.
Also changes how expire routine works - passing zero ttl results in the session
being destroyed immediately.
2022-11-09 04:39:07 +00:00
JediKev
e1d759c4d5 Merge pull request #6357 from JediKev/security/username-xss
Reviewed-By: JediKev <kevin@enhancesoft.com>
2022-11-08 14:57:27 -06:00
JediKev
5213ff138c security: Username XSS
This mitigates a vulnerability reported by legoclones on
[huntr.dev](https://huntr.dev/) where XSS can be stored/executed via a
User's `username`. If an Agent is redirected to the User Lookup AJAX
response directly it will execute the stored XSS. This adds
`Format::sanitize()` to the `username` before saving to the database so any
potential XSS is sanitized. Additionally, this adds `Format::htmlchars()` to
the `username` before displaying in the UI to help prevent code execution.
2022-11-08 20:54:15 +00:00
JediKev
dc0507e3b8 Merge pull request #6356 from JediKev/security/refresh-url
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>
2022-11-08 14:51:31 -06:00
protich
3702a4fb3f security: Refresh URL
This initially targeted a particular vulnerability with the Advanced
Search Refresh URLs but subsequently evolved into unifying all Refresh
URLs. We decided to create methods that properly encode URLs to prevent
code execution and to ensure they are all uniformly created. This adds
such capabilities and updates current Refresh URL generation to the new
methods. This also adds a method `Format::http_query_string()` that will
filter the query string and remove any unwanted params. Lastly, this
removes the old and tired `Misc::currentURL()` in favor of the new
"hip" (is that what the kids say these days?) method `Http::url()`.
2022-11-08 14:48:29 -06:00
JediKev
2832d44362 Merge pull request #6345 from protich/issue/fetcher+expunge
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-08 14:40:26 -06:00
JediKev
1c42e26a0e Merge pull request #6334 from protich/issue/session++
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-08 14:38:57 -06:00
JediKev
08b5eea771 Merge pull request #6349 from protich/feature/mailparse-error-handling
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-08 14:37:48 -06:00
JediKev
406b06f284 Merge branch '1.17.x' into feature/mailparse-error-handling 2022-11-08 14:36:46 -06:00
JediKev
2d8bd7498f Merge branch '1.17.x' into issue/fetcher+expunge 2022-11-08 14:35:13 -06:00
JediKev
22db3eca53 Merge pull request #6304 from protich/issue/emailaccount-isactive
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-08 14:33:55 -06:00
Peter Rotich
f85f9036a4 mailparse: Misc. Fixes 2022-11-08 19:31:57 +00:00
Peter Rotich
46b7899bc7 mailparse: Thread Entry on Parse Error
Add mail parse error as the Message instead of an empty entry.
2022-11-08 19:31:57 +00:00
Peter Rotich
678098d874 mailparse: FileName On Parse Error
Use subject as a filename if available otherwise default to Message Id
2022-11-08 19:31:57 +00:00
Peter Rotich
30ad9cd477 api: Don't validate email requests
Skip validating the structure of email request - this is important since emails
don't necessarily have all User Information - e.g name and should be failure
resistant.
2022-11-08 19:31:57 +00:00
Peter Rotich
ba2d31cd68 api: Pass Ticket Source to Ticket Create 2022-11-08 19:31:57 +00:00
Peter Rotich
a4e36d399c mailparse: Handle Mail Parse Errors Gracefully
On mail parse error, osTicket typically leaves the email alone and reports it
as a fetch failure - which results in multiple fetch attempts until an admin
manually moves the email out of the Fetch Folder. Most parse errors are caused
by poorly formatted mime message (email) that failes to decode.

This commit adds ability to create a ticket even when osTicket encounters mail
parse error. The entire raw email is attached to the ticket as message/rfc822
(.eml) attachment.
2022-11-08 19:31:57 +00:00
Peter Rotich
75c5cfe91e fetcher: Delayed Mailbox Expunction
As part of post-processing options, when fetching emails, osTicket can archive
(move) or delete (remove) a mail message from the server. When using mail
storage backend like IMAP - both operations requires `expunge` command to truly
commit the changes to the mail server.

That's all good and well, except Laminas Mail (our darling upstream lib)
expunges the mailbox in real-time when a message is moved or deleted. This
creates a problem for osTicket because **expunging causes message sequence
numbers to reset at the server end during the session!!** Which in turn causes
mismatch when we process the next message in the queued sequence. Additionally,
expunging on every message operation cases unnecessary traffic to the mail
server when every second counts.

To resolve the issue - until we switch to using `UIDs` -  this commit simply
bypasses Laminas Mail IMAP's removeMessage function so we can skip expunging on
message move/remove operations. osTicket expunges the mailbox at the end of
fetch session.
2022-11-08 19:22:05 +00:00
Peter Rotich
320981df67 fetcher: Improved Error / Warning Messages and Comments 2022-11-08 19:22:05 +00:00
JediKev
cd3c101246 Merge pull request #6355 from protich/issue/Sendmail+Windows
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-08 12:41:10 -06:00
JediKev
40f16dd2d6 Merge pull request #6350 from protich/issue/SMTP+QUIT
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-08 12:31:11 -06:00
JediKev
6ab019c161 Merge branch '1.17.x' into issue/SMTP+QUIT 2022-11-08 12:29:49 -06:00
JediKev
2b2f0a524e Merge pull request #6344 from protich/feature/fetchorder
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-08 12:25:18 -06:00
JediKev
78c4e5c617 Merge pull request #6307 from protich/issue/setup-session
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-08 12:23:31 -06:00
JediKev
9ab605e699 Merge pull request #6303 from protich/issue/3366
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-08 12:20:00 -06:00
JediKev
c02dce7468 Merge pull request #6302 from protich/feature/mail+plain
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-08 12:14:32 -06:00
JediKev
8c52e042f7 Merge pull request #6301 from protich/issue/mailer++
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-08 12:13:47 -06:00
Peter Rotich
bfccc82057 Merge pull request #6352 from JediKev/issue/mta-typo
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-11-08 11:28:28 -06:00
Peter Rotich
75e111cecc Merge pull request #6308 from JediKev/issue/file-upload-size
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-11-08 11:27:46 -06:00
Peter Rotich
39d7625882 Misc. Typo Fix 2022-11-08 17:23:58 +00:00
Peter Rotich
ac3855a4b5 sendmail: Strip "To" and "Subject" headers
This commit removes "to" and "subject" headers before headers are prepared and
passed to MTA. It's necessary since the headers in question are set directly
via PHP mail() function - leaving them results in duplicate headers.

This is a temp fix needed for Windows installs until we upgrade to the latest
version of Laminas Mail which already has the fix. The version we use currently
doesn't strip the headers on Windows.
2022-11-08 03:59:25 +00:00
JediKev
03eeb8e2b9 issue: MTA Typo
This addresses a small typo (6351) where `l` was being added to each
email string for the Default MTA setting. This removes the `l` so the
emails are displayed properly.
2022-11-07 16:40:21 -06:00
Peter Rotich
977cf9aafb typo: Fix Typo 2022-11-07 22:07:14 +00:00
Peter Rotich
dbeae227de SMTP: Dont send 'QUIT' on __destruct()
This commit attempts to avoid errors / exceptions that cannot be managed from PHP
userspace when an SMTP object is destructed. It also adds a connection timeout
of 5 minutes so old connections can be recreated.
2022-11-07 20:21:45 +00:00
JediKev
45f554adf6 issue: File Upload Size
This addresses an issue where if the Issue Details field or any custom
FileUpload field don't have `configuration` set any file upload to such
fields will fail with `File too large` error. This is due to the
`configuration` not being set so when we check the uploaded file size to
the allowed file `size` from the field's config via `$config['size']` it
fails. This adds a check for the `size` option in the `$config` array
and if not set or set to NULL then we will default to the system setting
`Agent Maximum File Size`.
2022-11-07 13:50:12 -06:00
JediKev
c145b2d8fd Merge pull request #4186 from JediKev/issue/system-timezone-var
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-11-07 11:53:08 -06:00
Peter Rotich
24338c2b5d fetcher: Max Fetch vs. Mail Box Count
This commit addresses a logic bug introduced in commit f1639d4123
2022-11-06 21:45:15 +00:00
JediKev
bc017ecdb5 Merge pull request #6343 from protich/issue/mailfetch_errors
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-03 16:42:16 -05:00
JediKev
268a246b19 Merge pull request #6342 from JediKev/issue/miscellaneous-fixes
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-03 16:41:49 -05:00
JediKev
ca02831c08 Merge pull request #6339 from protich/issue/email-status+upgrader
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-03 16:41:00 -05:00
JediKev
1b4ec3d0e8 Merge pull request #6333 from JediKev/issue/urldecode-dispatcher
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-03 16:40:48 -05:00
JediKev
a1977d9265 Merge pull request #6332 from JediKev/issue/plugin-config-item-exists
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-03 16:40:11 -05:00
JediKev
5c76bd8183 Merge pull request #6329 from JediKev/issue/information-sectionbreak-fields-on-edit
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-03 16:38:51 -05:00
JediKev
7899ae023a Merge pull request #6327 from JediKev/issue/favicons-login-headers
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-03 16:38:32 -05:00
JediKev
5784621fec Merge pull request #6324 from JediKev/issue/schedules-stops_on
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-03 16:38:05 -05:00
JediKev
cecab4a3c4 Merge pull request #6316 from aydreeihn/issue/assign-perm
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-11-03 16:36:42 -05:00
JediKev
5e970061b5 issue: No Ticket For checkStaffPerm()
This addresses an issue where there may be no `$ticket` available which
makes the `checkStaffPerm()` check fatally fail. This adds a check for
`$ticket` before checking the permissions. If no Ticket exists then we
set `Access Denied` error.
2022-11-03 16:09:59 +00:00
JediKev
c2a15b64c7 update: Static isValidFile()
As the name suggests this converts the `FileUploadField::isValidFile()`
method to static. This also updates the relevant calls to static format.
Lastly, this removes the check for `function_exists('exif_imagetype')` as we
now define this if it doesn't exist.
2022-11-03 16:09:59 +00:00
JediKev
9c280b49d6 searchable: Length Param
This adds a minor improvement to the `Format::searchable()` method by adding
a new param called `$length` that will allow you to pass in a specific
length to ensure the text is less than or equal to the supplied length. If
the text length is greater than the supplied length then it will return
`NULL` (ie. not searchable).
2022-11-03 16:09:59 +00:00
JediKev
3568418939 issue: Position CSS
This addresses an issue where some emails contain CSS `position` attributes
that can royally mess up the Ticket View. This adds REGEX to the
`Format::safe_html()` method that will remove any `position` attributes to
avoid rendering issues. This also adds a `position` attribute to the Thread
Entry div to ensure proper rendering.
2022-11-03 16:09:59 +00:00
JediKev
e036fd9a1e issue: Malformed Headers
This addresses an issue where some emails have malformed headers like `From:
"User Name" <email@domain.tld` which causes issues when processing.
This adds a check for malformed headers and if malformed then it will fixup
the header to prevent processing issues.
2022-11-03 16:09:59 +00:00
JediKev
5002bef9f3 issue: SelectionField Typeahead Validation
This addresses an issue where when validating a SelectionField's typeahead
value if there is no `$entry` the check will fail. This ensures `$entry`
exists and is set before using it in the subsequent checks.
2022-11-03 16:09:59 +00:00
Peter Rotich
f1639d4123 fetcher: Mail Fetch Order
osTicket currently fetches messages in LIFO order meaning the most recent messages get
fetched first. This was intentional to avoid possible stalemate when fetched
messages are left in the inbox (Do Nothing Option). However, this can creates
out of order threading issues since subsequest replies can be fetched first.

This commits addresses the issue by changing the logic so we can fetch older
messages first (FIFO order) as long as the numbers of the emails in the folder
being fetched are less than maximum messages to process at a time.
2022-11-03 10:13:56 +00:00
Peter Rotich
c92006e638 mail: moveMessage Return Value
This commit makes sure we're returning the correct result (boolean) when moving
a message. Laminas Mail (upstream) doesn't return a value when moving a message
- success is assumed unless we catch an exception.
2022-11-03 09:42:55 +00:00
Peter Rotich
22edf2b42e mailfetcher: Fetch vs. Archive Folders
This commit removes ambiguity by making it clear which folder we're referencing.
2022-11-03 07:56:56 +00:00
Peter Rotich
384aad1365 mailfetcher: Catch & Log Post Create Errors
This commit adds a try/catch to catch possible exceptions that might happen
post ticket create. This is necessary so we can isolate problematic emails from
segfaulting the entire process (fatal error) or cause it to skip expunging the
mailbox.

The commit also adds debug logging for such errors / exceptions.
2022-11-03 07:56:56 +00:00
JediKev
50bc1a2133 issue: exif_imagetype()
This addresses an issue where if someone doesn't install the PHP `exif`
extension, subsequent calls to `exif_imagetype()` returns `undefined`
errors. This checks to see if the method is defined and if not defines it.
2022-11-02 20:23:02 +00:00
Peter Rotich
aee25d69a5 upgrader: Email Account Status
This commit addresses an issue where we mistakenly disabled email account
status when upgrading to 1.17.x from older versions.

The change makes sure the status is maintained based on what's set on
hashtable.
2022-10-31 21:06:38 +00:00
Peter Rotich
db913bd385 cron: Cleanup Expired Sessions
This commit fixes cron session cleanup to use the new session model
2022-10-28 02:17:44 +00:00
Peter Rotich
c2be4ebf02 Misc. Fixes 2022-10-27 16:23:15 +00:00
Peter Rotich
f2c018409e session: Add onEvent Callback Support
This commit adds ability for session backends to report on certain events,
allowing us to keep app logic out of the session library.

The only event supported at the moment is "onClose"  - triggered right before
the session commits the session data.
2022-10-27 14:59:34 +00:00
Peter Rotich
da6673667e Add Basic Checks for Database Session Record
Make sure User Agent stays same and session id len is 32
2022-10-26 20:42:40 +00:00
Peter Rotich
9ab5b4f18f session: Regenerate session based on Max Idle Time
osTicket renegerates session id 10 seconds after login and that's it. This
commit add a timer to regenerate session id based on user's set max idle timeout
regardless of activity. If session timeout is disabled then it defaults to
every 24 hrs.
2022-10-26 20:17:59 +00:00
JediKev
c00b8e879a issue: urldecode Dispatcher
This addresses an issue where if using a webserver that encodes URLs by
default (like NGINX) some of the AJAX URLs will not resolve. This
ensures the URL is decoded with `urldecode()` before matching and
dispatching.
2022-10-26 14:47:56 -05:00
JediKev
fdf9e4127a issue: Plugin Config Item Exists
This addresses an issue where the Audit Log plugin shows Ticket Views in
the Audit Logs and shows the setting as enabled even when you disable
and save via plugin config. This is due to the current
`Config::exists()` method using the config `->get()` method and checking
the return value to determine `true` or `false`. This is problematic,
especially when values can be `NULL` or `0`. This updates the
`Config::exists()` method to check if the key exists in the session, in
the config array, or in the defaults. If the key does exist then we
return `true` otherwise we return `false`.
2022-10-26 14:45:55 -05:00
Peter Rotich
e2e6c0f436 session: Make Session Name Config Option
This commit adds session name as an option in config file. Also it
adds information about chaining session storage backends.
2022-10-26 19:37:36 +00:00
Peter Rotich
47df2b4409 session: use osTicket\Session Backends
This commit should have probably been broken into smaller commits but the
wholesale change was required since it's an overhaul of how osTicket handles
sessions.
2022-10-26 01:18:21 +00:00
Peter Rotich
f58e1c5d62 session: osTicket Session Backends
This commit adds unified & abstract osTicket Session Handlers / Storage
Backends to make it easy to add and extends storage backends.
2022-10-26 00:21:03 +00:00
JediKev
7b60c57653 issue: Information and SectionBreak Fields On Edit
This addresses an issue where Information and SectionBreak Fields do not
display on Ticket edit for Users and Agents. This is due to no
visibility option for Editable for these field types. Since these field
types don't have Editable visibility when we check for
`isEditableTo[Staff|Users]()` it returns false. This overwrites the two
methods for both field types so that they are visible on Edit if the
field is visible to the respective user-type (User/Agent).
2022-10-24 10:26:13 -05:00
JediKev
61655ec608 issue: Favicons Login Headers
This addresses a small issue where Favicons weren't appearing on the
Staff Login Page. This is due to the Staff Login Page using a different
header than other pages. This adds the Favicons to the Staff Login Page
Headers so that they appear correctly.
2022-10-19 14:48:28 -05:00
JediKev
e2dcbb6991 issue: Schedules stops_on
This addresses a small issue where we were calling `setTimestamp()`
instead of `getTimestamp()` which causes a Fatal Error. We didn't notice
this in previous versions due to pre-PHP 8 it was only a Warning not a
Fatal Error. This updates the line to call the correct method which
avoids any fatal errors.
2022-10-18 14:25:24 -05:00
aydreeihn
b4732b0523 Issue: Agent Permission to Assign Tickets
When we do $thisstaff->getDeptAgents, we are trying to get active agents $thisstaff can assign tickets to. The query done for this groups records in the staff_dept_access table by staff_id, so when we try to do getRoles() for $thisstaff, the only role we can get from staff_dept_access is the first record for $thisstaff.

This commit unsets the dept_access relation so that it can be requeried to get all staff_dept_access records for $thisstaff.
2022-10-13 17:09:55 -05:00
Peter Rotich
b6b63c9916 setup: Use Bootstrap:ini() to initialize session
This commit centralizes sessions init by defering it to Bootstrap:ini() -
when setup scripts needs to start an independent session (e.g cli installer)

Centralized session init is also important because, going forward, it will make it
easy to make changes in one place e.g when changing error_reporting setting
application-wide.
2022-10-10 22:24:44 +00:00
Peter Rotich
48e436f002 setup: Use Bootstrap:ini() to initialize session
This commit centralizes sessions init by defering it to Bootstrap:ini() -
when setup scripts needs to start an independent session (e.g cli installer)

Centralized session init is also important because, going forward, it will make it
easy to make changes in one place e.g when changing error_reporting setting
application-wide.
2022-10-09 22:06:44 +00:00
Peter Rotich
b943a95536 cli: Remove inline noop session handler
This commit removes noop session hadler set by manage.php for cli utils - cli utils generally don't require sessions and are disabled by default.
2022-10-09 21:53:55 +00:00
Peter Rotich
ced0ef4da3 session: Add Noop Session Backend
osTicket support uploading your own logos as well as login backgrounds and
calls logo.php (on both panels) to fetch the image in question. Previously we
didn't care about session data so session handler was set to an inline noop
function as a way to bypass starting a "real" session. The problem with the
approach is; if osTicket is hosted behind a proxy with multiple web severs in the backend,
the proxy needs to send all requests to the same server for specific user
session. To do so session name (OSTSESSID) is used to route requests from the same session
to the same backend server.

This commit adds a noop session backend allowing us to start a session while
still ignoring data writes. Noop backend is used when DISABLE_SESSION is
defined and set to false.
2022-10-09 20:46:40 +00:00
Peter Rotich
a9b5749656 email: Split Auth updates & getters into individual routines
This commit doesn't add anything useful other an splitting Auth updates & getters into individual routines per backend.
2022-10-09 05:24:01 +00:00
Peter Rotich
5efd9f14dd email: Make sure Email Account is Active 4realz!
This commit adds a check to make sure an Email Account has valid credentials
when determining if the account is active. It's necessary so we can detect
deleted credentials or expired refresh token in the case of accounts with
oauth2 backends.
2022-10-09 05:20:23 +00:00
Peter Rotich
b0e5ac83f2 smtp: Use proper name when saying What's Up (HELO/EHLO)
This commit addresses an issue  where outgoing email can get rejected when the
target mail server is tight-assed - requiring a valid host when saying
helo/ehlo.

We were setting the name to null which resulted in localhost being used as the
name. Now we're asking the OS to give us the proper hostname of the server -
hopefully the server admin has the correct hostname and reverse-lookup setup
correctly for better deliverability.
2022-10-09 00:04:37 +00:00
Peter Rotich
7c1b97b306 mail: Add plain:// encryption scheme hint
When configuring an email, starting with osTicket v1.17, the adminstrator can
hint to the desired encryption when using non-stantard ports by prefixing the
`hostname` with an encryption scheme  e.g `tls://` or `ssl://`. Using hints is
much better and less confusing than the old ways of presenting a user with
`IMAP` and `IMAP + SSL` as protocol selections - when infact we could use the
port number, in the backend, to determined the encryption.

This commit adds support for the opposite  - by allowing an admin to force
unencrypted connection even when using standard encryption ports by simply
adding `plain://` as the scheme hint.
2022-10-08 22:14:10 +00:00
Peter Rotich
23a80592e9 mail: Message-Id
This commit adds a routine to get Message-Id and also adds a check for an
invalid Id when trying to set a Message Id.

Please note that invalid ID are silently ignored since the upstram library
generates one.
2022-10-08 19:47:58 +00:00
Peter Rotich
845e500844 mailer: Spoofing & Originator
Spoofing: When an SMTP Account **DOES NOT** allow spoofing then set the Sender
(Envelope FROM) to the account's email address and leave the Header FROM as
set

Originator: When an SMTP account send fails - we now reset the Originator /
Sender to the original From Address, incase spoofing setting changed the
Sender

* Spoofing - when an SMTP Account does NOT allow spoofing then set the Sender
(Envelope FROM) to the account address and leave the Header FROM as set.

* SetOrinator - When an SMTP send mails we now reset the Originator / Sender to the original
From Address just incase spoofing setting had changed the Sender.
2022-10-08 19:42:59 +00:00
Peter Rotich
0564438c9c Merge remote-tracking branch 'upstream/1.17.x' into 1.17.x 2022-10-06 17:52:46 +00:00
Peter Rotich
7ef7dec03c Merge remote-tracking branch 'upstream/develop' into 1.17.x 2022-10-06 17:52:36 +00:00
JediKev
03f8813348 Merge pull request #6292 from JediKev/cli/user-import
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-10-06 12:32:34 -05:00
JediKev
1301db4935 Merge pull request #6297 from JensEB/patch-1
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-10-06 12:27:52 -05:00
JediKev
c431d26bb4 Merge pull request #6296 from protich/issue/Email_Message-Id
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-10-06 12:23:03 -05:00
Peter Rotich
60c8c8841e mailer: Add Sender Header
Sender is equivalent to -f from@address in mail() interface which get used as
envelope FROM address - this is important when header FROM is spoofed.
2022-10-06 16:57:39 +00:00
Peter Rotich
020fa0d3db mailer: Lint Fix
$type is a protected static upstream without a getter!
2022-10-06 16:05:12 +00:00
Peter Rotich
2223877c6a mailer: Add InReplyTo Header Interface
In-Reply-To: Unbreak My Soul
2022-10-06 03:46:08 +00:00
Peter Rotich
00981554be mailer: Add References Header
Use References Header to add references instead of using Generic Headers which might break due encoding
2022-10-06 03:46:08 +00:00
Peter Rotich
aaee6b0fde mailer: Add Return-Path Header Interface
This commits adds Return-Path Header interface to osTicket\Mail\Message
2022-10-06 03:45:59 +00:00
JensEB
ccd8445e97 Update class.mail.php 2022-10-05 20:18:54 +02:00
JensEB
b84547b0b6 POP3 Configration can not saved
First error: abstract functions can not be private. It have to be protected or public...

Second error: the pop3 login function doesn't respond anything on successful login. This will be return false in MailBoxProtocolTrait::basicAuth()
2022-10-05 14:17:09 +02:00
Peter Rotich
c08a8a8a21 mailer: Make Headers Valid Again
Message-Id: osTicket uses encoded message Id to thread emails on reply.
Laminas Mail, the new backend we're mangles up the Message-Id when set as a
generic header. This commits corrects the issue by using MessageId object -
making sure it is ASCII encoded.

Date: Date is auto-added upstream - so we're removing it from Mailer to
avoid double header issues - but if need be, you can overwrite it by setting
your own fake date!
2022-10-05 05:49:06 +00:00
JediKev
4c43e46f68 cli: User Import
This addresses issue 6291 where importing users from CSV via command
line causes a fatal error. This is due to attempting `fclose()` on a
stream that has already been closed. The CsvImporter `__descontruct()`
method already closes the stream so when we get to the end of the import
and attempt to call `fclose()` in the CLI method, it causes a fatal
error as the stream is no longer valid (as it's already been closed).
This adds a check to see if the stream is a valid resource and if so
then we close it, otherwise we skip and continue (as it's already
closed).
2022-10-03 15:34:55 -05:00
JediKev
1d8b79023e Add Release Notes for v1.17 2022-09-30 17:09:42 +00:00
JediKev
1eadd02354 Merge pull request #6290 from protich/issue/plugins-backwards-compatibility
Reviewed-By: aydreeihn <adriane@enhancesoft.com>, JediKev <kevin@enhancesoft.com>
2022-09-30 12:04:08 -05:00
Peter Rotich
3163972b1e plugin: Add getNamespace to Base 2022-09-30 15:30:40 +00:00
Peter Rotich
5787f9ae0d plugin: Add New Instance
This commit fixes a bug where blacklisted plugin cannot add a new instance post install.
2022-09-30 14:57:15 +00:00
Peter Rotich
7b26f6654a plugin: Make Plugin Base Class Nice
This commit addresses backward compatibility issue related to the data sent to
pre_save hook. Older plugins expected clean data whereas the new interface
provided to_db data which might be json formatted for example depending on the
config field database format. Now we will send the clean data to pre_save then
transform the results for database storage via the form / fields.
2022-09-30 14:48:28 +00:00
Peter Rotich
41b19db3f0 Merge pull request #6288 from protich/1.17.x
Reviewed-By: osTicket Devs <devs@osticket.com>
2022-09-29 12:46:21 -05:00
Peter Rotich
1b7f41aab3 Merge pull request #6287 from protich/1.17.x
Reviewed-By: osTicket Devs <devs@osticket.com>
2022-09-29 12:39:45 -05:00
Peter Rotich
65430abe20 Merge pull request #6285 from protich/issue/file-key-lookup
Reviewed-By: osTicket Devs <devs@osticket.com>
2022-09-29 12:39:20 -05:00
Peter Rotich
9ed39d9c95 Merge pull request #6284 from protich/issue/manage-users-password-policy
Reviewed-By: osTicket Devs <devs@osticket.com>
2022-09-29 12:38:37 -05:00
Peter Rotich
06e334fa89 Merge pull request #6283 from JediKev/issue/user-agent-password-reset
Reviewed-By: osTicket Devs <devs@osticket.com>
2022-09-29 12:38:15 -05:00
Peter Rotich
11c3b2e2b4 Merge pull request #6282 from JediKev/issue/imagesx-boolean
Reviewed-By: osTicket Devs <devs@osticket.com>
2022-09-29 12:35:08 -05:00
Peter Rotich
ce2b1ef3d4 Merge pull request #6281 from protich/issue/mailbox-exceptions
Reviewed-By: osTicket Devs <devs@osticket.com>
2022-09-29 12:34:44 -05:00
Peter Rotich
1f0a5ed086 Merge pull request #6279 from protich/issue/form-notice
Reviewed-By: osTicket Devs <devs@osticket.com>
2022-09-29 12:34:23 -05:00
Peter Rotich
08b7a18080 Merge pull request #6280 from protich/issue/misc-v116-fixes
Reviewed-By: osTicket Devs <devs@osticket.com>
2022-09-29 12:33:58 -05:00
Peter Rotich
423a0d74b1 Merge pull request #6278 from protich/issue/email-parse-error
Reviewed-By: osTicket Devs <devs@osticket.com>
2022-09-29 12:33:32 -05:00
Peter Rotich
39319e5b3d Merge branch '1.16.x' into 1.17.x 2022-09-29 17:25:45 +00:00
Peter Rotich
d98bbfcdfe Merge remote-tracking branch 'upstream/develop' into 1.16.x 2022-09-29 17:24:19 +00:00
Peter Rotich
d149ea0d42 v1.17 Misc. Fixes 2022-09-29 16:24:56 +00:00
Peter Rotich
21c71c6ed2 file: Possible Key Collision
This commit is a temporary fix for possible Fatal Crash when file key (hash
lookup) matches more than one object on lookup. See TODO note for more
information.
2022-09-28 22:45:28 +00:00
JediKev
5d06ee4618 issue: imagesx Boolean
This addresses an issue where if `imagecreatefromstring()` fails it
returns false (boolean) and we then pass that to `imagesx()`. This
method only accepts Gdimage (since PHP 8) so this adds a check to see if
`$image` can be set and if so we continue otherwise we skip the `if()`
completely and go straight to sending the data.
2022-09-28 21:36:06 +00:00
Peter Rotich
ebfc1d89f6 bug: Users Password Policy
Use the correct backend id on Users > Password Policy dropdown
2022-09-28 21:22:14 +00:00
JediKev
d3de159915 issue: User/Agent Password Reset
This addresses an issue where password reset is triggered even though the
user/agent doesn’t have a password or the authentication backend is
external.
2022-09-28 21:10:14 +00:00
Peter Rotich
81d5cb351b mail: Catch Possible Exceptions and Errors
This commit adds try/catch to MailBox related routines that shouldn't
necessarily affect email processing on failure.
2022-09-28 19:42:10 +00:00
Peter Rotich
4c0341e232 bug: Fix Sprintf Param placement 2022-09-28 17:40:15 +00:00
Peter Rotich
6e54e8106c forms: Add getNotice func. to DynamicFormEntry
This commit fixes a bug where getNotice was undefined when rendering a dynamic
form entry. It now delegates getting a notice, if any, from the form impl.
2022-09-28 17:33:31 +00:00
Peter Rotich
459520e8ed fetcher: Fail Safely on Email Parse Error
This commit adds ability to log email parse errors. This can happen if the
system fails to fetch complete raw email or when the email content is malformed
somehow.
2022-09-28 17:29:10 +00:00
Peter Rotich
e724ccb927 Merge pull request #6272 from protich/issue/ticket-denied
Reviewed-By: osTicket Team <devs@osticket.com>
2022-09-27 16:41:15 -05:00
Peter Rotich
5c5bd41e37 Merge pull request #6276 from protich/issue/filestoragebackend-errors
Reviewed-By: osTicket Team <devs@osticket.com>
2022-09-27 16:40:59 -05:00
Peter Rotich
1a47825aa6 Merge pull request #6271 from protich/issue/mailer-from-address
Reviewed-By: osTicket Team <devs@osticket.com>
2022-09-27 16:40:45 -05:00
Peter Rotich
b67f0a6e53 Merge pull request #6270 from protich/issue/attachments
Reviewed-By: osTicket Team <devs@osticket.com>
2022-09-27 16:40:26 -05:00
Peter Rotich
e8959f6af3 Merge pull request #6277 from aydreeihn/issue/plugin-inst-blacklist
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-09-27 16:38:50 -05:00
Peter Rotich
be178da5ca fetcher: Log Last Fetch on attempt
Log last fetch timestamp on login and before processing emails so that the
mailbox can yield to other mailboxes in the fetch queue.

Also catch Throwables instead of just Exceptions
2022-09-27 21:22:00 +00:00
Peter Rotich
e027dcc433 fetcher: Fetch Order based on Last Activity
Sort MailBoxes to fetch based on the last activity in ASC order. This is
necessary to give every mailbox a chance to be fetched.
2022-09-27 21:22:00 +00:00
Peter Rotich
79209144d1 fetcher: Catch Throwable
Catch Generic Exceptions / Errors
2022-09-27 21:21:07 +00:00
Peter Rotich
e685636ebc files: Catch possible Storage Backends Errors
Catch possible throwables (Exceptions and Errors) from file storage backends.
This is necessary so we don't crash while trying to save a file when storage
backend is having issues or bugs. osTicket uses database as fallback storage.
2022-09-27 21:07:20 +00:00
Peter Rotich
6932ea84cf Merge remote-tracking branch 'upstream/develop' into develop 2022-09-27 20:54:54 +00:00
aydreeihn
1ba8d37afc Issue: File Storage Plugins Blacklist
This commit will add the file storage plugins to the multi instance blacklist until we update them to offer multi instance support.
2022-09-27 14:47:28 -05:00
Peter Rotich
58f64e65b0 fetcher: TicketDenied Exception
Address a bug on how TicketDenied Exception is handled
2022-09-27 00:59:02 +00:00
Peter Rotich
75605a3fe9 mailer: Reset FROM Address when SMTP fails
This commit adds ability to reset FROM address when SMTP attempt fails. This is
important becacause of Spoofing Allowed setting.
2022-09-27 00:54:48 +00:00
Peter Rotich
2218aaca75 mailer: Skip Missing Attachment Files
This commit addresses an issue where mailer would crash due to missing
attachment files when sending out emails.
2022-09-26 14:25:25 +00:00
Peter Rotich
557d79f921 Merge pull request #6268 from ChrisTG742/patch-2
Reviwed-By: Peter Rotich <peter@osticket.com>
2022-09-25 23:12:53 -05:00
Chris
12a4cb093b Fix for https://github.com/osTicket/osTicket/issues/6267
Fix for Issue https://github.com/osTicket/osTicket/issues/6267, the order of the vars in line 691 was wrong.
2022-09-24 14:08:39 +02:00
JediKev
40d6ecbdd7 Merge branch 'develop' into 1.17.x 2022-09-23 18:59:18 +00:00
JediKev
edc7993289 Merge pull request #6262 from protich/issue/mailer-invalid-recipients
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-09-23 13:49:51 -05:00
Peter Rotich
d7d37c4cce Mailer: Change FromAddress to EmailAddress object + Spoofing
This commit refactors how FROM address is set and used within the Mailer. It's
now set as EmailAddress object - making it easy to get Email and Name
separately.

We're also removing Spoofing Check when determining possible SMTP accounts to
use for outgoing emails. It's now checked while attempting to send a Message.
If the account in question doesn't allow Spoofing then the FROM header is reset
to the accounts email address.
2022-09-23 07:37:56 +00:00
Peter Rotich
551f786464 email: EmailAddress:__toString()
This commit partially revers 5d799ecd to return Email when EmailAddress is
casted to String instead of Email Address (email+name)
2022-09-23 06:53:27 +00:00
Peter Rotich
046f432765 mailer: Skip Invalid Email Recipients
This commit adds ability for Mailer to skip invalid email recipients when
sending out an email. A Mailer warning with the recipient info (name & email
address) is logged.

For uniformity sake EmailContact interface now requires getEmailAddress() to be
defined downstream. This was necessary because some of the objects (e.g Staff)
return a name when casted to string (__toString) and changing it will affect
variables used in email templates and possibly leak the agents email.
2022-09-23 06:47:44 +00:00
Peter Rotich
b1e5386491 Merge pull request #6261 from JediKev/issue/undefined-var-orm
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-09-22 14:20:35 -05:00
JediKev
4009881ad8 issue: Undefined Variable ORM
This addresses an issue where a wrong variable was used which caused
undefined variable errors with the built-in tests. This corrects the
variable from `$sql` to `$exec->sql`.
2022-09-22 13:39:30 -05:00
JediKev
f8e6c327f1 Merge pull request #6258 from JediKev/issue/session-cookie-refresh
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-09-22 12:36:21 -05:00
JediKev
1630459a21 issue: Session Cookie Refresh
This addresses an issue where we were not renewing the cookie expired
time correctly when refreshing the session. This updates
`refreshSession()` to accept a `$refreshRate` defaulting to 60
(seconds). This also updates the `if()` statement from a negative to a
postive logic for better readability.
2022-09-22 12:19:31 -05:00
JediKev
d4ec4dd143 Merge pull request #6260 from protich/issue/plugin-instances
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-09-22 09:58:09 -05:00
JediKev
3a486600b4 Merge pull request #6259 from protich/issue/plugins-version
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-09-22 09:54:47 -05:00
JediKev
250f4195d1 Merge pull request #6257 from JediKev/issue/fix-upgrader-session-issues
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-09-22 09:47:12 -05:00
JediKev
9b3cd60bed Merge pull request #6256 from protich/1.17.x
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-09-22 09:43:09 -05:00
Peter Rotich
5607751671 plugins: Multi-Instance Blacklist
Some Plugins DO NOT or SHOULDN'T support multiple instances due design issues
or simply because of the fact that it doesn't make sense to do so.

The blacklist will be removed down the road once plugins are forced to declare
compatiple core osTicket version and if it supports multiple instances.
2022-09-22 02:20:51 +00:00
Peter Rotich
80bcc191d1 plugins: Make Plugins Upgradeable Again!
This adds ability and interface to auto-trigger plugins upgrade or downgrade
when there's a version mismatch on-load.

The base plugin class simply updates the version (for now) - the downstream
Plugins should implement the actual upgrade / downgrade process as needed,
especially if it creates database tables or needs to change stored config info
to match the new version format.
2022-09-22 01:47:09 +00:00
JediKev
311089a62e issue: Fix Upgrader Session Issues
This addresses session issues when upgrading from versions pre-1.7. This
bypasses the DBSessionBackend if `MAJOR_VERSION` is not defined or an
upgrade is pending and the `getDBVersion()` method returns `true` to
default to using the local session storage.
2022-09-21 15:55:15 -05:00
Peter Rotich
21e3a407dd smtp: Fix Crash on SMTP Error
This commit addresses a fatal error when system tries to log an error on SMTP account exception.
2022-09-21 20:32:59 +00:00
Peter Rotich
f3446599ea Merge pull request #6253 from ChrisTG742/patch-1
Reviewed-By: <peter@osticket.com>
2022-09-21 13:03:28 -05:00
Peter Rotich
5d799ecdf4 email: Add getEmail to EmailAddress
This commits adds getEmail to EmailAddress class to provide an interface for
osTicket Mailer to get the email address without the name.
2022-09-21 17:33:19 +00:00
Peter Rotich
6c2ecad220 mail: Fix Namespace Type 2022-09-21 16:52:32 +00:00
Peter Rotich
f10de7af90 Merge pull request #6254 from protich/issue/email_encoding
email: Set Default Email Message Encoding to UTF-8
2022-09-20 19:05:03 -05:00
Peter Rotich
f07d2c07b9 Merge pull request #6251 from protich/issue/mailfetch
Mail Fetcher Fixes
2022-09-20 19:04:50 -05:00
Peter Rotich
8954f05b6a mailer: Make Laminas Mail Happy Again
This commits split email and name to avoid the need for Laminas Mail to parse
the email address parts from a string - saving a couple of CPU cycles.
2022-09-20 14:31:28 +00:00
Peter Rotich
4656cf4cbc email: Set Default Email Message Encoding to UTF-8
This commits sets UTF-8 as the default encoding. It's necessary because - by
default Laminas Mail uses ASCII encoding - which can cause `invalid header
value` exception when an email address of the sender or a recipient has an
accented character.

Please note that individual Mine Parts set thier own encoding as needed - by
default osTicket uses base64 to encode `text/{plain/html}` parts to reduce the
overrall size of the Message - configuratable option to make `quoted-printable`
encoding an option is planned.
2022-09-19 23:49:10 +00:00
Peter Rotich
3decbe7863 Mail Fetcher: Handle Denied Tickets
This commit addresses a bug where the new Mail Fetcher failed to handle Denied
Tickets (via banlist/filter) correctly. In addition, email parsing is now
delagated to Mail Parser utility class.
2022-09-19 17:58:39 +00:00
Peter Rotich
1eebcd6661 email: Get Host Info from Email Account Setting
This commit addresees an issue where mail fetcher crashed when logging a
warning on excessive fetch errors. As part of the fix, Email AccountSetting is
now the single source of truth when it comes to Email Account information.
2022-09-19 17:58:39 +00:00
Chris
d5545b4e82 Update class.mail.php
Fix for typo in function auth(): expect need to be set to 334 instead of 234 here.
2022-09-19 13:29:54 +02:00
JediKev
a1a3d450b7 Merge branch 'develop' into 1.17.x 2022-09-15 19:31:29 +00:00
JediKev
ec331a549f Merge branch 'develop' into 1.16.x 2022-09-15 19:22:07 +00:00
JediKev
365a57ea3c Merge branch '1.15.x' into develop 2022-09-15 19:20:27 +00:00
Peter Rotich
0f81f5f3ac Merge pull request #6176 from JediKev/oops/jquery-ui-images
Reviewed-By:  Peter Rotich <peter@osticket.com>
2022-09-13 08:39:29 -05:00
JediKev
356e156ab2 Add Release Notes for v1.17-rc4 2022-09-12 19:53:59 +00:00
JediKev
6607facb6e Merge pull request #6249 from protich/issue/mail
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-09-12 14:48:13 -05:00
JediKev
1247609af9 Merge pull request #6246 from protich/1.17.x
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-09-12 14:45:48 -05:00
Peter Rotich
c71ddade63 mailer: Dropped Attachments
This commit fixes a bug that caused attachments to be dropped
2022-09-12 17:49:45 +00:00
Peter Rotich
2318714e25 plugin: Disable Multi-intance Support for 2FA plugin
Official 2FA Plugin  doesn't support multiple instances at the moment due to how the
plugin loads config info for versions <= 0.3
2022-09-12 03:11:14 +00:00
Peter Rotich
6de79a0bd4 mail: Mixed Mime Message with mixed multiparts
This commit addresses nesting issue when a message is mixed (with inline images
or attachments) and also has alternate content (plain & html).
2022-09-11 02:59:38 +00:00
Peter Rotich
f8f5d3dd7d 2fa: Fix 2FA Backend Registry
This commit updates 2FA Backend registery to match v1.17.x model
2022-09-08 21:09:39 +00:00
Peter Rotich
abbe1d2d99 Merge pull request #6230 from protich/feature/oauth2
Reviewed-By: osTicket Devs <devs@osticket.com>
2022-09-08 16:03:25 -05:00
Peter Rotich
c6ac4e7452 Merge pull request #6245 from JediKev/issue/dispatcher-include_once
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-09-08 16:02:44 -05:00
JediKev
dab490dc27 issue: Dispatcher include_once
This addresses issue 6218 where using `include` for things like
`api.tickets.php` fails with fatal error of `Cannot declare class xxx,
becuase the name is already in use`. This changes `include` to
`include_once` so that we avoid fatal errors and the files still get
included.
2022-09-08 11:20:42 -05:00
Peter Rotich
09197da959 Merge pull request #6241 from JediKev/php/8.1-support
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-09-08 11:19:53 -05:00
JediKev
dd2b9a4309 fix: Upgrader Issues
This addresses some various Upgrader issues when attempting to upgrade
from an older version (eg. `v1.6` and `v1.7`). This includes catching
more exception types, making a method static, adding some variable
checks before calling `count()`, checking for session ID before setting
custom handlers in the session class, and last (but not least) a
try-catch statement around `$__db->query()` calls so that we can catch
Excpetions without exiting.
2022-09-08 11:16:02 -05:00
Peter Rotich
eed1b66884 Merge pull request #6232 from jasplin/fix/storage-plugin-unhandledexception
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-09-08 10:59:35 -05:00
Peter Rotich
32877dcac0 Merge pull request #6243 from aydreeihn/issue/installer-yaml
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-09-08 10:58:12 -05:00
Peter Rotich
1388829286 email: Rename AccountOptions to AccountSetting
Options is a little confusing in the context used.
2022-09-08 08:26:36 +00:00
Peter Rotich
7123403e4f Merge pull request #6240 from JediKev/issue/searching-numbers
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-09-08 02:43:52 -05:00
Peter Rotich
774771cd8e Merge pull request #6235 from JediKev/issue/staff-ticket-open-session
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-09-08 02:43:07 -05:00
Peter Rotich
8f88a5513a Merge pull request #6239 from JediKev/issue/external-auth-regenerate-session
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-09-08 02:42:01 -05:00
Peter Rotich
5b76b887ac Merge pull request #6238 from JediKev/issue/unique-variable-names
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-09-08 02:40:46 -05:00
Peter Rotich
42e7b713f3 Merge pull request #6216 from JediKev/mailparse/thread-entry-recipients-to
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-09-08 02:39:05 -05:00
Peter Rotich
5717bdd427 Merge pull request #6233 from JediKev/issue/auth-backend-getBkId
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-09-08 02:38:31 -05:00
Peter Rotich
9f2ea7b0f9 Merge pull request #6228 from JediKev/installer/checking-existing-install
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-09-08 02:37:26 -05:00
Peter Rotich
9cb4408db5 Merge pull request #6234 from JediKev/plugins/audit-enable
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-09-08 02:36:39 -05:00
Peter Rotich
8e8d4f4fe1 Merge pull request #6242 from Wyrrrd/reflection
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-09-08 02:35:34 -05:00
Peter Rotich
2fa077aec7 plugins: Plugin Instance Config Class
This commit adds additional enhancements to allow Instances to dynamically set
Config Class. See e4f594c
2022-09-08 07:26:25 +00:00
Peter Rotich
481a047576 email: Validate Basic Auth Credentials
This commit adds validating when settting username and password.
2022-09-08 06:01:36 +00:00
JediKev
c24ee5753e issue: Fix Deprecation Warnings (line_endings)
This fixes the Deprecation warnings when attempting to set INI directive
`ini_set('auto_detect_line_endings', true)`. This directive is no longer
supported so this removes the calls completely.
2022-09-07 10:08:28 -05:00
JediKev
22b599e314 issue: Fix Deprecation Warnings (Serializable)
This addresses the Deprecation warnings when attempting to implement the
Serializable interface without defining the `__serialize()` and
`__unserialize()` methods. This updates the code for each class that
implements Serializable to define these methods and to call the original
methods we have.
2022-09-07 10:08:21 -05:00
Peter Rotich
5e53b1a131 issue: PasswordPolicyBackend
Use plugin getBkId() instead of getId() inline with plugins multi-instance
support.
2022-09-06 16:12:33 +00:00
aydreeihn
6780f29e74 Issue: Installer YAML
This commit fixes an issue where some default data was not being inserted into the database for fresh installs:

1. Sales Department
	The Sales Department was not being created because we were trying to set an sla_id before the SLA table was created.

2. Banlist
	The default Filter Rule and Action for the System Banlist was not being created because the save function now calls the ORM save function, so it is no longer calling save_rules and save_actions for the installer. We also have to change the YAML to refer to the fields as w,h,v instead of what, how, value for them to be recognized correctly.

It also fixes some spacing issues in the Filter class.
2022-09-06 09:51:53 -05:00
Peter Rotich
a706866844 plugins: Fix PluginConfig config load bug
This commit addresses an issue where Password Policy Plugin crashed on login
when password expiration is set to "Never Expire" and php mode set to strict.

This is because v1.17 changed how plugin instances config values are stored to match
the actual field format (using to_db), but on load - PluginConfig mistakenly
defaulted to database value when to_php returned an empty value - which is
indeed correct value when selection was empty option.
2022-09-03 01:10:44 +00:00
Peter Rotich
4ee44d4830 plugin: Fix message typo on instance update 2022-09-02 17:17:11 +00:00
Peter Rotich
76bf772568 email: Check if global error is set already on update failure 2022-09-02 16:59:43 +00:00
Peter Rotich
e4f594c2d3 plugins: Allow Plugin Instances to set Config Form
This is necessary to allow plugin instances to use different config forms as
desired depending on internal logic. For example OAuth2 plugin can have both
Authentication and Authorization instances which require slightly diffrent
fields and validation.
2022-09-02 16:50:24 +00:00
Peter Rotich
246c144513 basic-auth: Use updated PasswordField options
This commit refactors password filed in Basic Authentication form to utilize
the PasswordField correctly.
2022-09-02 16:47:18 +00:00
Peter Rotich
bc154434dd forms: Add ability to disable ChoiceField fields 2022-09-02 16:45:57 +00:00
Peter Rotich
681bea0839 forms: Add ability to add notice (warning) to forms
This commit adds ability to inject and display a warning message on dynamically
rendered forms. This is helpful when various components want to add such
warnings while not having full control of the display logic.
2022-09-02 16:38:03 +00:00
Wyrrrd
55f61390cc Replace hacky way with ReflectionClass 2022-09-01 14:37:56 +00:00
Peter Rotich
e705ff0f6c email: Add ability to stash / restore data
his is adds ability to stash data before doing external redirect when
configuring External Authorization e.g OAuth2
2022-08-29 21:35:53 +00:00
JediKev
e00003f68a issue: Searching Numbers
This updates the ticket search mechanism to check if the supplied keyword is
a number. If the keyword is a number we will only search Ticket Numbers to
improve search performance.
2022-08-29 15:22:56 +00:00
Peter Rotich
3024d7f50c smtp: Fix Configure Mailbox Authentication Error
This commit addreses an error that happens when "Same as Mailbox"
authentication is selected when configuring SMTP Account.
2022-08-29 05:35:54 +00:00
JediKev
8325226046 issue: External Auth Regenerate Session
This addresses an issue where we are rotating the session on each login
to help prevent session fixation. However, this causes an issue when
using an External Authentication Backend such as OAuth2. This remove the
`regenerateSession()` calls from the `login()` methods and instead we
will set a time bomb that will auto regenerate the session a certain
amount of time after login. This way external auth backends will still
function properly and we can still regenerate the session.
2022-08-26 11:38:44 -05:00
JediKev
0dc5a02e4a issue: Unique Variable Names
This addresses an issue where we are not checking the Field Variable
Names with case-insensitive comparison. This causes issues as the
database doesn't care about case and sees the two variables as the same.
This hardens our variable name checks to convert existing names and the
new name(s) to lowercase for case-insensitve comparison. With this
change it will no longer allow duplicate variable names (with a
difference of case).
2022-08-26 11:31:35 -05:00
JediKev
7796d60c1d plugins: Audit enable()
This addresses an issue where the Audit Plugin relies on `enable()`
being called in order to create the `_audit` table required for saving
audit data to the database. This adds the method back temporarily until
we can rewrite the plugin to fit the new standards. This also attempts
to call `enable()` on install so that the table can be created. If the
plugin doesn't have an `enable()` method it defaults to the parent class
Plugin `enable()` which just returns `true`.
2022-08-25 13:59:13 -05:00
JediKev
4f3d3cff7c issue: Staff Ticket Open Session
This addresses an issue where we are not regenerating the session for
Staff members after they've opened a Ticket. This adds
`$thisstaff->regenerateSession()` after creating a Ticket so that the
session is regenerated.
2022-08-25 13:27:31 -05:00
JediKev
29f01c090a issue: AuthenticationBackend getBkId()
This addresses an issue where we are calling `getId()` instead of
`getBkId()` in some places when registering and listing plugin instances. This causes
an issue if a User is auto-created via something like LDAP and the
`backend` is set to something like `ldap.client.p[id]i[id]`. When we get
available/registered backends we are not selecting the appropriate ID so
the backend is considered "not in the list" and does not let the User
authenticate. This changes all the calls to `getBkId()` to ensure we are
selecting the appropriate ID.
2022-08-25 10:42:25 -05:00
John Asplin
ca5932ac6f add try/catch to get inline email image 2022-08-23 17:42:49 +01:00
Peter Rotich
ab9a810181 email: Add routine to log fetch errors 2022-08-23 00:50:56 +00:00
Peter Rotich
bd300c44d3 Basic Auth: Remove Maxlength
This commit removes maxlength restriction of 30 added upstream by default to TextboxField.
2022-08-22 15:35:04 +00:00
JediKev
a3f2e24647 installer: Checking Existing Install
This addresses an issue with PHP 8.1 where on install we run a query to
see if we can select data from the ost_config table to see if there is
already an instance installed to avoid overwriting data. In PHP 8.1 this
will throw a fatal error which kills the installer. This adds a `try {}
catch() {}` statement around the call so that if it does throw an error
it continues without issues. This is what we want anyways, if the query
returns false or an error then we want to continue.
2022-08-19 10:54:03 -05:00
JediKev
34c64f14b4 Add Release Notes for v1.17-rc3 2022-08-05 14:56:56 +00:00
JediKev
e4db87635f Merge pull request #6217 from protich/feature/oauth2
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-08-05 09:49:50 -05:00
Peter Rotich
fdc5b0fa82 oauth2: Verify Config Signature
This commit adds ability to verify config signature when getting Access Token.
2022-08-04 21:04:10 +00:00
Peter Rotich
d9b41f4e5c plugin: Plugin Instance Config Signature
This commit addresses a bug where config signature routine used ksort
incorrectly - resulting in same signature everytime.
2022-08-04 21:04:10 +00:00
Peter Rotich
fa8dc24d0e email: OAuth2 Backend Change
Update OAuth2 backend on update since it could be changed on instance update.
2022-08-04 21:04:04 +00:00
Peter Rotich
265065c1d9 email: Port Password to the new Encryption Scheme 2022-08-04 19:13:30 +00:00
JediKev
a82af69845 mailparse: thread_entry_recipients to
This addresses an issue where some emails don't have a To header which
causes a fatal error when parsing mail. This is due to assuming To is set
and passing it to array_unique which expects the value to be an array. This
adds a check to see if To is set and if so we call array_unique otherwise we
default to an empty array.
2022-08-04 19:13:24 +00:00
JediKev
d821546a1d Add Release Notes for v1.17-rc2 2022-08-02 19:05:37 +00:00
JediKev
ca7d12fc49 Merge remote-tracking branch 'origin/1.17.x' into 1.17.x 2022-08-02 18:58:37 +00:00
JediKev
99e415f7a9 Merge pull request #6211 from protich/feature/oauth2
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-08-02 13:57:40 -05:00
Peter Rotich
4576a6ef07 Merge pull request #6200 from JediKev/issue/manually-sorted-topics
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-08-01 13:15:21 -05:00
Peter Rotich
55f3dd3c04 Merge pull request #6199 from JediKev/i18n/default-ticket-queue
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-08-01 13:14:14 -05:00
Peter Rotich
aef471b10a Merge pull request #6214 from JediKev/issue/queue-export-headings
Reviewed-By: Peter Rotich <peter@osticket.com>
2022-08-01 13:12:41 -05:00
Peter Rotich
4da6fb07a0 email: Don't load Instance Config when Auth is getting reconfigured. 2022-08-01 17:41:30 +00:00
Peter Rotich
90ed880b0b email: Check Auth Backend on Save
Make sure authentication backend is configured when selection is made, even if
the account is disabled.
2022-08-01 17:35:34 +00:00
Peter Rotich
72b2a2492a email: Use full Auth backend string
This commit also adds warning when reconfiguring auth backend.
2022-08-01 17:32:11 +00:00
JediKev
4744975840 issue: Queue Export Headings
This addresses an issue where updating the Queue Export Column Headings
doesn't actually save the changes. This is due to using the previous heading
regardless of the headings being passed in. This adds a check for the column
heading in `$fields` and if exists we use that otherwise we default back to
the previous heading.
2022-07-27 21:24:10 +00:00
Peter Rotich
8a8c1a162b plugin: Plugin Instance Config
This commit addresses an issue where the plugin cached the config of the first
instance loaded. The change delegates config loading to the instance when side
loading on bootstrap.
2022-07-27 18:28:32 +00:00
Peter Rotich
fa6cc0402b email: SMTP Open Relay
This commit addresses an issue where selecting "None - No Authentication" on
STMP setting resulted in a error of "Configure Authentication".
2022-07-27 16:36:15 +00:00
Peter Rotich
97a9b0af10 config: Accept Defaults
This commit makes it possible to pass default config values to Config class on init
2022-07-22 15:29:01 +00:00
JediKev
9f51d4673a Add Release Notes for v1.17-rc1 2022-07-20 18:56:41 +00:00
JediKev
e854a598e9 Merge pull request #6191 from protich/feature/oauth2
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <peter@osticket.com>, aydreeihn <adriane@enhancesoft.com>
2022-07-20 13:50:11 -05:00
Peter Rotich
163c28b56d email: External Auth Redirect
Use  window.location.href to redirect instead of href click
2022-07-20 03:30:57 +00:00
Peter Rotich
c3314a056b Email Delete
Delete email config and plugin instance on delete
2022-07-20 03:28:11 +00:00
Peter Rotich
bfb5b8a1f3 email: Fix Basic Authentication 2022-07-20 03:24:21 +00:00
Peter Rotich
d1ca71e5cb mailfetch: Fix email fetch bugs
This commit addresses email fetch bugs - thanks to @kevin
2022-07-20 03:17:30 +00:00
Peter Rotich
bfb808d6c6 email: Support Encrypting/Decrypting Credentials Secrets 2022-07-19 04:29:49 +00:00
Peter Rotich
47cb6a7b6f PluginInstance: Update signature routine
With the config moved to central config it's necessary to sort, platten the
array to json before getting md5 value.

Signature is used to detect configuratuion changes.
2022-07-19 04:24:28 +00:00
Peter Rotich
8367a03277 PluginConfig: Cache transcoded values locally
This is necessary for fields that chance e.g PasswordField getting decrypted.
Using upstream config store can result in decoded/decrypted values being saved
by mistake.
2022-07-19 04:21:14 +00:00
Peter Rotich
c1c7560305 forms: Change PasswordField encryption sub-key key
This commit also changes the length of the password placeholder to match the
actual length of the input/password.
2022-07-19 04:17:50 +00:00
Peter Rotich
87ac370efb forms: unset "unchanged" field on to_db
This is necessary so the backends can play blind and only update what changed
unless input is required.
2022-07-19 04:15:56 +00:00
Peter Rotich
2c5d114a3c Restore SCP Ajax Login
This commit reverts part of 7af5cf2c18 to restore
Ajax logins  - including scp/js/scj.js would be an overkill for the login page.
2022-07-14 21:09:34 +00:00
Peter Rotich
dff23a7e0d upgrader: Support Upgrading from v1.6 ST
This commit addresses issues related to upgrading osTicket prior to v1.7
2022-07-14 21:09:34 +00:00
Peter Rotich
95d35fc066 database: Add database patch to move config back to central config 2022-07-14 21:08:48 +00:00
Peter Rotich
88d6cfea48 patch: Remove ON UPDATE CURRENT_TIMESTAMP from datetime field 2022-07-14 21:01:39 +00:00
Peter Rotich
e7b1358e1f lint: Misc. Fixes 2022-07-14 21:01:39 +00:00
Peter Rotich
25d77b13a7 forms: Use AddErrors to inject errors to form object 2022-07-14 21:01:38 +00:00
Peter Rotich
f9c7c56f33 plugin: Mass Actions + destroy config on delete 2022-07-14 21:00:51 +00:00
Peter Rotich
e7d1449a4b email: Save Configuration via update on create 2022-07-12 14:36:34 +00:00
Peter Rotich
8bf4154e62 PasswordField: Add ability to set crypto sub-key 2022-07-12 14:24:49 +00:00
Peter Rotich
eb364ceda0 email: Validate Basic Auth 2022-07-12 13:55:24 +00:00
Peter Rotich
b77bdc1a4d plugin: Move plugin Config store to central config
Save plugin config data in central config by default.
2022-07-12 13:50:24 +00:00
Peter Rotich
2a50be2e27 email: Store username and password in central config
Move Basic Auth Credentials storage from email account to central config
2022-07-12 13:39:08 +00:00
JediKev
a80db8a7f7 issue: Manually Sorted Topics
This addresses issue 6197 where setting Help Topics sort to Manually throws
a fatal error. This is due to not checking if `$_POST['ids']` is an array
before passing it to `array_intersect()`. This adds a check to see if the
ids are an array and if not then `$diff` will be set to an empty array
(which is what `array_intersect()` returns if the arrays have no common
values).
2022-07-11 17:36:23 +00:00
JediKev
7a5dd0ebeb i18n: Default Ticket Queue
This adds the Help Tip for Default Ticket Queue setting in Admin Panel >
Settings > Tickets > Settings.
2022-07-11 16:00:30 +00:00
Peter Rotich
36b6e809ae oauth2: Get OAuth2 Credentials
Request oauth2 credentials specifically when configuring OAuth2 Client. This is
necessary because an account could be getting reconfigured.
2022-07-06 03:19:13 +00:00
Peter Rotich
48a1c022d7 plugin: Init All Installed Plugins on bootstrap
This is necessary so plugins can register interfaces that might be necessary to
add new an instance.
2022-07-06 03:19:13 +00:00
Peter Rotich
de02e8cf3a plugin: Bug fix
is_phar > isphar
2022-07-06 03:19:13 +00:00
Peter Rotich
61383cd788 tips: Add Email Authentication tooltip
This commits adds and adjusts email tooltips related to modern authentication.
2022-07-06 03:19:13 +00:00
Peter Rotich
35b457e9c5 mailer: Make sure SMTP Account are unique
This commit makes sure the SMTP accounts are unique and adds email address to
alert logged on error.
2022-07-06 03:19:13 +00:00
Peter Rotich
7e91724cee mail: Only set Content-Type when multipart
Content-Type only set when message is multi part
2022-07-06 03:19:13 +00:00
Peter Rotich
b7d99f9a31 lint: Add Laminas Mail Stubs 2022-07-06 03:19:13 +00:00
Peter Rotich
195577eba6 Highlight Tabs on Error
This pull request improves tabs highlighting on error to provide vitual
indication of errors on tabs that are not in focus.
2022-07-06 03:19:13 +00:00
Peter Rotich
df104e8c3a smtp: Outgoing Email Addresses
Include emails routing to System Default department - if the agent has access
to said department.
2022-07-06 03:19:13 +00:00
Peter Rotich
0d1d9ce06c Misc. Fixes 2022-07-06 03:19:13 +00:00
Peter Rotich
1af3ad416e Add Widget Field id to simple form field
This is necessary for visibility to work when somple form template use used to
print form fields.
2022-07-06 03:19:13 +00:00
Peter Rotich
f433b2f003 Log Alert before attemping to send out email 2022-07-06 03:19:13 +00:00
Peter Rotich
91a5932dec email: Add osTicket\Mail\Fetcher
This commit changes email fetching to use osTicket\Mail with Laminas Mail backend.
2022-07-06 03:19:13 +00:00
Peter Rotich
e4ec833b47 email: Add Email Account Table (v1.17 upgrade)
This commit adds email_account table and Model
upgrader: migrates settings from email table and drops columns.
2022-07-06 03:19:13 +00:00
Peter Rotich
8919d044b1 email: Add Modern Authentication (OAuth2) Support
Overhaul email Authentication / Configuration by adding plugable OAuth Authorization
2022-07-06 03:19:12 +00:00
Peter Rotich
7f95f4fd8f mailer: Add osTicket\Mail\Mailer
This commit changes outgoing emails to use osTicket\Mail with Laminas Mail backend
2022-07-06 03:19:12 +00:00
Peter Rotich
53cee5a0ab mail: Add osTicket\Mail Package
This commit add core mailbox & smptp classes and utils using Laminas Mail backend.
2022-07-06 03:19:12 +00:00
Peter Rotich
b139104005 Laminas: Add Laminas Mail Package
This increases osTicket core size significantly. Starting in 2.0 such packages will be added via composer
2022-07-06 03:19:12 +00:00
Peter Rotich
a91ee207dd auth: Add break and spacing to external auth buttons 2022-07-06 03:19:12 +00:00
Peter Rotich
7af5cf2c18 js: Make objectifyForm global
This is necessary when stashing form data
2022-07-06 03:19:12 +00:00
Peter Rotich
9ca4082c65 auth: Add OAuth2 Backends and osTicket\OAuth2
This commit adds OAuth2 Authentication and Authorization backends interface as well as AccessToken class.
2022-07-06 03:19:12 +00:00
Peter Rotich
ae80e27f30 Plugin: Add Config Signature
Add routine to get signature of the config store. This is important when detecting changes.
2022-07-06 03:19:12 +00:00
Peter Rotich
251150a5b1 Plugin: Add New Instance Options & Defaults 2022-07-06 03:19:12 +00:00
Peter Rotich
8cb6255338 Plugin: Add ability to pass vars when instantiating PluginConfig form
This is important when possible defaults are generated outside the form fields
context.
2022-07-06 03:18:51 +00:00
Peter Rotich
59d57c010d Plugin: Add Defaults to PluginConfig
Add ability to pass defaults to PluginConfig as well as make config cacheable
2022-07-01 22:30:03 +00:00
Peter Rotich
7ee1f406bc plugins: Add init routine
Used to initialize a plugin as part of bootstrapping process
2022-07-01 22:30:03 +00:00
Peter Rotich
8ceb3e8316 auth: Add Authentication Backends Lookup
This is useful to plugins looking for triggered backend.

This commit also adds 'id' to Auth Strike backends - needed to be successfully registered them.
2022-07-01 22:28:56 +00:00
Peter Rotich
5c2d68d548 Utils: Add routine to get base_url 2022-07-01 22:28:56 +00:00
Peter Rotich
0b93b48ec6 plugins: Support Multi-instance Services
Support ability to register multiple instances of the same backend
2022-07-01 22:28:56 +00:00
Peter Rotich
a2e78bf763 plugins: Register ClassLoader on init
Register namespace fallbacks when initializing plugins
2022-07-01 22:28:56 +00:00
Peter Rotich
9f2cc16e35 plugins: Clear side loaded config
Reset sideloaded config used to bootstrap instances
Fix compatible check
2022-07-01 22:28:56 +00:00
Peter Rotich
64c97ea6fc Plugins: Add Version Compatibility Check
This commit adds osTicket version compatibility check to plugins.
Plugins can now indicate required version of core osTicket by specifying
`ost_version` in plugin.php file.
2022-07-01 22:28:56 +00:00
Peter Rotich
a70bd13ae7 phar: Re-add Plugin Verification
Re-add and move plugin phar file verification to PluginManger since it's
a utility functionality.
2022-07-01 22:28:56 +00:00
Peter Rotich
3e96e5fdd6 plugin: Add Multi-instance Support
This commit adds multi-instance support for plugins
2022-07-01 22:28:56 +00:00
Peter Rotich
5d704ffe66 Backends Registry
This commits converts the backends to use the new unified Service
Registry.
2022-07-01 22:28:56 +00:00
Peter Rotich
7e3d4aaf84 JS: Confirm Action
Add util to prompt users for a confirmation when submitting a form
action. Also checks to make sure at least one checkbox is checked.
2022-07-01 22:28:56 +00:00
Peter Rotich
a0e87a053f Add Service Registry Util
This commit adds service registry abstract class
2022-07-01 22:28:56 +00:00
Peter Rotich
d99ff6e196 Controller: Add access check to Dispatcher
This commit adds a top level Controller to provide ability to check if
the caller has access.
2022-07-01 22:28:56 +00:00
JediKev
822a05fedb oops: jQuery UI Images
This adds the images missed in the jQuery UI upgrade. This addresses an
issue where the date picker doesn't show the arrows to skip months/years.
2022-06-01 17:01:41 +00:00
JediKev
e1487278b7 Add Release Notes for v1.16.3 2022-05-19 16:05:54 +00:00
JediKev
94444ab168 Merge branch '1.15.x' into develop 2022-05-19 16:01:11 +00:00
JediKev
0daca43afc Add Release Notes for v1.15.8 2022-05-19 15:53:55 +00:00
JediKev
1cf0201102 Merge pull request #6172 from JediKev/issue/search-and-lookup-improvements
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-05-19 10:47:16 -05:00
JediKev
1f940580bb issue: User Typeahead Lookup
This improves User Typeahaed lookup by switching from JOINS to UNIONS (for
performance reasons).
2022-05-19 15:39:34 +00:00
JediKev
a594d4b2bb issue: Email Search Improvements
This improves Email searching on full search. This ensures the search query
is emailish and if so searches only user emails. This improves sql query
execution time.
2022-05-19 15:38:51 +00:00
JediKev
3079c0a59e Merge pull request #6171 from JediKev/security/latest-security-patches-05-2022
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-05-19 10:27:57 -05:00
protich
57721def6a mpdf: Unregister PHAR
This unregisters PHAR only when calling WriteHTML instead of globally.
2022-05-19 15:22:46 +00:00
JediKev
45b6cf2e6a issue: Form Elements & Attribute
This addresses an issue discovered by the AppSec Research Team at Checkmarx
where the `action` attribute passes through sanitization successfully which
can be potentially harmful. In addition `<form>`, `<input>`, and `<button>`
elements can be maliciously crafted. This updates the htmLawed configuration
to deny the `action` attribute in all elements as well as denies all
`<form>`, `<input>`, and `<button>` elements.
2022-05-19 15:21:48 +00:00
Peter Rotich
85a76f403a Security: Session Fixation
This commit addresses possible session fixation on both agent and end user
login. Upon login users are now issued new session with the old one
invalidated.
2022-05-19 15:21:48 +00:00
JediKev
01a378f640 security: Unvalidated Password Change
This mitigates a vulnerability discovered by the AppSec Research Team at
Checkmarx where it's possible to update your Agent password without passing
a valid current password. This adds a check to compare the provided current
password with the actual current password to ensure they match. If they do
not match an error will be returned stating `Current password is
incorrect.`.
2022-05-19 15:21:48 +00:00
JediKev
334934ec6b xss: System Logs
This mitigates a vulnerability discovered by the AppSec Research Team at
Checkmarx where it's possible to perform Stored XSS via invalid CSRF tokens
in the System Logs. This is due to not properly sanitizing the CSRF content
before saving to the database as well as not properly escaping the content
before displaying in the UI. This adds `Format::sanitize()` to the content
before saving to the database as well as `Format::htmlchars()` before
displaying.
2022-05-19 15:21:48 +00:00
JediKev
a5c4d931e3 xss: Agent Directory
This mitigates a vulnerability discovered by the AppSec Research Team at
Checkmarx where it's possible to perform Reflected XSS in the Agent
Directory. This was due to the `order` and `sort` URL params not being
properly escaped. This adds `Format::htmlchars()` to both params to ensure
they are properly escaped.
2022-05-19 15:21:48 +00:00
JediKev
0a5f557b50 Merge pull request #6170 from JediKev/jquery/update-jquery-ui-1.13.1
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-05-19 10:20:29 -05:00
JediKev
9e30604fb6 Merge pull request #6164 from esamo/patch-3
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-05-19 10:18:21 -05:00
JediKev
b071fc4444 Merge pull request #6169 from JediKev/issue/check-user-status
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-05-19 10:15:58 -05:00
JediKev
3a2f072d63 issue: Check User Status
This is a rewrite of 6163 where we aren’t checking User/Agent status on
login and on session validation. This adds checks to the
UserAuthenticationBackend to ensure the User is active (ie. confirmed and
not administratively locked). This also adds checks to the
StaffAuthentciationBackend to ensure the Agent is active (ie. not locked).
If a User/Agent is not active they will be redirected to the login screen.
2022-05-19 15:11:56 +00:00
JediKev
542b4f0320 Merge pull request #6154 from socsimrishamn/develop
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-05-19 09:12:04 -05:00
JediKev
0ece428fd8 Merge pull request #6168 from JediKev/install/help-topic-disabled-fields
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-05-19 09:08:14 -05:00
JediKev
fbc05c6ead Merge pull request #6166 from JediKev/issue/redactor-freezing
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-05-19 09:07:11 -05:00
JediKev
c91585d1f3 Merge pull request #6160 from JediKev/issue/mpdf-table-print
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-05-19 09:06:37 -05:00
JediKev
81e99fe491 installer: Help Topic Disabled Fields
This addresses an issue where installing `1.16.x` has the Ticket Details
Form Fields disabled for every Help Topic. This is due to checking if the
Field IDs are in an empty array which obviously will return false. This
updates the declaration of `$fields` from `array()` to `null` if the array
index doesn’t exist. This also updates the `if` statement for the Field ID
checking to ensure the `$fields` variable is an array. This ensures that the
check is skipped if `$fields` is `null` avoiding erroneous disabled fields.
2022-05-17 18:51:13 +00:00
JediKev
42525acaa7 issue: Redactor Freezing
This addresses an issue where removing all text in Redactor in the Post
Reply box freezes the entire page (using 90% or more CPU). As a quick
workaround user jarnott from the Forum found that adding a `br` tag
between the `draft_id` input and the Post Reply `textarea` fixes the
issue. This adds the `br` tag to temporarily address the issue until
Redactor addresses the issue permanently.
2022-05-13 14:09:51 +00:00
Patryk Frey
026336948a Do not autocomplete new access fields of the (another) user
Otherwise these fields get autocompleted by browser and need to be emptied to save the form (for example I want only to block the account and save).
2022-05-09 22:07:09 +02:00
JediKev
38c0979e21 issue: mPDF Table Print
This addresses issue 6159 where tables are not appearing correctly in PDFs.
This is due to mPDF config revert when upgrading mPDF. This updates the
config `ignore_table_widths` to `true` so tables appear correctly.
2022-05-06 15:46:00 +00:00
Peter Rundqvist
612183cee6 Make strings localizable 2022-05-04 08:59:52 +02:00
Peter Rundqvist
4cc509b1f8 Make string localizable 2022-05-04 08:43:45 +02:00
JediKev
ed958d98bc jquery: Update jQueryUI 1.13.1
This is a rewrite of pull 6146 where it updates jQueryUI from `1.12.1` to
`1.13.1`. This also includes the updated CSS for jQueryUI `1.13.1`.
2022-04-26 20:36:12 +00:00
JediKev
39ab0c5b5c Add Release Notes for v1.16.2 2022-04-20 15:18:18 +00:00
JediKev
f4e71a9bf3 Merge branch '1.15.x' into develop 2022-04-20 15:09:53 +00:00
JediKev
26ab7e9791 Add Release Notes for v1.15.7 2022-04-20 14:57:45 +00:00
JediKev
16211325d4 Merge pull request #6141 from JediKev/issue/fixes-test-errors
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-04-20 09:53:14 -05:00
JediKev
34cf2f2024 issue: Fixes Test Errors
This addresses a small issue where there was a missing `(` parenthesis.
2022-04-20 14:50:18 +00:00
JediKev
c6ad36303d Merge pull request #6140 from JediKev/typo/emtpy-to-empty
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-04-20 09:39:46 -05:00
JediKev
f926bdc2db Merge pull request #6139 from JediKev/issue/sweden-capitalization
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-20 09:39:21 -05:00
JediKev
126e545fb7 Merge pull request #6138 from aydreeihn/issue/disabled-dept-access
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-20 09:38:58 -05:00
ntozier
9c858b88b1 typo: Emtpy To Empty
This fixes a typo in the Mail Folder help tip. This updates `emtpy` to
`empty`.
2022-04-20 14:31:33 +00:00
JediKev
3cb05ad14a issue: Sweden Capitalization
This capitalizes the word Svenska.
2022-04-20 14:24:37 +00:00
aydreeihn
0b7c9c2148 Issue: Disabled/Archived Primary Dept
This commit fixes an issue where we were only filtering out Private Departments from the Primary Department options for Agents when we should have been filtering out the Disabled and Archived Departments as well. This allowed trying to save an archived/disabled Department as the Agent's primary Department which threw an error.

Another issue was that if there was an error on dept_id, we didn't display the specific error message under the field, so you wouldn't know what to correct. Additionally, the error message for role_id was under the 'Fall back to primary role' checkbox instead of the Role field.

This problem was discovered through Issue #6135
2022-04-20 09:06:18 -05:00
JediKev
f427b66999 Merge pull request #6136 from JediKev/i18n/translatable-tooltip
Reviewed-By: JediKev <kevin@enhancesoft.com>
2022-04-19 17:43:01 -05:00
JediKev
c88ad0a050 Merge pull request #6137 from JediKev/i18n/no-tasks-string
Reviewed-By: JediKev <kevin@enhancesoft.com>
2022-04-19 17:42:36 -05:00
JediKev
01016dbc97 18n: No Tasks String
This adds translation support for the "no tasks" string.
2022-04-19 22:24:01 +00:00
empty88
b063504ed4 Update ticket-tasks.inc.php 2022-04-19 22:23:27 +00:00
JediKev
e2be9e780f i18n: Translatable Tooltip
This is a rewrite of pull 6123 where it adds translation support for client
side queue column sort tooltips. This breaks apart the tooltip strings into
two parts allowing `Sort By` and the column name to be translated
separately.
2022-04-19 22:06:32 +00:00
JediKev
20e9b3033c Merge pull request #6101 from JediKev/issue/maxlength-attribute
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-19 16:57:23 -05:00
JediKev
225eb7a143 issue: MaxLength Attribute
This addresses an issue reported on the Forum where Fields are not honoring
the Length value. This is due to the label being set to `length` while the HTML
attribute is named `maxlength`. This adds a case for `length` that sets the
attribute name to `maxlength`.
2022-04-19 21:51:19 +00:00
JediKev
7bb477ce67 Merge pull request #6134 from JediKev/i18n/bosnian-flag-css
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-19 14:59:45 -05:00
JediKev
39d05d74ea Merge pull request #6133 from JediKev/oops/banlist-created
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-19 14:59:24 -05:00
JediKev
5354293557 Merge pull request #6126 from empty88/patch-1
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-19 14:55:32 -05:00
JediKev
a5e11df622 Merge pull request #6124 from empty88/patch-12
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-19 14:53:57 -05:00
JediKev
69f73e9a4f Merge pull request #6122 from empty88/patch-9
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-19 14:50:13 -05:00
JediKev
1b9fdec655 Merge pull request #6109 from JediKev/issue/last-message-data-source
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-19 14:49:47 -05:00
JediKev
97e0d944b1 Merge pull request #6108 from JediKev/issue/filter-events
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-19 14:47:27 -05:00
JediKev
f9e4e12725 Merge pull request #6099 from JediKev/issue/task-export
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-19 14:44:17 -05:00
JediKev
8f1a34df8d Merge pull request #6096 from JediKev/issue/queue-sort
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-19 14:43:22 -05:00
JediKev
5a0bfea883 Merge pull request #6094 from cengizonkal/fix-staff-login-redirect-loop-when-offline
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-19 14:40:12 -05:00
JediKev
c727290132 Merge pull request #6087 from JediKev/issue/deprecated-required-parameter
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-19 14:38:21 -05:00
JediKev
f33e529818 Merge pull request #6071 from JediKev/issue/php-analyzer-each
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-19 14:32:02 -05:00
JediKev
14e4877ff7 Merge pull request #6018 from mlocati/fix-l10n-links
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-19 14:28:54 -05:00
JediKev
4f9dd9e7c8 Merge pull request #6008 from rmilecki/fix-thread-html-syntax
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-19 14:28:23 -05:00
JediKev
eb8436b89a Merge pull request #5913 from wmertens/patch-1
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-19 14:28:04 -05:00
JediKev
ad71b22e3c Merge pull request #5876 from gerundt/getHelpTopics-localize-allData
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-19 14:26:32 -05:00
JediKev
94f5e95701 i18n: Bosnian Flag CSS
This fixes the CSS for the Bosnian Flag for Lanugage Packs.
2022-04-18 14:12:03 +00:00
JediKev
f0ae76f4c1 oops: Banlist Created
This addresses an issue where the created date for banlist items show an
old, incorrect date. This is due to not setting a created date before saving
to the database.
2022-04-14 20:05:29 +00:00
JediKev
a77158b6ec issue: Nullable date() Timestamp
This addresses an issue where starting with PHP 8.0 the `date()` method
changed the `timestamp` parameter to be nullable. If the `timestamp` is
`NULL` then it will return the output from the `time()` method. So instead
of returning a default date of `1970-01-01T00:00:00+00:00` like it used to
it returns the current date. This is obviously a problem when it comes to
datetime based Queue Columns like Last Response with no value. Since there
is no value it gets translated to the current time and displays `just now`
in the columns instead of a blank column. This updates the `date()` call to
use `0` if the `timestamp` is `NULL` so that it returns the default datetime
of `1970-01-01T00:00:00+00:00` essentially restoring previous behavior.
2022-04-11 20:23:20 +00:00
cengiz
38b8f53354 fix staff login redirect loop when system is offline 2022-04-11 23:05:27 +03:00
Michele Locati
da1309a363 Fix markdown of jipt.i18n.osticket.com link 2022-04-11 20:57:14 +02:00
Michele Locati
d52cc40300 Use HTTPS version of Crowdin project 2022-04-11 20:57:04 +02:00
JediKev
0d417d3910 Merge pull request #6046 from davidglezz/patch-1
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-11 13:25:23 -05:00
JediKev
20955014f4 Merge pull request #6007 from rmilecki/email-formatting-space
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-11 13:20:03 -05:00
JediKev
7445c04f96 Merge pull request #5946 from Af0x/patch-1
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>, protich <peter@osticket.com>
2022-04-11 13:13:44 -05:00
empty88
84c4eb8503 Update ticket-view.inc.php 2022-04-03 13:20:12 +02:00
empty88
e6f437cdcb Update ticket-preview.tmpl.php
fix translation
2022-04-03 13:03:13 +02:00
empty88
8b5ea64068 Update edit.inc.php 2022-04-03 12:51:31 +02:00
JediKev
d59d695c6a Merge pull request #6115 from JediKev/issue/undefined-constant-glob_brace
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-03-28 14:44:02 -05:00
JediKev
bb9810a7a4 Merge pull request #6107 from JediKev/issue/pear-mail-parseaddresslist
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-03-28 14:33:40 -05:00
JediKev
ea6eeba9b4 Merge pull request #6089 from JediKev/tests/validation-checks
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-03-28 14:15:27 -05:00
JediKev
1a9a8027e7 Merge pull request #6088 from JediKev/tests/uninitialized-vars
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-03-28 14:14:21 -05:00
JediKev
9d249c45aa Merge pull request #6080 from JediKev/issue/preg_match-pass-by-reference
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-03-28 14:12:13 -05:00
JediKev
0499b97b73 issue: Undefined Constant GLOB_BRACE
This addresses issue 6079 where using `1.16.x` and PHP 8.0 on certain GNU
systems (eg. Alpine, Solaris, etc.) throws a fatal error of `Undefined
constant: "GLOB_BRACE"` when loading Plugins. This is due to `GLOB_BRACE`
not being available on said GNU systems. This removes `GLOB_BRACE` from
`include/class.plugin.php` completely as it is not used anyways.
2022-03-23 20:07:49 +00:00
JediKev
d185e509ff issue: Last Message Data Source
This addresses an issue reported on the Forum where the Last Message and
Last Response Data Sources are not showing the correct times. This is due to
an erroneous change made to support PHP 8.0. We added `isset()` to parts
where we are selecting array keys but didn’t add a check for the value. This
adds checks for the values to ensure we are continuing at the appropriate
times.
2022-03-21 21:54:55 +00:00
JediKev
8c9b392009 issue: Filter Events
This addresses issue 6059 where Ticket Filter processing stops accurately
but the Thread Events are blindly added for any Filters that match the
criteria. This is due to not checking the current Filter in the iteration
for `stopOnMatch()`. This adds a check for `stopOnMatch()` and if `true` we
will break the loop.
2022-03-21 20:51:21 +00:00
JediKev
713005612f issue: PEAR Mail parseAddressList()
This addresses an issue reported on the Forum where when
`Mail::parseRecipients()` is called it throws a fatal error of `Uncaught
ValueError: mb_convert_encoding(): Argument #2 ($to_encoding) must be a
valid encoding, "localhost" given`. This is due to an erroneous change when
adding PHP 8.0 support. We changed the called class from `Mail_RFC822` to
`Mail_Parse` but left the same arguments. This is obviously an issue as both
classes' methods take different arguments. This changes the called class
back to `Mail_RFC822` to stick to original functionality and updates it for
PHP 8.0 standards.
2022-03-21 19:55:33 +00:00
JediKev
baa46d35cf issue: Task Export
This addresses an issue reported on the Forum where exporting Tasks gives
you a fatal error. This is due to not checking if `$current` is set before
using it in `method_exists()`. This checks to make sure we have `$current`
set before using it.
2022-03-15 14:41:54 +00:00
JediKev
92e820c2d1 issue: QueueSort
This addresses an issue reported on the Forum where when adding a new Queue
Sort an error of `Uncaught TypeError: count(): Argument #1 ($value) must be
of type Countable|array, string given` is thrown. This is due to passing the
wrong arguments to the `QueueSort::update()` method. This corrects the
arguments to `$vars, $errors` to resolve the error.
2022-03-14 14:24:26 +00:00
JediKev
91e7d305d2 tests: Validation Checks
This addresses an issue where when running cli tests a validation check for
`jared@` failed. This is due to switching the calling class of
`parseAddressList()` from `Mail_RFC822` to `Mail_Parse`. This reverts the
calling class to `Mail_RFC822` to fix the issue.
2022-02-24 21:55:47 +00:00
JediKev
60f6ad718e tests: UninitializedVars
This addresses an issue found when running cli tests where two static
methods are using `$this`. This updates the methods to remove the use of
`$this` and updates remaining non-static calls to static.
2022-02-24 21:29:39 +00:00
JediKev
0d0ab46755 issue: Deprecated Required Parameter
This addresses a few issues reported on the Forum where some methods are
throwing `Deprecated: required parameter follows optional parameter` errors.
This updates the erroneous methods to match the PHP 8 standards using
explicit nullable types with defaults.
2022-02-24 20:50:02 +00:00
JediKev
f627a5ec67 issue: php_analyze each()
This cleans up the changes we did for the `each()` statements in
`class.php_analyze.php` as well as fixes reamining `each()`.
2022-02-17 22:10:12 +00:00
JediKev
148a2e70f7 issue: preg_match Pass By Reference
This addresses an issue where in PHP 8 you can no longer pass variables by
reference to preg_match. This moves the definition of the variable outside
the preg_match so it no longer complains.
2022-02-16 15:09:22 +00:00
JediKev
b42ddc7aeb Add Release Notes for v1.16.1 2022-02-15 21:21:54 +00:00
JediKev
b8e341aef0 Merge branch '1.15.x' into develop 2022-02-15 21:20:03 +00:00
JediKev
1a64b841f2 Add Release Notes for v1.15.6 2022-02-15 21:14:06 +00:00
JediKev
984ce78832 Merge pull request #6075 from JediKev/issue/user-sendUnlockEmail
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-02-15 15:06:54 -06:00
JediKev
7547fe3f49 Merge pull request #6068 from JediKev/issue/remaining-deprecated-each
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-02-15 15:06:08 -06:00
JediKev
02a930a6a8 Merge pull request #6066 from JediKev/issue/php-version-requirements-1.16.x
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-02-15 15:05:07 -06:00
JediKev
18198fd83f Merge pull request #6065 from JediKev/issue/email-templates-static
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-02-15 15:04:49 -06:00
JediKev
a99abcff9d Merge pull request #6064 from JediKev/issue/status-list-getSortModes
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-02-15 15:04:32 -06:00
JediKev
50c5f0a308 Merge pull request #6058 from dsoms/patch-1
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-02-15 15:04:10 -06:00
JediKev
69db3a72b1 issue: Remaining Deprecated each()
This addresses an issue reported on the Forum where a couple of `each()`
methods slipped through the cracks. This updates all the `each()` (minus the
php_analyzer file for now) to the new PHP 8 standards.
2022-02-15 20:46:01 +00:00
JediKev
5a2f8292e3 Merge pull request #4487 from JediKev/issue/required-cfield-user-reg
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <protich@enhancesoft.com>
2022-02-15 13:58:01 -06:00
JediKev
13652db2ad issue: User sendUnlockEmail
This addresses issue 6074 where `sendUnlockEmail()` is static but cannot be
as it uses `$this`. This updates the method to non-static and updates all
calls to non-static calls.
2022-02-15 00:44:01 +00:00
JediKev
4378f773f6 issue: Auth_SASL Non-Static
This addresses an issue where `Auth_SASL::factory()` is being called
statically but is not static. This updates the method to static so it can be
called statically with no issues.
2022-02-11 15:54:28 +00:00
JediKev
96995e1a52 issue: Banlist Non-Static
This addresses an issue where `Banlist::add()` and `Banlist::remove()` are
non-static methods that are being called statically. This updates the
methods to static so that they can be called statically.
2022-02-11 14:34:04 +00:00
JediKev
fdc044519c issue: PHP Version Requirements 1.16.x
This updates the PHP version requirements from `PHP version 8.0 (or
greater)` to `PHP version 8.0` until we can fully confirm PHP 8.1 is
supported.
2022-02-09 17:38:46 +00:00
JediKev
fb0b075f2c issue: Email Templates Static
This addresses an issue reported on the Forum where cloning a Template Set
gives you a fatal error of `Non-static method EmailTemplateGroup::add()
cannot be called statically`. This updates the `add()` method to static for
`EmailTemplateGroup` and `EmailTemplate` classes.
2022-02-09 17:34:50 +00:00
JediKev
6d4650d113 issue: Status List getSortModes()
This addresses issue 6063 where TicketStatusList does not have a
`getSortModes()` method and fatal fails when loading the status list. This
was done by `__call()` via `CustomListHandler` previously but we changed
`getSortModes()` to a static method to match PHP 8 standards. We didn't
account for the fact that `__call()` cannot call a static method as it uses
`$this` context. This renames the `getSortModes()` method for `DynamicList`
to `sortModes()`, creates a new method called `getSortModes()` which returns
the self `sortModes()`, updates `dynamic-list.inc.php` to call the method
non-statically and for `DynamicList` calls `sortModes()` statically, and
removes `static` keyword from `getSortModes()` on `CustomList` interface.
2022-02-09 17:17:56 +00:00
JediKev
27b8f55777 Add Release Notes for v1.16 2022-02-08 17:38:25 +00:00
JediKev
cf23b06767 Merge branch '1.15.x' into 1.16.x 2022-02-08 17:35:58 +00:00
JediKev
2ba1d8e5e0 Add Release Notes for v1.15.5 2022-02-08 17:27:30 +00:00
JediKev
d22cfaccdc Merge pull request #6060 from JediKev/issue/php8
Reviewed-By: JediKev <kevin@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-02-08 10:34:37 -06:00
JediKev
2767ad4ff7 prereqs: Update README.md
This updates the `README.md` file to mention that v1.16 requires PHP 8.
2022-02-08 16:13:51 +00:00
JediKev
57347ab4b6 upgrader: v1.16 Release
This adds an upgrade patch, updates the install-mysql.sql script, and
updates the core signature to trigger the v1.16 upgrade.
2022-02-08 15:44:48 +00:00
JediKev
0905e7bef7 release: Prep Release
This preps the installer/upgrader/bootstrap for the v1.16 release.
2022-02-08 15:33:19 +00:00
JediKev
0afe2b27df php8: Temporarily Suppress Warnings
This commit suppresses PHP 8 Warnings until we can go through and address
all warnings in the code.
2022-02-07 21:01:12 +00:00
JediKev
1eafd98063 issue: PEAR each()
This addresses a lingering `each()` error with the PEAR library. This
updates the `while(list() = each())` statement to a simple `foreach()` to
get rid of the error.
2022-02-07 16:19:28 +00:00
JediKev
03d203b9fa issue: Further Fixes
This includes further miscellaneous fixes found while running through a
basic installation. This includes calling non-static statically changes,
parameter must be of type array changes, and class not found changes.
2022-02-07 16:19:28 +00:00
JediKev
c4fc76c02f issue: EmailDataParser
This addresses the `Using $this when not in object context` error for
EmailDataParser and ApiEmailDataParser using PHP 8.
2022-02-07 16:19:28 +00:00
JediKev
998cd7d9fe issue: Even More Fixes
This includes even more miscellaneous fixes found while adding PHP 8
support. This includes incorrect capitalization of a class name and removing
unnecessary include statements.
2022-02-07 16:19:28 +00:00
aydreeihn
958a74813a PHP8: Static Method Lint Test
This commit adds a lint test to check the code for any static calls to a non-static function which throws a fatal error as of PHP 8.0.
2022-02-07 16:19:28 +00:00
aydreeihn
59dc587d46 Update MPDF
Update MPDF from version 7.1 to 8.0 to support PHP 8.
2022-02-07 16:19:27 +00:00
JediKev
ec9c09d027 issue: Misc. Fixes
This includes miscellaneous fixes found while adding PHP 8 support.
2022-02-07 16:19:27 +00:00
JediKev
3bb36ff4f3 issue: Static to Non-Static
This updates the static calls that need to be non-static.
2022-02-07 16:19:27 +00:00
JediKev
7668b718c3 issue: create_function()
This addresses the `Call to undefined function create_function()` errors.
2022-02-07 16:19:26 +00:00
JediKev
64076e2718 issue: Unparenthesized
This addresses the `Unparenthesized "a ? b : c ?: d"` warnings.
2022-02-07 16:19:26 +00:00
JediKev
69747343ee issue: Undefined Key, Var, Prop
This addresses the `Undefined array key`, `Undefined variable`, and
`Undefined property` errors.
2022-02-07 16:19:26 +00:00
JediKev
e261a5fb22 issue: Undefined Function each()
This addresses the `Call to undefined function each()` errors.
2022-02-07 16:19:26 +00:00
JediKev
a4ab19d361 issue: Calling Non-Static Statically
This addresses the `Non-static method XXX cannot be called statically`
errors.
2022-02-07 16:19:26 +00:00
Tim Gerundt
ab77c0afcf Fix deprecated implode() syntax for arrays 2022-02-07 16:19:25 +00:00
Tim Gerundt
29dcbd9000 Switch CryptoAlgo->exists() to real static function 2022-02-07 16:19:25 +00:00
Tim Gerundt
6b3c7f9f53 Use static keyword for some helper functions 2022-02-07 16:19:25 +00:00
Tim Gerundt
4d972b53d8 Use static keyword for JsonDataParser/JsonDataEncoder functions 2022-02-07 16:19:25 +00:00
Tim Gerundt
e63998e730 Array and string offset access syntax with {} is deprecated in PHP 7.4. Use [] instead. 2022-02-07 16:19:25 +00:00
JediKev
fc99cfc213 Merge branch 'develop' into 1.15.x 2022-02-07 16:15:18 +00:00
JediKev
92ba15d98f Merge branch '1.14.x' into 1.15.x 2022-02-07 16:11:40 +00:00
JediKev
a0b4c25d6a Merge pull request #6005 from protich/feature/authbackends_auto-register
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <protich@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-02-07 09:56:02 -06:00
JediKev
2b9e1a9829 Merge pull request #6057 from JediKev/issue/khmer-lang-flag
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <protich@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-02-07 09:55:19 -06:00
JediKev
79d51bed0b Merge pull request #6056 from JediKev/issue/assignee-item-property
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <protich@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-02-07 09:54:11 -06:00
JediKev
adc1e18559 Merge pull request #6054 from JediKev/issue/cdata-rebuild-issue
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <protich@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-02-07 09:49:15 -06:00
JediKev
fe69de4833 Merge pull request #6026 from JediKev/issue/plus-symbol
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <protich@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-02-07 09:46:14 -06:00
JediKev
d165ffd2c5 Merge pull request #6017 from JediKev/issue/plain-text-canned-responses
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <protich@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-02-07 09:43:41 -06:00
JediKev
aaa28ee1ee Merge pull request #6014 from JediKev/issue/imporved-validation-for-variable-names
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <protich@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-02-07 09:42:04 -06:00
JediKev
dcac452c3b Merge pull request #6013 from JediKev/redactor/upgrade-to-version-3.5.2
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <protich@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-02-07 09:41:09 -06:00
JediKev
549e243012 Merge pull request #5994 from JediKev/develop
Reviewed-By: JediKev <kevin@enhancesoft.com>, protich <protich@enhancesoft.com>, aydreeihn <adriane@enhancesoft.com>
2022-02-07 09:38:26 -06:00
David Soms
823b5fbba5 i18n: Missing translations for the action buttons 2022-02-03 21:20:39 +01:00
JediKev
f878f14480 issue: Khmer Language Flag
This addresses issue 6031 where the Khmer Language Flag is inaccurate. This
updates the `css/flags.css` file to adjust the pixel frame for
`.flag.flag-km`.
2022-02-03 16:17:38 +00:00
JediKev
3cde88a519 issue: Assignee Item Property
This addresses an issue where you cannot save a value for an Assignee field
that’s in the Item Properties of a List Item. This is due to the method used
to prepare the data for the backend as well as the format of the data when
returned from `to_database()`. This updates `to_php()` to `to_database()` to
get the appropriate data for the backend. This also updates `to_database()`
to return the JSON encoded data itself instead of in an array. Lastly, this
updates the `getChanges()` method to remove the need to account for an
array.
2022-02-02 19:46:46 +00:00
JediKev
9da3b5ea31 issue: CDATA Rebuild Issue
This addresses issues 6043 and 6053 where installing osTicket and doing a
User lookup will return a fatal error of `Unknown column 'A5.phone' in
'where clause'`. This is an indirect result of the changes we did to rebuild
CDATA tables post-installation. The issue is that we are using
`getInstance()` which returns a cached object. Changing this to always
return a new instance via `getNewInstance()` subsequently had issues with
the TicketForm not adding the Priority field. We determined this is due to
object caching in the ORM so we added `ModelInstanceManager::flushCache()`
before we rebuild the CDATA tables to clear the object cache and prevent the
reuse of old objects.
2022-01-27 21:50:34 +00:00
David Gonzalez
f72691c638 Fix slugify regex
a-z is inside A-z. A-z range also match [\]^_` chars
2022-01-11 23:18:37 +01:00
JediKev
b0f143ec38 issue: Plus Symbol
This updates the formula regex to allow `+` at the beginning of digit
characters like phone numbers.
2021-11-17 19:42:11 +00:00
arneschuldt
19c03ca0dd issue: Plain-Text Canned Responses
**Problem:**

In a non-rich-text setting, osTicket includes HTML elements if canned
responses are added to a response. [This problem has been discussed, for
instance, in a thread on the osTicket
forum.](https://forum.osticket.com/d/96411-agent-responses-show-html-markup)

**Background:**

The problem came in with commit a1116036af,
probably during an upgrade of the Redactor library. Since this commit, a
canned response loaded from the server always turns the response into a
rich-text response on the client side. However, the server (rightly) expects
non-rich-text content so that the response is malformatted later in this
process.

**Solution:**

The solution to fix this is quite easy. The response HTML element is
annotated the `.richtext` if it is meant to contain rich text. Hence, the
selector for Redactor can be extended with this class. Consequently, rich
text is only used if that is intended, i.e., no more malformatting with
unwanted HTML elements.
2021-11-02 18:56:54 +00:00
JediKev
35c8ca829c issue: Improved Validation for Variable Names
This addresses an issue where a variable name with `/` for example throws
databases errors when reindexing cdata tables. This improves the regex
validation for variable names to ensure alphanumeric characters only.
2021-11-01 14:06:13 +00:00
JediKev
4e4f82e6bd redactor: Upgrade to version 3.5.2
This upgrades Redactor from 3.5.1 to 3.5.2 so that we are utilizing the
latest code. This provides a few bug fixes and improvements.
2021-10-28 14:40:03 +00:00
Rafał Miłecki
84913f5a9e Fix HTML syntax in thread view
This fixes "span" element being closed twice.

Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
2021-10-24 17:27:12 +02:00
Rafał Miłecki
7692637bd3 email: use correct e-mail formatting
Add missing space between name and actual e-mail.
Before: foo<foo@bar.com>
After: foo <foo@bar.com>

Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
2021-10-24 16:51:44 +02:00
Peter Rotich
6836b17ef6 auth: Allow AuthBackends to auto-register Users
This commit allows AuthBackends (e.g via SSO) to auto-register Users
even when the registration is set to private.

The rationale is - Users already in such backends like LDAP or SAML are
already approved/registered internally.
2021-10-20 15:52:57 +00:00
JediKev
5c91f64d87 Revert "issue: List Item Properties On Mouseover"
This reverts commit a6a719239b as putting the
preview in the display messes up exports and multi-selection.
2021-10-08 16:01:50 +00:00
Af0x
dee6a13a55 Fixes permission issue when registration mode ist disabled 2021-08-08 21:21:58 +02:00
Wout Mertens
0d0d8a1a0b class.email: allow empty smtp_passwd when existing 2021-06-17 15:14:50 +02:00
Tim Gerundt
a078a0fc34 Issue: Topic->getHelpTopics() don't return localized names when $allData = true
The function Topic->getHelpTopics() don't return localized names when the parameter $allData is true.
2021-04-28 05:48:43 +02:00
JediKev
f4e693ef3e issue: Required Custom Field User Registration
This addresses an issue where failing to input data for a Required Custom
Field on the User Registration form will not return validation errors.
2018-08-28 17:34:28 -05:00
JediKev
d729810a96 oops: Fix System Timezone Variable
This addresses an issue where people are getting the incorrect time on
tickets even though they have all the timezones set correctly. The
current MySQL variable the system uses to determine the timezone is
`system_time_zone`. This variable is set by the server and sometimes
cannot be updated properly (or cannot be updated at all). This changes
the variable from `system_time_zone` to `time_zone` so if in the event
the system returns the incorrect time and they can’t update the server
timezone properly, they can just change the `@@global.time_zone` and
`@@session.time_zone` MySQL variables and should get the correct time.
2018-07-17 10:16:34 -05:00
1301 changed files with 143245 additions and 42792 deletions

2
.gitignore vendored
View File

@@ -7,6 +7,8 @@ setup/cli/modules/importer
.vagrant
._.DS_Store
Vagrantfile
# VS Code workspace files
*.code-workspace
# Staging directory used for packaging script
stage

View File

@@ -22,13 +22,14 @@ easy to setup and use. The best part is, it's completely free.
Requirements
------------
* HTTP server running Microsoft® IIS or Apache
* PHP version 7.2 to 7.4, 7.4 is recommended
* PHP version 8.2 - 8.4 (8.4 recommended)
* mysqli extension for PHP
* MySQL database version 5.5
* MySQL database version 5.5 (or greater)
### Recommendations
* gd, gettext, imap, json, mbstring, and xml extensions for PHP
* APC module enabled and configured for PHP
* ctype, fileinfo, gd, gettext, iconv, imap, intl, json, mbstring,
Zend OPcache, phar, xml, xml-dom, and zip extensions for PHP
* APCu module enabled and configured for PHP
Deployment
----------
@@ -58,33 +59,9 @@ Upgrading
---------
osTicket supports upgrading from 1.6-rc1 and later versions. As with any
upgrade, strongly consider a backup of your attachment files, database, and
osTicket codebase before embarking on an upgrade.
To trigger the update process, fetch the osTicket tarball from either
the osTicket [github](http://github.com/osTicket/osTicket/releases) page
or from the [osTicket website](https://osticket.com). Extract the tarball
into the folder of your osTicket codebase. This can also be accomplished
with the zip file, and a FTP client can of course be used to upload the new
source code to your server.
Any way you choose your adventure, when you have your codebase upgraded to
osTicket-1.7, visit the /scp page of you ticketing system. The upgrader will
be presented and will walk you through the rest of the process. (The couple
clicks needed to go through the process are pretty boring to describe).
### Upgrading from v1.6
**WARNING**: If you are upgrading from osTicket 1.6, please ensure that all
your files in your upload folder are both readable and writable to your
http server software. Unreadable files will not be migrated to the
database during the upgrade and will be effectively lost.
After upgrading, we recommend migrating your attachments to the database or
to the new filesystem plugin. Use the `file` command-line applet to perform
the migration.
php manage.php file migrate --backend=6 --to=D
View the UPGRADING.txt file for other todo items to complete your upgrade.
osTicket codebase before embarking on an upgrade. Please review our [Upgrade
Guide](https://docs.osticket.com/en/latest/Getting%20Started/Upgrade%20and%20Migration.html)
or the [UPGRADING.txt file](UPGRADING.txt) for upgrade instructions.
Help
----
@@ -101,18 +78,17 @@ the feature is published in your fork, send a pull request to begin the
conversation of integrating your new feature into osTicket.
### Localization
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/osticket-official/localized.png)](http://i18n.osticket.com/project/osticket-official)
[![Crowdin](https://badges.crowdin.net/osticket-official/localized.svg)](https://crowdin.com/project/osticket-official)
The interface for osTicket is now completely translatable. Language packs
are available on the [download page](https://osticket.com/download). If you
do not see your language there, join the [Crowdin](http://i18n.osticket.com)
do not see your language there, join the [Crowdin](https://crowdin.com/project/osticket-official)
project and request to have your language added. Languages which reach 100%
translated are are significantly reviewed will be made available on the
osTicket download page.
The software can also be translated in place in our [JIPT site]
(http://jipt.i18n.osticket.com). Once you have a Crowdin account, login and
translate the software in your browser!
The software can also be translated in place in our [JIPT site](http://jipt.i18n.osticket.com).
Once you have a Crowdin account, login and translate the software in your browser!
Localizing strings in new code requires usage of a [few rules](setup/doc/i18n.md).
@@ -123,18 +99,19 @@ file for the gory details of the General Public License.
osTicket is supported by several magical open source projects including:
* [Font-Awesome](http://fortawesome.github.com/Font-Awesome/)
* [HTMLawed](http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed)
* [jQuery dropdown](http://labs.abeautifulsite.net/jquery-dropdown/)
* [jsTimezoneDetect](http://pellepim.bitbucket.org/jstz/)
* [mPDF](http://www.mpdf1.com/)
* [PasswordHash](http://www.openwall.com/phpass/)
* [PEAR](http://pear.php.net/package/PEAR)
* [PEAR/Auth_SASL](http://pear.php.net/package/Auth_SASL)
* [PEAR/Mail](http://pear.php.net/package/mail)
* [PEAR/Net_SMTP](http://pear.php.net/package/Net_SMTP)
* [PEAR/Net_Socket](http://pear.php.net/package/Net_Socket)
* [PEAR/Serivces_JSON](http://pear.php.net/package/Services_JSON)
* [Font-Awesome](https://fontawesome.com/)
* [HTMLawed](https://www.bioinformatics.org/phplabware/internal_utilities/htmLawed)
* [jQuery dropdown](https://labs.abeautifulsite.net/jquery-dropdown/) (Project Deleted)
* [jsTimezoneDetect](https://pellepim.bitbucket.org/jstz/)
* [laminas-mail](https://github.com/laminas/laminas-mail)
* [mPDF](https://github.com/mpdf/mpdf)
* [PasswordHash](https://www.openwall.com/phpass/)
* [PEAR](https://pear.php.net/package/PEAR)
* [PEAR/Auth_SASL](https://pear.php.net/package/Auth_SASL)
* [PEAR/Mail](https://pear.php.net/package/mail)
* [PEAR/Net_SMTP](https://pear.php.net/package/Net_SMTP)
* [PEAR/Net_Socket](https://pear.php.net/package/Net_Socket)
* [PEAR/Serivces_JSON](https://pear.php.net/package/Services_JSON)
* [php-gettext](https://launchpad.net/php-gettext/)
* [phpseclib](http://phpseclib.sourceforge.net/)
* [Spyc](http://github.com/mustangostang/spyc)
* [phpseclib](https://phpseclib.sourceforge.net/)
* [Spyc](https://github.com/mustangostang/spyc)

View File

@@ -1,5 +1,33 @@
Welcome to osTicket 1.7
=======================
Upgrading osTicket
==================
osTicket supports upgrading from 1.6-rc1 and later versions. As with any
upgrade, strongly consider a backup of your attachment files, database, and
osTicket codebase before embarking on an upgrade.
To trigger the update process, fetch the osTicket tarball from either
the osTicket [GitHub](https://github.com/osTicket/osTicket/releases) page
or from the [osTicket website](https://osticket.com/download). Extract the tarball
into the folder of your osTicket codebase. This can also be accomplished
with the zip file, and a FTP client can of course be used to upload the new
source code to your server.
Any way you choose your adventure, when you have your codebase upgraded,
visit the /scp page of you ticketing system. The upgrader will
be presented and will walk you through the rest of the process. (The couple
clicks needed to go through the process are pretty boring to describe).
### Upgrading from v1.6
**WARNING**: If you are upgrading from osTicket 1.6, please ensure that all
your files in your upload folder are both readable and writable to your
http server software. Unreadable files will not be migrated to the
database during the upgrade and will be effectively lost.
After upgrading, we recommend migrating your attachments to the database or
to the new filesystem plugin. Use the `file` command-line applet to perform
the migration.
php manage.php file migrate --backend=6 --to=D
Some tasks are better left to a system administrator rather than a mindless
upgrade script. These are those remaining things that we'd rather you take
care of:

View File

@@ -1,3 +1,490 @@
osTicket v1.18.4
================
### Security
* security: Latest Patches 06/2026 (52c366f, 5afdf54, c54a6ac, 1e39bf1, feccb6a, 6eb6b98, 078516e, 98abb05, e52e010, fd96bba, 7bbd8ab, ba6217a, 580e1c8, b535782, 5963797, d590a97, eaebe01, b4cc092, d457c14, 5600f94, 5ff9795, 119cefe, b4ede88, 2a0c388, 6558b33)
osTicket v1.18.3
================
### Enhancements
* mPDF: Upgrade To v8.2.7 (39cdd2b1)
* htmLawed: Upgrade To 1.2.15 (877adf56)
### Security
* security: Latest Patches 01/2026 (c646c8cb, d1b634a9, 6852e719, 8db4d377, 1552cfa3, d832f243)
osTicket v1.18.2
================
### Improvements
* OAuth2: Show Scopes and Enforce Strict Mode (a77cf535, 390555db)
osTicket v1.18.1
================
### Improvements
* Update upgrade.php (9fd83eba)
* Update upgrade.inc.php (8c8a7fd1)
* update: PHP Requirements 1.18.x (1c0c670b)
* CLI: Make sure manage util can be executed via CLI (0caf5864)
* Update raphael-min.js to 2.3.0 (d4aeada1)
osTicket v1.18
==============
### Enhancements
* update: Laminas-Mail (66fa10af, bb45d37a)
### Improvements
* Database: Change Plugin Name to varchar(255) (aac546d0)
* update: MAJOR_VERSION (3eac42e5)
osTicket v1.17.8
================
### Security
* security: Latest Patches 06/2026 (52c366f, 5afdf54, c54a6ac, 1e39bf1, feccb6a, 6eb6b98, 078516e, 98abb05, e52e010, fd96bba, 7bbd8ab, ba6217a, 580e1c8, b535782, 5963797, d590a97, eaebe01, b4cc092, d457c14, 5600f94, 5ff9795, 119cefe, b4ede88, 2a0c388, 6558b33)
osTicket v1.17.7
================
### Enhancements
* mPDF: Upgrade To v8.2.7 (39cdd2b1)
* htmLawed: Upgrade To 1.2.15 (877adf56)
### Improvements
* join cdata table earlier (81ec2562)
* issue: Content Management Errors (2a4fe6ad)
* mail: SMTPUTF8 Backwards Compatibility (05b87199)
* issue: SVG MIME Type (a6309e22)
* Fix Banlist permission check (a7f14bf2)
* issue: POP OAuth2 Error REGEX (10f38bd0)
* issue: Kazakh Flag CSS (624c7171)
* Fixing Argentinian flag (924380a9)
* Update class.setup.php (8b6a0f05)
* Fixes for Implicitly marking parameters as nullable (573e8496, ca13d888)
* issue: Date Column Interval (1e38901e)
* issue: PGP + S/MIME Mail Parsing (9aff6b03)
* issue: Trim User Name (0fbafda1)
* issue: Microsoft UTF-8 BOM (5074c032)
* issue: Empty Dept Error On Topic Update (f1ba934b)
* Fix typo in 'Add New Instance' title (ae434418)
* Update class.file.php (1425fe1e)
* issue: Regex OR Operator (331ca48d)
* Revert "issue: Guest Session TTL" (c3819e4d)
### Security
* security: Latest Patches 01/2026 (c646c8cb, d1b634a9, 6852e719, 8db4d377, 1552cfa3, d832f243)
osTicket v1.17.6
================
### Improvements
* Fix PR #6283: DB column value of "local backend" is 'client' and not 'local' (7805cd94)
* i18n: Tasks Department ID Missing (00bc6b1b)
* issue: Checkbox Template Variable (09de9587)
* Fixed mailfetch error message (cd4717a1)
* issue: fix square characters being printed when printing tickets that's using languages like Thai (872a6492, 25844034)
* issue: Position Styling.. Again (7f03a64b, b843fb15)
* mysqli: Multi-Host Failure (0a8475fb)
* issue: Task Last Respondent (5277c7ab)
* i18n: Schedules getDays() (f2facda3)
* issue: Referred Closed Tickets (a7b0711d)
* php: Update Prerequisites (d331a44c)
* i18n: Lang URL Param (fece6ff7)
* mysql: Update Minimum Version (317ab967)
* Fix ajax path for AddRemoteCollaborator (6df6cd98)
* Fix staff mobile field class (f8455c82)
* patches: Latest 05/28/2024 (2d65cb77, 36b4c94f, 854cf47c, d9fe3ada)
* upgrade: Laminas-Mail (2.25.1) (c0a74162, 939a278c, f658268d)
* hotfix: Fix getRawEmail() (c26d840c, 1eb71197)
* issue: Linked Icon annotation (6c096cce)
* fix(email): fix case sensitivity in "Action" header retrieval (07843598)
* Upgrade mimeDecode.php from v1.48 to v1.5.6 2016-08-28 (cbd78ac8)
* mailer: sendmail() Failure (14e2057b)
* php: 8.3 Support (a3931f46, b38db372, fea5e1e0, 387a1c04, 136d372e)
* issue: Ticket Relations (9426b2dd)
* issue: Basic Search Selector (74b8bd0e)
* mail: Reapply Memory Optimization (eaaa64d9, ab76ffe8)
* php: iconv Recommendation (33ecc3a0)
* Ignore VS Code Workspace Files (0da2e0e0)
* issue: Email Remote Backend Name (b54dd584)
* issue: Safari Response Content Disposition (64a5df68)
* api: DueDate and Other Errors (14814ae2)
* issue: Multiselect List Export (f3bf8553)
* issue: Image Annotation (aeeb2850)
* user: List Import (fec70c4a)
* issue: Latest SQL Warnings (9bdfde5a)
* fields: Variable Name Validation (0724d1ad)
* issue: Transfer Empty Comments Var (ed87b257)
* update: Composer symfony/process (59f25918)
* issue: Client ACL Staff File Download (8255b2e7)
* update: PHP Deprecations & Warnings (84c14ace)
* issue: User Lookup Umlaut (504b0bfb)
* issue: Department Field User Import (5c2b6a5d)
* Fix force_https auto-enabling on settings page (3805bbfd)
* queue: getTotal Incorrect Counts (4f137dc2)
* issue: Referred Tickets Incorrect Queue Counts (b42cad6f)
* php: 8.4 Support (a4c0f674, b4938b99, 5e5a9ff5, fb9a39ba)
* issue: Canned Response Access (b930a68b)
* issue: Excessive Fetching Errors (c546a167)
* issue: Plain-text Base64 (216ded32)
* i18n: Crowdin API v2 (0ec6670d)
* issue: iFrame Logins (3a5da66b)
* issue: mimeDecode .eml Attachments (7fc3d8c2)
* patches: Latest 11/18/2024 (416b548b, 245e7554)
* Revert part of 0784359 commit (ec76a203)
### Security
* security: Latest Vulns 01/2025 (193f5fe0, ab6672fa)
osTicket v1.17.5
================
### Improvements
* update: README.md PHP & Laminas (4baf9dc8, 555c4a74, 88294e2d, 30a4a6e9)
* issue: putenv() Disabled (3c0d5f47)
* i18n: Update Signing (98706274, 776c0f27)
* i18n: Flags Position & Codes (3ee11112, 45f5b5b4)
* mailer: Undefined SmtpAccount::getName() (560a4f1d)
* issue: SystemSessionHandler (7e17daa6)
* issue: Status List Overflow (1deee342)
* faq: Category Notes Images (ccca0f59)
* patches: Latest 10/03/2023 (65e12297, 48e8501d, a8d42a9d, 4475e03e, bd034712, 64b8c81d, 070eefcb, 1775ce75)
* mail: NoValidateCert (d3c140ce)
* issue: Choice/Selection Field Searches (00e22fad)
* stubs: Add stubs to pass lint test (895c7236)
* issue: Canned Response Inline Images (a503c160)
* format: Viewable Images Bug (2a412883)
### Security
* issue: CSP Headers (6228f640)
* security: Latest Vulns 09/2023 (04f4e611, 88a87a33, c4ad48de, 37cf8350)
* issue: User Account Creation (777e6f0b)
osTicket v1.17.4
================
### Improvements
* issue: Thread Entry Actions z-index (215a0ce2)
* oauth2: Strict Matching Bug (e014ffd2)
* Make string translatable (1105cde7)
* issue: Inline-Images Canned Responses (4493b126)
* issue: Remove Old Login Code (e17ad463)
* issue: i18n Audit Exports (45dd7c4f)
* Bug: Custom File Upload Field Config (6371269a, 7283ac81, 902b5d35)
* issue: Duplicate User Copy/Paste Import (b304cdb)
* issue: Relocate Typeahead JS/CSS Files (62cd406)
* issue: strftime() Deprecation (3fe132c4)
* jquery: Update To 3.7.0 (ffa23da)
* issue: glob() Empty Array (c64a2611)
* issue: Email Only Attachment (9e45f3fa)
* installer: Change Email (a11aee29)
### Security
* security: Latest Vulns 06/2023 (86c2ba02, 69244175, 73b997a, ae37925, e4bfb00)
osTicket v1.17.3
================
### Enhancements
* oauth2: Strict Matching (7275d67b, b7718be4, ca913ba2, cb3625e8, f531718a, c0cd8b16)
* OAuth2 Resource Owner (5c239adb)
### Improvements
* session: Regenerate Session Id (d5853245)
* fetcher: Mail Fetcher / Parser Error Handling (d4d9c424, 0a4498b2)
* mysql: Support Sidecar Database Proxies (246aaa4d)
* Misc. Fixes (e6beeb9b, 24fd5075, eac9960a, 10af29a5)
* fetcher: Default Department (f410d4ec)
* issue: LDAP Multi-Instance Fatal Error (f6486044)
* issue: TicketDenied errno (6de7cf4c)
* session: User Logged Out (00d409d3)
* Uncaught Error: Call to a member function getId() on bool (af83896d)
* Mailer: Inline Images (7efbdfd2, 8815d087, 64a8abf3)
osTicket v1.17.2
================
### Improvements
* Fix old reference to SessionData (cf8b8ce7)
* Email Misc. Fixes (de620fe6, c19a5f03)
* session: Memcache Max TTL (ea3e03d2)
* mailer: Use Namespaced Mailer (133585f8)
osTicket v1.17.1
================
### Enhancements
* mail: Add plain:// encryption scheme hint (7c1b97b3, 39d76258)
### Improvements
* mailer: Make Headers Valid Again (c08a8a8a, aaee6b0f, 00981554, 2223877c, 020fa0d3, 60c8c884)
* POP3 Configration can not saved (b84547b0, ccd8445e)
* fetcher: Errors Handling & Logging (384aad13, 22edf2b4, c92006e6)
* upgrader: Email Account Status (aee25d69)
* issue: Plugin Config Item Exists (fdf9e412)
* mailer: Improvements & Enhancements (845e5008, 23a80592, 977cf9aa)
* smtp: Use proper name when saying What's Up (HELO/EHLO) (b0e5ac83)
* email: Make sure Email Account is Active 4realz! (5efd9f14, a9b57496)
* Session++ (ced0ef4d, b943a955, b6b63c99, f58e1c5d, 47df2b44, e2e6c0f4, 9ab5b4f1, da667366, f2c01840, c2be4ebf, db913bd3)
* setup: Use Bootstrap:ini() to initialize setup dir sessions (48e436f0)
* issue: Plugin Config Item Exists (fdf9e412)
* upgrader: Email Account Status (aee25d69)
* fetcher: Errors Handling & Logging (384aad13, 22edf2b4, c92006e6)
* fetcher: Mail Fetch Order (f1639d41, 24338c2b)
* SMTP: Dont send 'QUIT' on __destruct() (dbeae227)
* issue: MTA Typo (03eeb8e2)
* sendmail: Strip "To" and "Subject" headers (ac3855a4)
* Mail Parse Error Handling (320981df, 75c5cfe9, a4e36d39, ba2d31cd, 30ad9cd4, 678098d8, 46b7899b, f85f9036)
* Issue/session revisited (49d91b05, 4fc5f5cc)
osTicket v1.17
==============
### Enhancements
* email: Set Default Email Message Encoding to UTF-8 (4656cf4c, 8954f05b)
* plugins: Multi-Instance Blacklist (56077516)
* mailer: Reset FROM Address when SMTP fails (75605a3f)
* Issue: File Storage Plugins Blacklist (1ba8d37a)
* mail: Catch Possible Exceptions and Errors (81d5cb35)
### Improvements
* Update class.mail.php (d5545b4e)
* Mail Fetcher Fixes (1eebcd66, 3decbe78)
* 1.17.x Bug Fixes (6c2ecad2, 5d799ecd, 21e3a407)
* plugins: Make Plugins Upgradeable Again! (80bcc191)
* mailer: Skip Invalid Email Recipients (046f4327, 551f7864, d7d37c4c)
* issue: Wrong Variable Order (12a4cb09)
* mailer: Skip Missing Attachment Files (2218aaca)
* fetcher: TicketDenied Exception + Throwable (58f64e65, 79209144, e027dcc4, be178da5)
* files: Catch possible Storage Backends Errors (e685636e)
* fetcher: Fail Safely on Email Parse Error (459520e8)
* forms: Add getNotice func. to DynamicFormEntry (6e54e810)
* bug: Users Password Policy (ebfc1d89)
* v1.17 Misc. Fixes (d149ea0d)
* plugins: Make Plugin Base Class Play Nice (7b26f665, 5787f9ae, 3163972b)
osTicket v1.17-rc4
==================
### Enhancements
* v1.17: Bug Fixes & Enhancements (bd300c44, ab9a8101, 3024d7f5, e705ff0f, 681bea08, bc154434, 246c1445, e4f594c2, 76bf7725, 4ee44d48, a7068668, 5e53b1a1, 481a0475, 2fa077ae, 13888292)
### Improvements
* mail: Mixed Mime Message with mixed multiparts (6de79a0b, c71ddade)
* 2fa: Fix 2FA Backend Registry (f8f5d3dd, 2318714e)
* issue: Dispatcher include_once (dab490dc)
* plugins: Audit enable() (7796d60c)
* issue: AuthenticationBackend getBkId() (29f01c09)
osTicket v1.17-rc3
==================
### Improvements
* Oauth2 / Modern Email Auth Latest Fixes (fdc5b0fa, d9b41f4e, fa8dc24d, 265065c1)
osTicket v1.17-rc2
==================
### Improvements
* Oauth2 / Modern Email Auth Latest Fixes (4da6fb07, 90ed880b, 72b2a249, 8a8c1a16, fa6cc040, 97a9b0af)
osTicket v1.17-rc1
==================
### Enhancements
* Oauth2 / Modern Email Authentication Support (d99ff6e1-163c28b5)
osTicket v1.16.6
================
### Improvements
* session: Collab View Ticket (6afe0efa)
* issue: Static forTask() (1079011f)
* issue: Featured FAQs (8df09581)
* print: Hidden Thread Events (f63fc67d)
* issue: Does Not Have A Value (d47fc2b1)
* issue: Disabled Queues Run Counts (3ff3ce93)
* issue: Queue Counts Incorrect (2428cc58)
* jquery: Update jQueryUI 1.13.2 (b896f70d)
* issue: Department Export (5418cf20)
* jquery: Update To 3.6.3 (8d39a4cc)
* i18n: make-pot (74274b5c)
* update: README PHP Version (6087bc55)
* issue: mPDF Font Files (427a8aa6)
* issue: FileUpload False (8dd2ab61)
* Add translation for "last resort" standard column labels (cb528044)
* add translation for task status (46c005a0)
* issue: getRealPath() Stub (32a229aa)
### Security
* security: Latest Security Patches 03/2023 (86f9693d, 091ddba9, ec604393, daee20fd, 9fb01bc1, 619ce0f6, 343a2b47, 8a55c29f, e2c8f799, ee7ce3d9, 263f71a3)
osTicket v1.16.5
================
### Improvements
* Fix trusted proxy wildcard check (04c0bb3a)
* issue: Html Typo (50dfe931)
* mailparse: Parse filenames with complex format (c8067212)
* validator: Email Domain Verification (3c4a20a8)
* user: Make sure User has a valid Email on create (55fbfcb5)
osTicket v1.16.4
================
### Enhancements
* i18n: Default Ticket Queue (7a5dd0eb)
* mailparse: thread_entry_recipients to (a82af698)
* issue: Unique Variable Names (0dc5a02e)
* php: 8.1 Support (22b599e3, c24ee575, dd2b9a43)
* issue: Session Cookie Refresh (1630459a)
### Improvements
* oops: jQuery UI Images (822a05fe)
* issue: Manually Sorted Topics (a80db8a7)
* issue: Queue Export Headings (47449758)
* installer: Checking Existing Install (a3f2e246)
* Catch storage error when mailing ticket response (ca5932ac)
* issue: Staff Ticket Open Session (4f3d3cff)
* issue: External Auth Regenerate Session (83252260)
* issue: Searching Numbers (e00003f6)
* Replace hacky way with ReflectionClass (55f61390)
* Issue: Installer YAML (6780f29e)
* issue: Fix Upgrader Session Issues (311089a6)
* issue: Undefined Variable ORM (4009881a)
* v1.16: Misc Bug Fixes (4c0341e2)
* issue: User/Agent Password Reset (d3de1599)
* issue: imagesx Boolean (5d06ee46)
* file: Possible Key Collision (21c71c6e)
* cli: User Import (4c43e46f)
* issue: Miscellaneous Fixes (50bc1a21, 5002bef9, e036fd9a, 35684189, 9c280b49, c2a15b64, 5e970061)
* issue: urldecode Dispatcher (c00b8e87)
* issue: Information and SectionBreak Fields On Edit (7b60c576)
* issue: Favicons Login Headers (61655ec6)
* issue: Schedules stops_on (e2dcbb69)
* Issue: Agent Permission to Assign Tickets (b4732b05)
* issue: File Upload Size (45f554ad)
### Security
* security: Username XSS (5213ff13)
* security: Refresh URL (3702a4fb)
osTicket v1.16.3
================
### Improvements
* installer: Help Topic Disabled Fields (81e99fe4)
* Do not autocomplete new access fields of the (another) user (02633694)
* issue: mPDF Table Print (38c0979e)
* Make string localizable (4cc509b1, 612183ce)
### Security
* mpdf: Unregister PHAR (57721def)
* issue: Form Elements & Attribute (45b6cf2e)
* Security: Session Fixation (85a76f40)
* security: Unvalidated Password Change (01a378f6)
* xss: System Logs (334934ec)
* xss: Agent Directory (a5c4d931)
osTicket v1.16.2
================
### Improvements
* Issue: Topic->getHelpTopics() don't return localized names when $allData = true (a078a0f)
* class.email: allow empty smtp_passwd when existing (0d0d8a1)
* Fixes permission issue when registration mode ist disabled (dee6a13)
* email: use correct e-mail formatting (7692637)
* Fix HTML syntax in thread view (84913f5)
* Fix slugify regex (f72691c)
* issue: preg_match Pass By Reference (148a2e7)
* issue: php_analyze each() (f627a5e)
* issue: Deprecated Required Parameter (0d0ab46)
* tests: UninitializedVars (60f6ad7)
* tests: Validation Checks (91e7d30)
* issue: QueueSort (92e820c)
* issue: Task Export (baa46d3)
* issue: PEAR Mail parseAddressList() (7130056)
* issue: Filter Events (8c9b392)
* issue: Last Message Data Source (d185e50)
* issue: Undefined Constant GLOB_BRACE (0499b97)
* Update edit.inc.php (8b5ea64)
* Update ticket-preview.tmpl.php (e6f437c)
* Update ticket-view.inc.php (84c4eb8)
* issue: Nullable date() Timestamp (a77158b)
osTicket v1.16.1
================
### Improvements
* issue: Remaining Deprecated each() (69db3a7)
* issue: User sendUnlockEmail (13652db)
* issue: Auth_SASL Non-Static (4378f77)
* issue: Banlist Non-Static (96995e1)
* issue: PHP Version Requirements 1.16.x (fdc0445)
* issue: Email Templates Static (fb0b075)
* issue: Status List getSortModes() (6d4650d)
osTicket v1.16
==============
### Enhancements
* PHP8: Static Method Lint Test (958a748)
* Update MPDF (59dc587)
### Improvements
* prereqs: Update README.md (2767ad4)
* upgrader: v1.16 Release (57347ab)
* release: Prep Release (0905e7b)
* php8: Temporarily Suppress Warnings (0afe2b2)
* issue: PEAR each() (1eafd98)
* issue: Further Fixes (03d203b)
* issue: EmailDataParser (c4fc76c)
* issue: Even More Fixes (998cd7d)
* issue: Misc. Fixes (ec9c09d)
* issue: Static to Non-Static (3bb36ff)
* issue: create_function() (7668b71)
* issue: Unparenthesized (64076e2)
* issue: Undefined Key, Var, Prop (6974734)
* issue: Undefined Function each() (e261a5f)
* issue: Calling Non-Static Statically (a4ab19d)
* Fix first problems with PHP 8 (ab77c0a, 29dcbd9, 6b3c7f9, 4d972b5, e63998e)
osTicket v1.15.8
================
### Enhancements
* issue: Check User Status (3a2f072d)
* jquery: Update jQueryUI 1.13.1 (ed958d98)
### Improvements
* issue: Redactor Freezing (42525aca)
### Security
* mpdf: Unregister PHAR (57721def)
* issue: Form Elements & Attribute (45b6cf2e)
* Security: Session Fixation (85a76f40)
* security: Unvalidated Password Change (01a378f6)
* xss: System Logs (334934ec)
* xss: Agent Directory (a5c4d931)
osTicket v1.15.7
================
### Enhancements
* i18n: Bosnian Flag CSS (94f5e95)
* i18n: Translatable Tooltip (e2be9e7)
* i18n: No Tasks String (01016db)
### Improvements
* Use HTTPS version of Crowdin project (d52cc40, da1309a)
* fix staff login redirect loop when system is offline (38b8f53)
* oops: Banlist Created (f0ae76f)
* issue: MaxLength Attribute (225eb7a)
* Update ticket-tasks.inc.php (b063504)
* Issue: Disabled/Archived Primary Dept (0b7c9c2)
* issue: Sweden Capitalization (3cb05ad)
* typo: Emtpy To Empty (9c858b8)
* issue: Fixes Test Errors (34cf2f2)
osTicket v1.15.6
================
### Enhancements
* i18n: Missing translations for the action buttons (823b5fb)
### Improvements
* issue: Required Custom Field User Registration (f4e693e)
osTicket v1.15.5
================
### Enhancements
* redactor: Upgrade to version 3.5.2 (4e4f82e)
* auth: Allow AuthBackends to auto-register Users (6836b17)
### Improvements
* issue: Khmer Language Flag (f878f14)
* issue: Assignee Item Property (3cde88a)
* issue: CDATA Rebuild Issue (9da3b5e)
* issue: Plus Symbol (b0f143e)
* issue: Plain-Text Canned Responses (19c03ca)
* issue: Improved Validation for Variable Names (35c8ca8)
* Revert "issue: List Item Properties On Mouseover" (5c91f64)
osTicket v1.15.4
================
### Enhancements
@@ -16,22 +503,6 @@ osTicket v1.15.4
* xss: Advanced Search (4a8d3c8)
* xss: Tasks (b01c6a2)
osTicket v1.14.8
================
### Improvements
* issue: Auto-Assign Comments Var (c3171c3)
* issue: List Item Properties On Mouseover (a6a7192)
* issue: def_assn_role (6ad568f)
* search: Child Thread Relation (08785f9)
### Security
* security: PwReset Username and Username Discoverability (e282910, 86165c2)
* security: SSRF External Images (1c6f98e)
* xss: Stored XSS/Domain Whitelist Bypass (4b4da5b)
* security: Recipient Injection via User's Name (7c5c584)
* xss: Advanced Search (4a8d3c8)
* xss: Tasks (b01c6a2)
osTicket v1.15.3.1
==================
### Improvements
@@ -107,6 +578,22 @@ osTicket v1.15.1
* readme: Update PHP Version (a4c85d7)
* placeholder: Quote and encode html chars (0056d14)
osTicket v1.14.8
================
### Improvements
* issue: Auto-Assign Comments Var (c3171c3)
* issue: List Item Properties On Mouseover (a6a7192)
* issue: def_assn_role (6ad568f)
* search: Child Thread Relation (08785f9)
### Security
* security: PwReset Username and Username Discoverability (e282910, 86165c2)
* security: SSRF External Images (1c6f98e)
* xss: Stored XSS/Domain Whitelist Bypass (4b4da5b)
* security: Recipient Injection via User's Name (7c5c584)
* xss: Advanced Search (4a8d3c8)
* xss: Tasks (b01c6a2)
osTicket v1.14.7
================
### Enhancements

View File

@@ -60,7 +60,7 @@ elseif ($_POST) {
$_POST['email'] = $thisclient->getEmail();
}
if (!$user_form->isValid(function($f) { return !$f->isVisibleToUsers(); }))
if (!$user_form->isValid(function($f) { return $f->isVisibleToUsers(); }))
$errors['err'] = __('Incomplete client information');
elseif (!$_POST['backend'] && !$_POST['passwd1'])
$errors['passwd1'] = __('New password is required');
@@ -125,6 +125,8 @@ elseif ($_POST) {
}
break;
}
// Rotate CSRF on successful POST
$ost->getCSRF()->rotate();
}
if ($errors && $user && $user != $thisclient)

View File

@@ -48,5 +48,5 @@ $dispatcher = patterns('',
))
);
Signal::send('ajax.client', $dispatcher);
print $dispatcher->resolve($ost->get_path_info());
print $dispatcher->resolve(Osticket::get_path_info());
?>

View File

@@ -15,14 +15,20 @@
**********************************************************************/
file_exists('../main.inc.php') or die('System Error');
/*
* Why API_SESSION ??
* It indicates that the session is API - which session handler should handle as
* stateless for new sessions.
* Existing session continue to work as expected - this it's important for
* SSO authentication, which uses /api/auth/* endpoints. Such calls are not
* stateless.
*
*/
define('API_SESSION', true);
// APICALL const.
define('APICALL', true);
// Disable sessions for the API. API should be considered stateless and
// shouldn't chew up database records to store sessions
if (!defined('DISABLE_SESSION'))
define('DISABLE_SESSION', true);
require_once('../main.inc.php');
require_once(INCLUDE_DIR.'class.http.php');
require_once(INCLUDE_DIR.'class.api.php');

View File

@@ -13,15 +13,9 @@
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
// Use sessions — it's important for SSO authentication, which uses
// /api/auth/ext
define('DISABLE_SESSION', false);
require 'api.inc.php';
# Include the main api urls
require_once INCLUDE_DIR."class.dispatcher.php";
$dispatcher = patterns('',
url_post("^/tickets\.(?P<format>xml|json|email)$", array('api.tickets.php:TicketApiController','create')),
url('^/tasks/', patterns('',
@@ -29,8 +23,8 @@ $dispatcher = patterns('',
))
);
// Send api signal so backend can register endpoints
Signal::send('api', $dispatcher);
# Call the respective function
print $dispatcher->resolve($ost->get_path_info());
print $dispatcher->resolve(Osticket::get_path_info());
?>

View File

@@ -18,10 +18,10 @@ ini_set('memory_limit', '256M'); //The concern here is having enough mem for ema
@chdir(dirname(__FILE__).'/'); //Change dir.
require('api.inc.php');
//Only local piping supported via pipe.php
// Only local piping supported via pipe.php
if (!osTicket::is_cli())
die(__('pipe.php only supports local piping - use http -> api/tickets.email'));
require_once(INCLUDE_DIR.'api.tickets.php');
PipeApiController::process();
PipeApiController::process('cli');
?>

View File

@@ -28,4 +28,4 @@ require_once INCLUDE_DIR.'/class.dispatcher.php';
$dispatcher = new Dispatcher();
Signal::send('ajax.client', $dispatcher);
print $dispatcher->resolve($ost->get_path_info());
print $dispatcher->resolve(Osticket::get_path_info());

View File

@@ -21,9 +21,7 @@ class Bootstrap {
session_cache_limiter('nocache');
#Error reporting...Good idea to ENABLE error reporting to a file. i.e display_errors should be set to false
$error_reporting = E_ALL & ~E_NOTICE;
if (defined('E_STRICT')) # 5.4.0
$error_reporting &= ~E_STRICT;
$error_reporting = E_ALL & ~E_NOTICE & ~E_WARNING;
if (defined('E_DEPRECATED')) # 5.3.0
$error_reporting &= ~(E_DEPRECATED | E_USER_DEPRECATED);
error_reporting($error_reporting); //Respect whatever is set in php.ini (sysadmin knows better??)
@@ -45,11 +43,29 @@ class Bootstrap {
}
date_default_timezone_set('UTC');
if (!function_exists('exif_imagetype')) {
function exif_imagetype ($filename) {
if ((list($width,$height,$type,) = getimagesize($filename)) !== false)
return $type;
return false;
}
}
if (!function_exists('exif_imagetype')) {
function exif_imagetype ($filename) {
if ((list($width,$height,$type,) = getimagesize($filename)) !== false)
return $type;
return false;
}
}
if (!isset($_SERVER['REMOTE_ADDR']))
$_SERVER['REMOTE_ADDR'] = '';
}
function https() {
static function https() {
return osTicket::is_https();
}
@@ -125,6 +141,7 @@ class Bootstrap {
define('SLA_TABLE', $prefix.'sla');
define('EMAIL_TABLE',$prefix.'email');
define('EMAIL_ACCOUNT_TABLE', $prefix.'email_account');
define('EMAIL_TEMPLATE_GRP_TABLE',$prefix.'email_template_group');
define('EMAIL_TEMPLATE_TABLE',$prefix.'email_template');
@@ -133,6 +150,7 @@ class Bootstrap {
define('FILTER_ACTION_TABLE', $prefix.'filter_action');
define('PLUGIN_TABLE', $prefix.'plugin');
define('PLUGIN_INSTANCE_TABLE', $prefix.'plugin_instance');
define('SEQUENCE_TABLE', $prefix.'sequence');
define('TRANSLATION_TABLE', $prefix.'translation');
define('QUEUE_TABLE', $prefix.'queue');
@@ -150,7 +168,7 @@ class Bootstrap {
define('TIMEZONE_TABLE',$prefix.'timezone');
}
function loadConfig() {
static function loadConfig() {
#load config info
$configfile='';
if(file_exists(INCLUDE_DIR.'ost-config.php')) //NEW config file v 1.6 stable ++
@@ -180,7 +198,7 @@ class Bootstrap {
define('SESSION_TTL', 86400); // Default 24 hours
}
function connect() {
static function connect() {
#Connect to the DB && get configuration from database
$ferror=null;
$options = array();
@@ -191,19 +209,29 @@ class Bootstrap {
'key' => DBSSLKEY
);
if (!db_connect(DBHOST, DBUSER, DBPASS, $options)) {
$ferror=sprintf('Unable to connect to the database — %s',db_connect_error());
}elseif(!db_select_database(DBNAME)) {
$ferror=sprintf('Unknown or invalid database: %s',DBNAME);
$hosts = explode(',', DBHOST);
foreach ($hosts as $host) {
$ferror = null;
try {
if (!db_connect($host, DBUSER, DBPASS, $options))
$ferror = sprintf('Unable to connect to the database — %s', db_connect_error());
elseif (!db_select_database(DBNAME))
$ferror = sprintf('Unknown or invalid database: %s', DBNAME);
} catch (mysqli_sql_exception $e) {
$ferror = sprintf('Database error — %s', $e->getMessage());
}
// break if no error
if (!$ferror) break;
}
if($ferror) //Fatal error
if ($ferror) //Fatal error
self::croak($ferror);
}
function loadCode() {
static function loadCode() {
#include required files
require_once INCLUDE_DIR.'class.util.php';
include_once INCLUDE_DIR.'class.controller.php';
require_once INCLUDE_DIR.'class.translation.php';
require_once(INCLUDE_DIR.'class.signal.php');
require(INCLUDE_DIR.'class.model.php');
@@ -214,14 +242,13 @@ class Bootstrap {
require(INCLUDE_DIR.'class.crypto.php');
require(INCLUDE_DIR.'class.page.php');
require_once(INCLUDE_DIR.'class.format.php'); //format helpers
require_once(INCLUDE_DIR.'class.validator.php'); //Class to help with basic form input validation...please help improve it.
require(INCLUDE_DIR.'class.mailer.php');
require_once(INCLUDE_DIR.'class.validator.php');
require_once INCLUDE_DIR.'mysqli.php';
require_once INCLUDE_DIR.'class.i18n.php';
require_once INCLUDE_DIR.'class.queue.php';
}
function i18n_prep() {
static function i18n_prep() {
ini_set('default_charset', 'utf-8');
ini_set('output_encoding', 'utf-8');
@@ -310,10 +337,10 @@ class Bootstrap {
}
}
function croak($message) {
static function croak($message) {
$msg = $message."\n\n".THISPAGE;
Mailer::sendmail(ADMIN_EMAIL, 'osTicket Fatal Error', $msg,
sprintf('"osTicket Alerts"<%s>', ADMIN_EMAIL));
osTicket\Mail\Mailer::sendmail(ADMIN_EMAIL, 'osTicket Fatal Error', $msg,
sprintf('"osTicket Alerts" <%s>', ADMIN_EMAIL));
//Display generic error to the user
Http::response(500, "<b>Fatal Error:</b> Contact system administrator.");
}
@@ -340,7 +367,7 @@ define('CLI_DIR', INCLUDE_DIR.'cli/');
#Current version && schema signature (Changes from version to version)
define('GIT_VERSION','$git');
define('MAJOR_VERSION', '1.15');
define('MAJOR_VERSION', '1.18');
define('THIS_VERSION', MAJOR_VERSION.'-git'); //Shown on admin panel
//Path separator
if(!defined('PATH_SEPARATOR')){
@@ -366,7 +393,7 @@ if (!defined('ROOT_PATH') && ($rp = osTicket::get_root_path(dirname(__file__))))
Bootstrap::init();
#CURRENT EXECUTING SCRIPT.
define('THISPAGE', Misc::currentURL());
define('THISPAGE', Http::url());
define('DEFAULT_MAX_FILE_UPLOADS', ini_get('max_file_uploads') ?: 5);
define('DEFAULT_PRIORITY_ID', 1);

View File

@@ -67,8 +67,9 @@ if($thisclient && $thisclient->getId() && $thisclient->isValid()){
}
/******* CSRF Protectin *************/
// Enforce CSRF protection for POSTS
if ($_POST && !$ost->checkCSRFToken()) {
// Enforce CSRF protection for state-changing methods
if (in_array($_SERVER['REQUEST_METHOD'], ['POST', 'PUT', 'PATCH', 'DELETE'])
&& !$ost->checkCSRFToken()) {
Http::redirect('index.php');
//just incase redirect fails
die('Action denied (400)!');
@@ -79,6 +80,8 @@ $ost->addExtraHeader('<meta name="csrf_token" content="'.$ost->getCSRFToken().'"
/* Client specific defaults */
define('PAGE_LIMIT', DEFAULT_PAGE_LIMIT);
define('SESSION_MAXLIFE', $thisclient ? $thisclient->getMaxIdleTime() :
SESSION_TTL);
require(INCLUDE_DIR.'class.nav.php');
$nav = new UserNav($thisclient, 'home');

View File

@@ -30,10 +30,10 @@
.flag.flag-bi {background-position: -112px -11px}
.flag.flag-bj {background-position: -128px -11px}
.flag.flag-bm {background-position: -144px -11px}
.flag.flag-bn {background-position: -160px -11px}
.flag.flag-bn {background-position: -32px -11px}
.flag.flag-bo {background-position: -176px -11px}
.flag.flag-br {background-position: -192px -11px}
.flag.flag-bs {background-position: -208px -11px}
.flag.flag-bs {background-position: 0px -11px}
.flag.flag-bt {background-position: -224px -11px}
.flag.flag-bv {background-position: -240px -11px}
.flag.flag-bw {background-position: 0 -22px}
@@ -122,8 +122,9 @@
.flag.flag-kg {background-position: -48px -77px}
.flag.flag-kh {background-position: -64px -77px}
.flag.flag-ki {background-position: -80px -77px}
.flag.flag-km {background-position: -96px -77px}
.flag.flag-km {background-position: -64px -77px}
.flag.flag-kn {background-position: -112px -77px}
.flag.flag-ko {background-position: -144px -77px}
.flag.flag-kp {background-position: -128px -77px}
.flag.flag-kr {background-position: -144px -77px}
.flag.flag-kurdistan {background-position: -160px -77px}
@@ -186,7 +187,7 @@
.flag.flag-pm {background-position: -48px -121px}
.flag.flag-pn {background-position: -64px -121px}
.flag.flag-pr {background-position: -80px -121px}
.flag.flag-ps {background-position: -96px -121px}
.flag.flag-ps {background-position: -48px 0px}
.flag.flag-pt {background-position: -112px -121px}
.flag.flag-pw {background-position: -128px -121px}
.flag.flag-py {background-position: -144px -121px}
@@ -211,6 +212,7 @@
.flag.flag-sn {background-position: -192px -132px}
.flag.flag-so {background-position: -208px -132px}
.flag.flag-somaliland {background-position: -224px -132px}
.flag.flag-sq {background-position: -96px 0px}
.flag.flag-sr {background-position: -240px -132px}
.flag.flag-ss {background-position: 0 -143px}
.flag.flag-st {background-position: -16px -143px}
@@ -244,7 +246,7 @@
.flag.flag-vc {background-position: -208px -154px}
.flag.flag-ve {background-position: -224px -154px}
.flag.flag-vg {background-position: -240px -154px}
.flag.flag-vi {background-position: 0 -165px}
.flag.flag-vi {background-position: -16px -165px}
.flag.flag-vn {background-position: -16px -165px}
.flag.flag-vu {background-position: -32px -165px}
.flag.flag-wales {background-position: -48px -165px}
@@ -256,3 +258,5 @@
.flag.flag-zanzibar {background-position: -144px -165px}
.flag.flag-zm {background-position: -160px -165px}
.flag.flag-zw {background-position: -176px -165px}
.flag.flag-fil {background-position: 0px -121px}
.flag.flag-kk {background-position: -208px -77px}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 418 B

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 B

After

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 B

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 348 B

After

Width:  |  Height:  |  Size: 458 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 B

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 B

After

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 B

After

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -45,9 +45,8 @@ if ($cfg->isAuthRequiredForFiles()
) {
// Try and determine if an agent is viewing the page / file
if (strpos($_SERVER['HTTP_REFERRER'], ROOT_PATH . 'scp/') !== false) {
$_SESSION['_staff']['auth']['dest'] =
'/' . ltrim($_SERVER['REQUEST_URI'], '/');
if (strpos($_SERVER['HTTP_REFERER'], ROOT_PATH . 'scp/') !== false) {
$_SESSION['_staff']['auth']['dest'] = Http::refresh_url();
Http::redirect(ROOT_PATH.'scp/login.php');
} else {
require 'secure.inc.php';
@@ -59,7 +58,7 @@ if ($cfg->isAuthRequiredForFiles()
// and the user has access to the parent ticket!!
if ($file->verifySignature($_GET['signature'], $_GET['expires'])) {
try {
if (($s = @$_GET['s']) && strpos($file->getType(), 'image/') === 0)
if (($s = @$_GET['s']) && $file->isInlineSafeImage())
return $file->display($s);
// Download the file..
@@ -68,7 +67,8 @@ if ($file->verifySignature($_GET['signature'], $_GET['expires'])) {
$file->download($filename, $disposition, @$_GET['expires']);
}
catch (Exception $ex) {
Http::response(500, 'Unable to find that file: '.$ex->getMessage());
$ost->logError(__('File download error'), $ex->getMessage());
Http::response(500, __('Unable to retrieve the requested file'));
}
}
// else

View File

@@ -152,7 +152,7 @@ class Services_JSON
return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
}
$bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
$bytes = (ord($utf16[0]) << 8) | ord($utf16[1]);
switch(true) {
case ((0x7F & $bytes) == $bytes):
@@ -205,17 +205,17 @@ class Services_JSON
case 2:
// return a UTF-16 character from a 2-byte UTF-8 char
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0x07 & (ord($utf8{0}) >> 2))
. chr((0xC0 & (ord($utf8{0}) << 6))
| (0x3F & ord($utf8{1})));
return chr(0x07 & (ord($utf8[0]) >> 2))
. chr((0xC0 & (ord($utf8[0]) << 6))
| (0x3F & ord($utf8[1])));
case 3:
// return a UTF-16 character from a 3-byte UTF-8 char
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr((0xF0 & (ord($utf8{0}) << 4))
| (0x0F & (ord($utf8{1}) >> 2)))
. chr((0xC0 & (ord($utf8{1}) << 6))
| (0x7F & ord($utf8{2})));
return chr((0xF0 & (ord($utf8[0]) << 4))
| (0x0F & (ord($utf8[1]) >> 2)))
. chr((0xC0 & (ord($utf8[1]) << 6))
| (0x7F & ord($utf8[2])));
}
// ignoring UTF-32 for now, sorry
@@ -260,7 +260,7 @@ class Services_JSON
*/
for ($c = 0; $c < $strlen_var; ++$c) {
$ord_var_c = ord($var{$c});
$ord_var_c = ord($var[$c]);
switch (true) {
case $ord_var_c == 0x08:
@@ -283,18 +283,18 @@ class Services_JSON
case $ord_var_c == 0x2F:
case $ord_var_c == 0x5C:
// double quote, slash, slosh
$ascii .= '\\'.$var{$c};
$ascii .= '\\'.$var[$c];
break;
case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
// characters U-00000000 - U-0000007F (same as ASCII)
$ascii .= $var{$c};
$ascii .= $var[$c];
break;
case (($ord_var_c & 0xE0) == 0xC0):
// characters U-00000080 - U-000007FF, mask 110XXXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c, ord($var{$c + 1}));
$char = pack('C*', $ord_var_c, ord($var[$c + 1]));
$c += 1;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
@@ -304,8 +304,8 @@ class Services_JSON
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var{$c + 1}),
ord($var{$c + 2}));
ord($var[$c + 1]),
ord($var[$c + 2]));
$c += 2;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
@@ -315,9 +315,9 @@ class Services_JSON
// characters U-00010000 - U-001FFFFF, mask 11110XXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var{$c + 1}),
ord($var{$c + 2}),
ord($var{$c + 3}));
ord($var[$c + 1]),
ord($var[$c + 2]),
ord($var[$c + 3]));
$c += 3;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
@@ -327,10 +327,10 @@ class Services_JSON
// characters U-00200000 - U-03FFFFFF, mask 111110XX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var{$c + 1}),
ord($var{$c + 2}),
ord($var{$c + 3}),
ord($var{$c + 4}));
ord($var[$c + 1]),
ord($var[$c + 2]),
ord($var[$c + 3]),
ord($var[$c + 4]));
$c += 4;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
@@ -340,11 +340,11 @@ class Services_JSON
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var{$c + 1}),
ord($var{$c + 2}),
ord($var{$c + 3}),
ord($var{$c + 4}),
ord($var{$c + 5}));
ord($var[$c + 1]),
ord($var[$c + 2]),
ord($var[$c + 3]),
ord($var[$c + 4]),
ord($var[$c + 5]));
$c += 5;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
@@ -519,7 +519,7 @@ class Services_JSON
for ($c = 0; $c < $strlen_chrs; ++$c) {
$substr_chrs_c_2 = substr($chrs, $c, 2);
$ord_chrs_c = ord($chrs{$c});
$ord_chrs_c = ord($chrs[$c]);
switch (true) {
case $substr_chrs_c_2 == '\b':
@@ -549,7 +549,7 @@ class Services_JSON
case $substr_chrs_c_2 == '\\/':
if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
($delim == "'" && $substr_chrs_c_2 != '\\"')) {
$utf8 .= $chrs{++$c};
$utf8 .= $chrs[++$c];
}
break;
@@ -562,7 +562,7 @@ class Services_JSON
break;
case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
$utf8 .= $chrs{$c};
$utf8 .= $chrs[$c];
break;
case ($ord_chrs_c & 0xE0) == 0xC0:
@@ -609,7 +609,7 @@ class Services_JSON
} elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
// array, or object notation
if ($str{0} == '[') {
if ($str[0] == '[') {
$stk = array(SERVICES_JSON_IN_ARR);
$arr = array();
} else {
@@ -648,12 +648,12 @@ class Services_JSON
$top = end($stk);
$substr_chrs_c_2 = substr($chrs, $c, 2);
if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
if (($c == $strlen_chrs) || (($chrs[$c] == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
// found a comma that is not inside a string, array, etc.,
// OR we've reached the end of the character list
$slice = substr($chrs, $top['where'], ($c - $top['where']));
array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
//print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
//print("Found split at [$c]: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
if (reset($stk) == SERVICES_JSON_IN_ARR) {
// we are in an array, so just push an element onto the stack
@@ -690,48 +690,48 @@ class Services_JSON
}
} elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
} elseif ((($chrs[$c] == '"') || ($chrs[$c] == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
// found a quote, and we are not inside a string
array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
//print("Found start of string at {$c}\n");
array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs[$c]));
//print("Found start of string at [$c]\n");
} elseif (($chrs{$c} == $top['delim']) &&
} elseif (($chrs[$c] == $top['delim']) &&
($top['what'] == SERVICES_JSON_IN_STR) &&
((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
// found a quote, we're in a string, and it's not escaped
// we know that it's not escaped becase there is _not_ an
// odd number of backslashes at the end of the string so far
array_pop($stk);
//print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
//print("Found end of string at [$c]: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
} elseif (($chrs{$c} == '[') &&
} elseif (($chrs[$c] == '[') &&
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
// found a left-bracket, and we are in an array, object, or slice
array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
//print("Found start of array at {$c}\n");
//print("Found start of array at [$c]\n");
} elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
} elseif (($chrs[$c] == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
// found a right-bracket, and we're in an array
array_pop($stk);
//print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
//print("Found end of array at [$c]: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
} elseif (($chrs{$c} == '{') &&
} elseif (($chrs[$c] == '{') &&
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
// found a left-brace, and we are in an array, object, or slice
array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
//print("Found start of object at {$c}\n");
//print("Found start of object at [$c]\n");
} elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
} elseif (($chrs[$c] == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
// found a right-brace, and we're in an object
array_pop($stk);
//print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
//print("Found end of object at [$c]: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
} elseif (($substr_chrs_c_2 == '/*') &&
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
// found a comment start, and we are in an array, object, or slice
array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
$c++;
//print("Found start of comment at {$c}\n");
//print("Found start of comment at [$c]\n");
} elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
// found a comment end, and we're in one now
@@ -741,7 +741,7 @@ class Services_JSON
for ($i = $top['where']; $i <= $c; ++$i)
$chrs = substr_replace($chrs, ' ', $i, 1);
//print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
//print("Found end of comment at [$c]: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
}
@@ -762,7 +762,7 @@ class Services_JSON
/**
* @todo Ultimately, this should just call PEAR::isError()
*/
function isError($data, $code = null)
static function isError($data, $code = null)
{
if (class_exists('pear')) {
return PEAR::isError($data, $code);

View File

@@ -146,7 +146,7 @@ class Spyc {
* @return array
* @param string $input Path of YAML file or string containing YAML
*/
function YAMLLoad($input) {
static function YAMLLoad($input) {
$Spyc = new Spyc;
return $Spyc->__load($input);
}

View File

@@ -48,7 +48,7 @@ class AdminAjaxAPI extends AjaxController {
}
$title = __("Add New Department");
$path = ltrim($ost->get_path_info(), '/');
$path = ltrim(Osticket::get_path_info(), '/');
include STAFFINC_DIR . 'templates/quick-add.tmpl.php';
}
@@ -95,7 +95,7 @@ class AdminAjaxAPI extends AjaxController {
}
$title = __("Add New Team");
$path = ltrim($ost->get_path_info(), '/');
$path = ltrim(Osticket::get_path_info(), '/');
include STAFFINC_DIR . 'templates/quick-add.tmpl.php';
}
@@ -139,7 +139,7 @@ class AdminAjaxAPI extends AjaxController {
}
$title = __("Add New Role");
$path = ltrim($ost->get_path_info(), '/');
$path = ltrim(Osticket::get_path_info(), '/');
include STAFFINC_DIR . 'templates/quick-add-role.tmpl.php';
}
@@ -186,7 +186,7 @@ class AdminAjaxAPI extends AjaxController {
}
$title = __("Add New Agent");
$path = ltrim($ost->get_path_info(), '/');
$path = ltrim(Osticket::get_path_info(), '/');
include STAFFINC_DIR . 'templates/quick-add.tmpl.php';
}
@@ -225,10 +225,12 @@ class AdminAjaxAPI extends AjaxController {
Http::response(403, 'Access denied');
$sort = new QueueSort();
$errors = array();
if ($_POST) {
$_POST['root'] = $root;
$data_form = $sort->getDataConfigForm($_POST);
if ($data_form->isValid()) {
$sort->update($data_form->getClean() + $_POST, $root);
$sort->update($data_form->getClean() + $_POST, $errors);
if ($sort->save())
Http::response(201, $this->encode(array(
'id' => $sort->getId(),

View File

@@ -19,7 +19,12 @@ require_once INCLUDE_DIR.'class.ajax.php';
class ContentAjaxAPI extends AjaxController {
function log($id) {
function syslog($id) {
global $thisstaff;
// Ensure there is a Staff and that they are an Administrator
if (!$thisstaff || !$thisstaff->isAdmin())
Http::response(403, 'Access Denied');
if($id && ($log=Log::lookup($id))) {
$content=sprintf('<div
@@ -27,7 +32,7 @@ class ContentAjaxAPI extends AjaxController {
style="white-space:pre-line;">%s</p>
<hr><strong>%s:</strong> <em>%s</em> <strong>%s:</strong> <em>%s</em></div>',
$log->getTitle(),
Format::display(str_replace(',',', ',$log->getText())),
Format::display(str_replace(',',', ',Format::htmlchars($log->getText()))),
__('Log Date'),
Format::daydatetime($log->getCreateDate()),
__('IP Address'),
@@ -142,8 +147,8 @@ class ContentAjaxAPI extends AjaxController {
function manageContent($id, $lang=false) {
global $thisstaff, $cfg;
if (!$thisstaff)
Http::response(403, 'Login Required');
if (!$thisstaff || !$thisstaff->isAdmin())
Http::response(403, 'Access Denied');
$content = Page::lookup($id, $lang);
@@ -168,8 +173,8 @@ class ContentAjaxAPI extends AjaxController {
function manageNamedContent($type, $lang=false) {
global $thisstaff, $cfg;
if (!$thisstaff)
Http::response(403, 'Login Required');
if (!$thisstaff || !$thisstaff->isAdmin())
Http::response(403, 'Access Denied');
$langs = $cfg->getSecondaryLanguages();
@@ -180,10 +185,10 @@ class ContentAjaxAPI extends AjaxController {
}
function updateContent($id) {
global $thisstaff;
global $thisstaff, $cfg;
if (!$thisstaff)
Http::response(403, 'Login Required');
if (!$thisstaff || !$thisstaff->isAdmin())
Http::response(403, 'Access Denied');
elseif (!($content = Page::lookup($id)))
Http::response(404, 'No such content');
@@ -201,6 +206,7 @@ class ContentAjaxAPI extends AjaxController {
}
if (!$errors['err'])
$errors['err'] = __('Correct any errors below and try again.');
$langs = Internationalization::getConfiguredSystemLanguages();
$info = $_POST;
$errors = Format::htmlchars($errors);
include STAFFINC_DIR . 'templates/content-manage.tmpl.php';

View File

@@ -3,10 +3,11 @@
if(!defined('INCLUDE_DIR')) die('!');
require_once(INCLUDE_DIR.'class.draft.php');
require_once(INCLUDE_DIR.'class.file.php');
class DraftAjaxAPI extends AjaxController {
function _createDraft($vars) {
static function _createDraft($vars) {
if (false === ($vars['body'] = self::_findDraftBody($_POST)))
return JsonDataEncoder::encode(array(
'error' => __("Draft body not found in request"),
@@ -22,7 +23,7 @@ class DraftAjaxAPI extends AjaxController {
));
}
function _getDraft($draft) {
static function _getDraft($draft) {
if (!$draft || !$draft instanceof Draft)
Http::response(205, "Draft not found. Create one first");
@@ -35,7 +36,7 @@ class DraftAjaxAPI extends AjaxController {
));
}
function _updateDraft($draft) {
static function _updateDraft($draft) {
if (false === ($body = self::_findDraftBody($_POST)))
return JsonDataEncoder::encode(array(
'error' => array(
@@ -50,81 +51,83 @@ class DraftAjaxAPI extends AjaxController {
echo "{}";
}
function _uploadInlineImage($draft) {
static function _uploadInlineImage($draft) {
global $cfg;
if (!isset($_POST['data']) && !isset($_FILES['file']))
if (!isset($_FILES['file']))
Http::response(422, "File not included properly");
# Fixup for expected multiple attachments
if (isset($_FILES['file'])) {
$file = AttachmentFile::format($_FILES['file']);
$file = AttachmentFile::format($_FILES['file']);
# Allow for data-uri uploaded files
$fp = fopen($file[0]['tmp_name'], 'rb');
if (fread($fp, 5) == 'data:') {
$data = 'data:';
while ($block = fread($fp, 8192))
$data .= $block;
$file[0] = Format::parseRfc2397($data);
list(,$ext) = explode('/', $file[0]['type'], 2);
$file[0] += array(
'name' => Misc::randCode(8).'.'.$ext,
'size' => strlen($file[0]['data']),
);
}
fclose($fp);
# TODO: Detect unacceptable attachment extension
# TODO: Verify content-type and check file-content to ensure image
$type = $file[0]['type'];
if (strpos($file[0]['type'], 'image/') !== 0)
return Http::response(403,
JsonDataEncoder::encode(array(
'error' => 'File type is not allowed',
))
);
# TODO: Verify file size is acceptable
if ($file[0]['size'] > $cfg->getMaxFileSize())
return Http::response(403,
JsonDataEncoder::encode(array(
'error' => 'File is too large',
))
);
// Paste uploads in Chrome will have a name of 'blob'
if ($file[0]['name'] == 'blob')
$file[0]['name'] = 'screenshot-'.Misc::randCode(4);
$ids = $draft->attachments->upload($file);
if (!$ids) {
if ($file[0]['error']) {
return Http::response(403,
JsonDataEncoder::encode(array(
'error' => $file[0]['error'],
))
);
}
else
return Http::response(500, 'Unable to attach image');
}
$id = (is_array($ids)) ? $ids[0] : $ids;
}
else {
$type = explode('/', $_POST['contentType']);
$info = array(
'data' => base64_decode($_POST['data']),
'name' => Misc::randCode(10).'.'.$type[1],
// TODO: Ensure _POST['contentType']
'type' => $_POST['contentType'],
// Allow one file at a time.
if (count($file) > 1)
return Http::response(403,
JsonDataEncoder::encode(array(
'error' => 'Send one file at a time',
))
);
# Allow for data-uri uploaded files
$fp = fopen($file[0]['tmp_name'], 'rb');
if (fread($fp, 5) == 'data:') {
$data = 'data:';
while ($block = fread($fp, 8192))
$data .= $block;
$file[0] = Format::parseRfc2397($data);
list(,$ext) = explode('/', $file[0]['type'], 2);
$file[0] += array(
'name' => Misc::randCode(8).'.'.$ext,
'size' => strlen($file[0]['data']),
);
// TODO: Detect unacceptable filetype
// TODO: Verify content-type and check file-content to ensure image
$id = $draft->attachments->save($info);
}
fclose($fp);
// Check file type to ensure image
$type = $file[0]['type'];
if (!AttachmentFile::isInlineSafeImageType($file[0]['type']))
return Http::response(403,
JsonDataEncoder::encode(array(
'error' => 'File type is not allowed',
))
);
// Check if file is truly an image
if (!FileUploadField::isValidFile($file[0]))
return Http::response(403,
JsonDataEncoder::encode(array(
'error' => 'File is not valid',
))
);
// Verify file size is acceptable
if ($file[0]['size'] > $cfg->getMaxFileSize())
return Http::response(403,
JsonDataEncoder::encode(array(
'error' => 'File is too large',
))
);
// Paste uploads in Chrome will have a name of 'blob'
if ($file[0]['name'] == 'blob')
$file[0]['name'] = 'screenshot-'.Misc::randCode(4);
$ids = $draft->attachments->upload($file);
if (!$ids) {
if ($file[0]['error']) {
return Http::response(403,
JsonDataEncoder::encode(array(
'error' => $file[0]['error'],
))
);
}
else
return Http::response(500, 'Unable to attach image');
}
$id = (is_array($ids)) ? $ids[0] : $ids;
if (!($f = AttachmentFile::lookup($id)))
return Http::response(500, 'Unable to attach image');
@@ -240,6 +243,7 @@ class DraftAjaxAPI extends AjaxController {
$draft = Draft::create(array(
'namespace' => $namespace,
'body' => ''
));
if (!$draft->save())
Http::response(500, 'Unable to create draft');
@@ -310,7 +314,8 @@ class DraftAjaxAPI extends AjaxController {
Http::response(403, "Login required for image upload");
$draft = Draft::create(array(
'namespace' => $namespace
'namespace' => $namespace,
'body' => ''
));
if (!$draft->save())
Http::response(500, 'Unable to create draft');
@@ -363,6 +368,9 @@ class DraftAjaxAPI extends AjaxController {
$files = array();
foreach ($images as $f) {
if (!$f->isInlineSafeImage())
continue;
$url = $f->getDownloadUrl();
$files[] = array(
// Don't send special sizing for thread items 'cause they
@@ -376,7 +384,7 @@ class DraftAjaxAPI extends AjaxController {
echo JsonDataEncoder::encode($files);
}
function _findDraftBody($vars) {
static function _findDraftBody($vars) {
if (isset($vars['name'])) {
$parts = array();
// Support nested `name`, like trans[lang]

59
include/ajax.email.php Normal file
View File

@@ -0,0 +1,59 @@
<?php
require_once INCLUDE_DIR . 'class.email.php';
class EmailAjaxAPI extends AjaxController {
function access() {
global $thisstaff;
if (!$thisstaff || !$thisstaff->isAdmin())
Http::response(403, 'Access Denied');
return true;
}
function stashFormData($id) {
if (!($email=Email::lookup($id)))
Http::response(404, 'Unknown Email');
$email->stashFormData($_POST ?: []);
Http::response(201, 'Form Stashed Maybe!');
}
function configureAuth($id, $type, $auth) {
if (!($email=Email::lookup($id)))
Http::response(404, 'Unknown Email');
if (!($account=$email->getAuthAccount($type)))
Http::response(404, 'Unknown Authentication Type');
if (!($form=$account->getAuthConfigForm($auth, $_POST ?: null)))
Http::response(404, 'Unknown Authentication Provider');
$info = $errors = [];
if ($_POST && $account->saveAuth($auth, $form, $errors)) {
if ($account->isOAuthAuth()
&& $account->shouldAuthorize()) {
Http::response(201, JsonDataEncoder::encode([
'redirect' => sprintf('emails.php?id=%d&do=autho&bk=%s',
$email->getId(), $account->getBkId())]),
'application/json');
} else {
Http::response(201, __('Successfully Updated Credentials.'));
}
}
// Passible types are basic or oauth2
list($authtype, $provider) = explode(':', $auth);
$template = sprintf('email-%sauth.tmpl.php', $authtype);
include INCLUDE_DIR . "staff/templates/$template";
}
/*
* Delete the OAuth2 token
*/
function deleteToken($id, $type) {
// Check to make sure the email exists
if (!($email=Email::lookup($id)))
Http::response(404, 'Unknown Email');
// Get the authentication account
if (!($account=$email->getAuthAccount($type)))
Http::response(404, 'Unknown Authentication Type');
// Destory the account config which will delete the token
if ($account->destroyConfig())
Http::response(201, __('Token Deleted Successfully.'));
// Couldn't delete the Token
Http::response(404, 'Unable to delete token.');
}
}

View File

@@ -85,7 +85,7 @@ class KbaseAjaxAPI extends AjaxController {
$title = __("Manage FAQ Access");
$verb = __('Update');
$path = ltrim($ost->get_path_info(), '/');
$path = ltrim(Osticket::get_path_info(), '/');
include STAFFINC_DIR . 'templates/quick-add.tmpl.php';
}

View File

@@ -13,6 +13,9 @@ class NoteAjaxAPI extends AjaxController {
Http::response(403, "Login required");
elseif (!($note = QuickNote::lookup($id)))
Http::response(205, "Note not found");
elseif (($note->staff_id !== $thisstaff->getId())
&& !$thisstaff->isAdmin())
Http::response(403, "Access Denied");
Http::response(200, $note->display());
}
@@ -24,6 +27,9 @@ class NoteAjaxAPI extends AjaxController {
Http::response(403, "Login required");
elseif (!($note = QuickNote::lookup($id)))
Http::response(205, "Note not found");
elseif (($note->staff_id !== $thisstaff->getId())
&& !$thisstaff->isAdmin())
Http::response(403, "Access Denied");
elseif (!isset($_POST['note']) || !$_POST['note'])
Http::response(422, "Send `note` parameter");
@@ -41,6 +47,9 @@ class NoteAjaxAPI extends AjaxController {
Http::response(403, "Login required");
elseif (!($note = QuickNote::lookup($id)))
Http::response(205, "Note not found");
elseif (($note->staff_id !== $thisstaff->getId())
&& !$thisstaff->isAdmin())
Http::response(403, "Access Denied");
elseif (!$note->delete())
Http::response(500, "Unable to remove note");
@@ -52,6 +61,9 @@ class NoteAjaxAPI extends AjaxController {
if (!$thisstaff)
Http::response(403, "Login required");
elseif (!in_array(substr($ext_id, 0, 1), ['O', 'U'])
&& !$thisstaff->isAdmin())
Http::response(403, "Access Denied");
elseif (!isset($_POST['note']) || !$_POST['note'])
Http::response(422, "Send `note` parameter");

View File

@@ -30,7 +30,7 @@ class OrgsAjaxAPI extends AjaxController {
if (!$_REQUEST['q'])
return $this->json_encode(array());
$q = $_REQUEST['q'];
$q = Format::sanitize($_REQUEST['q']);
$limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25;
if (strlen(Format::searchable($q)) < 3)
@@ -55,7 +55,7 @@ class OrgsAjaxAPI extends AjaxController {
foreach ($orgs as $O) {
list($id, $name) = $O;
$matched[] = array('name' => Format::htmlchars($name), 'info' => $name,
'id' => $id, '/bin/true' => $_REQUEST['q']);
'id' => $id, '/bin/true' => Format::htmlchars($q));
}
return $this->json_encode(array_values($matched));

62
include/ajax.plugins.php Normal file
View File

@@ -0,0 +1,62 @@
<?php
require_once(INCLUDE_DIR . 'class.plugin.php');
class PluginsAjaxAPI extends AjaxController {
/*
* Protect all routines in this controller
*/
function access() {
global $thisstaff;
if (!$thisstaff || !$thisstaff->isAdmin())
Http::response(403, 'Access Denied');
return true;
}
// helper func to look up plugin & instance
private function lookup($plugin_id, $instance_id=0) {
if (!($plugin = PluginManager::lookup( (int) $plugin_id)))
Http::response(404, 'No such plugin');
if ($instance_id && !($instance = $plugin->getInstance( (int) $instance_id)))
Http::response(404, 'No such plugin instance');
return [$plugin, $instance];
}
function getInstances($plugin_id) {
list($plugin,)= $this->lookup($plugin_id);
$pjax_container = '#items';
include(STAFFINC_DIR . 'templates/plugin-instances.tmpl.php');
}
function updateInstance($plugin_id, $instance_id) {
list($plugin, $instance) = $this->lookup($plugin_id, $instance_id);
$errors = array();
if ($_POST && $instance->update($_POST, $errors))
Http::response(201, $this->encode([
'redirect' => sprintf('plugins.php?id=%d#instances',
$plugin->getId())
]));
$form = $instance->getForm();
$action = "#plugins/{$plugin->getId()}/instances/{$instance->getId()}/update";
include STAFFINC_DIR . 'templates/plugin-instance-modal.tmpl.php';
}
function addInstance($plugin_id) {
list($plugin,) = $this->lookup($plugin_id);
$errors = array();
if ($_POST && ($instance=$plugin->addInstance($_POST, $errors)))
Http::response(201, $this->encode([
'redirect' => sprintf('plugins.php?id=%d#instances',
$plugin->getId())
]));
// This should return cached form with errors (if any)
$form = $plugin->getConfigForm();
// Set action
$action = "#plugins/{$plugin->getId()}/instances/add";
include(STAFFINC_DIR . 'templates/plugin-instance-modal.tmpl.php');
}
}
?>

View File

@@ -31,7 +31,7 @@ class SearchAjaxAPI extends AjaxController {
$search = new AdhocSearch(array(
'root' => 'T',
'staff_id' => $thisstaff->getId(),
'parent_id' => @$_GET['parent_id'] ?: 0,
'parent_id' => (int) @$_GET['parent_id'] ?: 0,
));
if ($search->parent_id) {
$search->flags |= SavedSearch::FLAG_INHERIT_COLUMNS;
@@ -125,8 +125,9 @@ class SearchAjaxAPI extends AjaxController {
// the recent search list
if (isset($_SESSION[$key])) {
reset($_SESSION[$key]);
while (count($keep) < 5) {
list($k, $v) = each($_SESSION[$key]);
for ($i = 0; $i < 5; $i++) {
$k = key($_SESSION[$key]);
$v = current($_SESSION[$key]);
if (!$k)
break;
$keep[$k] = $v;
@@ -167,7 +168,7 @@ class SearchAjaxAPI extends AjaxController {
'title' => __('Add Queue'),
'root' => 'T',
'staff_id' => $thisstaff->getId(),
'parent_id' => $_GET['pid'],
'parent_id' => (int) $_GET['pid'],
));
$this->_tryAgain($search);
}
@@ -187,11 +188,16 @@ class SearchAjaxAPI extends AjaxController {
'root' => 'T',
'staff_id' => $thisstaff->getId()
));
$new = true;
}
if (false === $this->_saveSearch($search))
return;
//store saved search count when it is created
if ($new)
$search->counts($thisstaff, false);
$info = array(
'msg' => sprintf('%s %s %s',
__('Search'),
@@ -345,17 +351,16 @@ class SearchAjaxAPI extends AjaxController {
function addCondition() {
global $thisstaff;
if (!$thisstaff) {
if (!$thisstaff)
Http::response(403, 'Agent login is required');
}
elseif (!isset($_GET['field']) || !isset($_GET['id'])
|| !isset($_GET['object_id'])
) {
|| !isset($_GET['object_id']))
Http::response(400, '`field`, `id`, and `object_id` parameters required');
}
elseif (!is_numeric($_GET['object_id'])) {
elseif (!is_numeric($_GET['object_id']))
Http::response(400, '`object_id` should be an integer');
}
elseif (!is_numeric($_GET['id']))
Http::response(400, '`id` should be an integer');
$fields = SavedSearch::getSearchableFields('Ticket');
if (!isset($fields[$_GET['field']])) {
Http::response(400, sprintf('%s: No such searchable field'),
@@ -381,8 +386,8 @@ class SearchAjaxAPI extends AjaxController {
Http::response(400, '`prop` and `condition` parameters required');
}
$prop = $_GET['prop'];
$id = $_GET['condition'];
$prop = Format::htmlchars($_GET['prop']);
$id = Format::htmlchars($_GET['condition']);
include STAFFINC_DIR . 'templates/queue-column-condition-prop.tmpl.php';
}

View File

@@ -66,7 +66,7 @@ class StaffAjaxAPI extends AjaxController {
$title = __("Set Agent Password");
$verb = $id == 0 ? __('Set') : __('Update');
$path = ltrim($ost->get_path_info(), '/');
$path = ltrim(Osticket::get_path_info(), '/');
include STAFFINC_DIR . 'templates/quick-add.tmpl.php';
}
@@ -119,7 +119,7 @@ class StaffAjaxAPI extends AjaxController {
$title = __("Change Password");
$verb = __('Update');
$path = ltrim($ost->get_path_info(), '/');
$path = ltrim(Osticket::get_path_info(), '/');
include STAFFINC_DIR . 'templates/quick-add.tmpl.php';
}
@@ -163,7 +163,7 @@ class StaffAjaxAPI extends AjaxController {
$title = __("Reset Agent Permissions");
$verb = __("Continue");
$path = ltrim($ost->get_path_info(), '/');
$path = ltrim(Osticket::get_path_info(), '/');
include STAFFINC_DIR . 'templates/reset-agent-permissions.tmpl.php';
}
@@ -205,7 +205,7 @@ class StaffAjaxAPI extends AjaxController {
$title = __("Change Primary Department");
$verb = __("Continue");
$path = ltrim($ost->get_path_info(), '/');
$path = ltrim(Osticket::get_path_info(), '/');
include STAFFINC_DIR . 'templates/quick-add.tmpl.php';
}

View File

@@ -59,10 +59,17 @@ class TasksAjaxAPI extends AjaxController {
}
function triggerThreadAction($task_id, $thread_id, $action) {
global $thisstaff;
if (!($task = Task::lookup($task_id))
|| !$task->checkStaffPerm($thisstaff))
Http::response(404, 'No such task thread entry');
$thread = ThreadEntry::lookup($thread_id);
if (!$thread)
Http::response(404, 'No such task thread entry');
if ($thread->getThread()->getObjectId() != $task_id)
if ($thread->getThread()->getObjectType() != 'A'
|| $thread->getThread()->getObjectId() != $task_id)
Http::response(404, 'No such task thread entry');
$valid = false;
@@ -269,7 +276,7 @@ class TasksAjaxAPI extends AjaxController {
break;
default:
$clean = $field->getClean();
$clean = is_array($clean) ? implode($clean, ',') :
$clean = is_array($clean) ? implode(',', $clean) :
(string) $clean;
if (strlen($clean) > 200)
$clean = Format::truncate($clean, 200);
@@ -451,7 +458,7 @@ class TasksAjaxAPI extends AjaxController {
case 'transfer':
$inc = 'transfer.tmpl.php';
$info[':action'] = '#tasks/mass/transfer';
$info[':title'] = sprintf('Transfer %s',
$info[':title'] = sprintf(__('Transfer %s'),
_N('selected task', 'selected tasks', $count));
$form = TransferForm::instantiate($_POST);
$form->hideDisabled();

View File

@@ -189,7 +189,7 @@ class ThreadAjaxAPI extends AjaxController {
return $resp;
}
function _addcollaborator($thread, $user=null, $form=null, $type=null, $info=array()) {
static function _addcollaborator($thread, $user=null, $form=null, $type=null, $info=array()) {
global $thisstaff;
$info += array(
@@ -227,7 +227,7 @@ class ThreadAjaxAPI extends AjaxController {
function _collaborator($collaborator, $form=null, $info=array()) {
static function _collaborator($collaborator, $form=null, $info=array()) {
global $thisstaff;
$info += array('action' => sprintf('#thread/%d/collaborators/%d',
@@ -243,7 +243,7 @@ class ThreadAjaxAPI extends AjaxController {
return $resp;
}
function _collaborators($thread, $info=array()) {
static function _collaborators($thread, $info=array()) {
ob_start();
include(STAFFINC_DIR . 'templates/collaborators.tmpl.php');

View File

@@ -23,7 +23,7 @@ include_once INCLUDE_DIR . 'class.thread_actions.php';
class TicketsAjaxAPI extends AjaxController {
function lookup() {
function lookup($wildcard=false) {
global $thisstaff;
$limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25;
@@ -53,15 +53,19 @@ class TicketsAjaxAPI extends AjaxController {
return $this->encode(array());
global $ost;
$hits = $ost->searcher->find($q, $hits, false);
$hits = $ost->searcher->find($q, $hits, false, ['boolean' => $wildcard]);
if (preg_match('/\d{2,}[^*]/', $q, $T = array())) {
$T = array();
if (preg_match('/\d{2,}[^*]/', $q, $T)) {
$hits = $this->lookupByNumber($limit, $visibility, $hits);
}
elseif (!count($hits) && preg_match('`\w$`u', $q)) {
elseif (preg_match('/(.*@.{2,})|(.{2,}@.*)/', $q, $T)) {
$hits = $this->lookupByEmail($limit, $visibility, $hits);
}
elseif (!$wildcard && !count($hits) && preg_match('`^[a-zA-Z0-9]+$`u', $q)) {
// Do wild-card fulltext search
$_REQUEST['q'] = $q.'*';
return $this->lookup();
return $this->lookup(true);
}
// TODO: Why not aggregate based on relationship?
@@ -144,6 +148,50 @@ class TicketsAjaxAPI extends AjaxController {
return $this->json_encode($tickets);
}
function lookupByEmail($limit=false, $visibility=false, $matches=false) {
global $thisstaff;
if (!$limit)
$limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25;
$tickets=array();
// Bail out of query is empty
if (!$_REQUEST['q'])
return $this->json_encode($tickets);
$q = trim($_REQUEST['q']);
if (!$visibility)
$visibility = $thisstaff->getTicketsVisibility();
$hits = Ticket::objects()
->values('user__default_email__address', 'cdata__subject', 'user__name', 'ticket_id', 'thread__id', 'flags')
->annotate(array(
'number' => new SqlCode('null'),
'tickets' => SqlAggregate::COUNT('ticket_id', true),
'tasks' => SqlAggregate::COUNT('tasks__id', true),
'collaborators' => SqlAggregate::COUNT('thread__collaborators__id', true),
'entries' => SqlAggregate::COUNT('thread__entries__id', true),
))
->filter($visibility)
->filter(array('user__default_email__address__contains' => $q))
->order_by('user__default_email__address')
->limit($limit);
if ($matches && is_a($matches, 'QuerySet'))
return $hits->union($matches);
foreach ($hits as $T) {
$count = $T['tickets'];
$email = $T['user__default_email__address'];
$tickets[$email] = array('email'=>$email, 'value'=>$email,
'info'=>"$email ($count)", 'matches'=>$_REQUEST['q']);
}
$tickets = array_values($tickets);
return $this->json_encode($tickets);
}
function acquireLock($tid) {
global $cfg, $thisstaff;
@@ -318,7 +366,7 @@ class TicketsAjaxAPI extends AjaxController {
return self::_userlookup($user, null, $info);
}
function _userlookup($user, $form, $info) {
static function _userlookup($user, $form, $info) {
global $thisstaff;
ob_start();
@@ -450,7 +498,7 @@ class TicketsAjaxAPI extends AjaxController {
return self::_updateMerge($parent, $tickets, $info);
}
private function _updateMerge($ticket, $tickets, $info) {
static private function _updateMerge($ticket, $tickets, $info) {
include(STAFFINC_DIR . 'templates/merge-tickets.tmpl.php');
}
@@ -688,7 +736,7 @@ class TicketsAjaxAPI extends AjaxController {
break;
default:
$clean = $field->getClean();
$clean = is_array($clean) ? implode($clean, ',') :
$clean = is_array($clean) ? implode(',', $clean) :
(string) $clean;
}
@@ -729,6 +777,8 @@ class TicketsAjaxAPI extends AjaxController {
if (!($ticket=Ticket::lookup($tid)))
Http::response(404, __('No such ticket'));
elseif (!$ticket->checkStaffPerm($thisstaff))
Http::response(403, __('Permission denied'));
elseif (!($field=$ticket->getField($fid)))
Http::response(404, __('No such field'));
@@ -994,7 +1044,7 @@ class TicketsAjaxAPI extends AjaxController {
foreach ($tickets as $ticket) {
list($ticket_id, $flags, $dept_id, $ticket_pid) = $ticket;
$mergeType = Ticket::getMergeTypeByFlag($flags);
$isParent = Ticket::isParent($flags);
$isParent = Ticket::isParentStatic($flags);
$role = $thisstaff->getRole($dept_id);
$hasPermission[] = $role->hasPerm($permission);
@@ -1086,6 +1136,7 @@ class TicketsAjaxAPI extends AjaxController {
$depts = $tickets->values_flat('dept_id');
}
//agents in depts $thisstaff can access
$members = $thisstaff->getDeptAgents(array('available' => true));
if ($depts) {
@@ -1163,6 +1214,9 @@ class TicketsAjaxAPI extends AjaxController {
$f->configure('prompt', $prompt);
if ($_POST && $form->isValid()) {
// we need to clear this relation so we can create a fresh
// query that will get roles for all depts this agent can access
unset($thisstaff->ht['dept_access']);
foreach ($_POST['tids'] as $tid) {
if (($t=Ticket::lookup($tid))
// Make sure the agent is allowed to
@@ -1185,7 +1239,7 @@ class TicketsAjaxAPI extends AjaxController {
case 'transfer':
$inc = 'transfer.tmpl.php';
$info[':action'] = '#tickets/mass/transfer';
$info[':title'] = sprintf('Transfer %s',
$info[':title'] = sprintf(__('Transfer %s'),
_N('selected ticket', 'selected tickets', $count));
$form = TransferForm::instantiate($_POST);
$form->hideDisabled();
@@ -1650,10 +1704,17 @@ class TicketsAjaxAPI extends AjaxController {
}
function triggerThreadAction($ticket_id, $thread_id, $action) {
global $thisstaff;
if (!($ticket = Ticket::lookup($ticket_id))
|| !$ticket->checkStaffPerm($thisstaff))
Http::response(404, 'No such ticket thread entry');
$thread = ThreadEntry::lookup($thread_id);
if (!$thread)
Http::response(404, 'No such ticket thread entry');
if ($thread->getThread()->getObjectId() != $ticket_id)
if ($thread->getThread()->getObjectType() != 'T'
|| $thread->getThread()->getObjectId() != $ticket_id)
Http::response(404, 'No such ticket thread entry');
$valid = false;
@@ -1670,7 +1731,7 @@ class TicketsAjaxAPI extends AjaxController {
$thread->triggerAction($action);
}
private function _changeSelectedTicketsStatus($state, $info=array(), $errors=array()) {
private static function _changeSelectedTicketsStatus($state, $info=array(), $errors=array()) {
$count = $_REQUEST['count'] ?:
($_REQUEST['tids'] ? count($_REQUEST['tids']) : 0);
@@ -1702,7 +1763,7 @@ class TicketsAjaxAPI extends AjaxController {
return self::_changeStatus($state, $info, $errors);
}
private function _changeTicketStatus($ticket, $state, $info=array(), $errors=array()) {
private static function _changeTicketStatus($ticket, $state, $info=array(), $errors=array()) {
$verb = TicketStateField::getVerb($state);
@@ -1738,7 +1799,7 @@ class TicketsAjaxAPI extends AjaxController {
return self::_changeStatus($state, $info, $errors);
}
private function _changeStatus($state, $info=array(), $errors=array()) {
private static function _changeStatus($state, $info=array(), $errors=array()) {
if ($info && isset($info['errors']))
$errors = array_merge($errors, $info['errors']);
@@ -1983,7 +2044,7 @@ class TicketsAjaxAPI extends AjaxController {
} else {
$filename = sprintf('%s Tickets-%s.csv',
$queue->getName(),
strftime('%Y%m%d'));
date('Ymd'));
}
try {

View File

@@ -34,7 +34,7 @@ class UsersAjaxAPI extends AjaxController {
if (!$_REQUEST['q'])
return $this->json_encode($matches);
$q = $_REQUEST['q'];
$q = Format::sanitize($_REQUEST['q']);
$limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25;
$users=array();
$emails=array();
@@ -74,16 +74,18 @@ class UsersAjaxAPI extends AjaxController {
return $this->search($type, $fulltext);
}
} else {
$filter = Q::any(array(
'emails__address__contains' => $q,
'name__contains' => $q,
'org__name__contains' => $q,
'account__username__contains' => $q,
));
if (UserForm::getInstance()->getField('phone'))
$filter->add(array('cdata__phone__contains' => $q));
$users->filter($filter);
$base = clone $users;
$users->filter(array('name__contains' => $q));
$users->union($base->copy()->filter(array(
'org__name__contains' => $q)), false);
$users->union($base->copy()->filter(array(
'emails__address__contains' => $q)), false);
$users->union($base->copy()->filter(array(
'account__username__contains' => $q)), false);
if (UserForm::getInstance()->getField('phone')) {
$users->union($base->copy()->filter(array(
'cdata__phone__contains' => $q)), false);
}
}
// Omit already-imported remote users
@@ -105,7 +107,7 @@ class UsersAjaxAPI extends AjaxController {
}
$name = Format::htmlchars(new UsersName($name));
$matches[] = array('email'=>$email, 'name'=>$name, 'info'=>"$email - $name",
"id" => $id, "/bin/true" => $_REQUEST['q']);
"id" => $id, "/bin/true" => $q);
}
usort($matches, function($a, $b) { return strcmp($a['name'], $b['name']); });
}
@@ -119,7 +121,12 @@ class UsersAjaxAPI extends AjaxController {
if(!$thisstaff)
Http::response(403, 'Login Required');
elseif(!($user = User::lookup($id)))
if (!$thisstaff->hasPerm(User::PERM_DIRECTORY)
&& !$thisstaff->hasPerm(User::PERM_EDIT))
Http::response(403, 'Permission Denied');
if(!($user = User::lookup($id)))
Http::response(404, 'Unknown user');
$info = array(
@@ -178,7 +185,7 @@ class UsersAjaxAPI extends AjaxController {
include(STAFFINC_DIR . 'templates/user.tmpl.php');
}
function register($id) {
static function register($id) {
global $thisstaff;
if (!$thisstaff)
@@ -273,6 +280,14 @@ class UsersAjaxAPI extends AjaxController {
}
function getUser($id=false) {
global $thisstaff;
if (!$thisstaff)
Http::response(403, 'Login Required');
if (!$thisstaff->hasPerm(User::PERM_DIRECTORY)
&& !$thisstaff->hasPerm(User::PERM_EDIT))
Http::response(403, 'Permission Denied');
if(($user=User::lookup(($id) ? $id : $_REQUEST['id'])))
Http::response(201, $user->to_json(), 'application/json');
@@ -286,7 +301,7 @@ class UsersAjaxAPI extends AjaxController {
return self::addUser();
}
function addUser() {
static function addUser() {
global $thisstaff;
$info = array();
@@ -321,7 +336,7 @@ class UsersAjaxAPI extends AjaxController {
elseif (!$bk || !$id)
Http::response(422, 'Backend and user id required');
elseif (!($backend = AuthenticationBackend::getSearchDirectoryBackend($bk))
|| !($user_info = $backend->lookup($id)))
|| !($user_info = $backend->lookup(html_entity_decode($id))))
Http::response(404, 'User not found');
$form = UserForm::getUserForm()->getForm($user_info);
@@ -420,6 +435,8 @@ class UsersAjaxAPI extends AjaxController {
Http::response(403, 'Login Required');
elseif (!($user = User::lookup($id)))
Http::response(404, 'Unknown user');
elseif (!$thisstaff->hasPerm(User::PERM_EDIT))
Http::response(403, 'Access Denied');
$info = array();
$info['title'] = sprintf(__('Organization for %s'),
@@ -432,6 +449,9 @@ class UsersAjaxAPI extends AjaxController {
if (!($org = Organization::lookup($_POST['orgid'])))
$info['error'] = __('Unknown organization selected');
} else { //Creating new org.
if (!$thisstaff->hasPerm(Organization::PERM_CREATE))
Http::response(403, 'Permission Denied');
$form = OrganizationForm::getDefaultForm()->getForm($_POST);
if (!($org = Organization::fromForm($form)))
$info['error'] = __('Unable to create organization.')

View File

@@ -3,40 +3,45 @@
include_once INCLUDE_DIR.'class.cron.php';
class CronApiController extends ApiController {
function execute() {
if(!($key=$this->requireApiKey()) || !$key->canExecuteCron())
if (!($key=$this->requireApiKey()) || !$key->canExecuteCron())
return $this->exerr(401, __('API key not authorized'));
$this->run();
}
/* private */
function run() {
global $ost;
protected function run() {
Cron::run();
$ost->logDebug(__('Cron Job'),__('Cron job executed').' ['.$_SERVER['REMOTE_ADDR'].']');
// TODO: Add elapsed time to the debug log
$this->debug(__('Cron Job'),
sprintf('%s [%s]', __('Cron job executed'), $this->getRemoteAddr()));
$this->response(200,'Completed');
}
}
class LocalCronApiController extends CronApiController {
function response($code, $resp) {
public function isCli() {
return true;
}
if($code == 200) //Success - exit silently.
protected function getRemoteAddr() {
// Local Cron doesn't have IP Addr set
return 'CLI';
}
public function response($code, $response) {
if ($code == 200) //Success - exit silently.
exit(0);
//On error echo the response (error)
echo $resp;
echo $response;
exit(1);
}
function call() {
$cron = new LocalCronApiController();
static function call() {
$cron = new LocalCronApiController('cli');
$cron->run();
}
}

View File

@@ -43,15 +43,22 @@ class TicketApiController extends ApiController {
foreach ($form->getFields() as $field)
$supported[] = $field->get('name');
if(!strcasecmp($format, 'email')) {
$supported = array_merge($supported, array('header', 'mid',
'emailId', 'to-email-id', 'ticketId', 'reply-to', 'reply-to-name',
'in-reply-to', 'references', 'thread-type', 'system_emails',
'mailflags' => array('bounce', 'auto-reply', 'spam', 'viral'),
'recipients' => array('*' => array('name', 'email', 'source'))
));
$supported['attachments']['*'][] = 'cid';
switch ($format) {
case 'email':
$supported = array_merge($supported, [
'header', 'mid', 'emailId', 'to-email-id', 'ticketId', 'reply-to',
'reply-to-name', 'in-reply-to', 'references', 'thread-type', 'system_emails',
'mailflags' => ['bounce', 'auto-reply', 'spam', 'viral'],
'recipients' => ['*' => ['name', 'email', 'source']]
]);
$supported['attachments']['*'][] = 'cid';
break;
case 'json':
case 'xml':
$supported = array_merge($supported, [
'duedate', 'slaId', 'staffId'
]);
break;
}
return $supported;
@@ -78,7 +85,7 @@ class TicketApiController extends ApiController {
$data['attachments'] = array();
//Validate attachments: Do error checking... soft fail - set the error and pass on the request.
if ($data['attachments'] && is_array($data['attachments'])) {
if (isset($data['attachments']) && is_array($data['attachments'])) {
foreach($data['attachments'] as &$file) {
if ($file['encoding'] && !strcasecmp($file['encoding'], 'base64')) {
if(!($file['data'] = base64_decode($file['data'], true)))
@@ -105,61 +112,84 @@ class TicketApiController extends ApiController {
function create($format) {
if(!($key=$this->requireApiKey()) || !$key->canCreateTickets())
if (!($key=$this->requireApiKey()) || !$key->canCreateTickets())
return $this->exerr(401, __('API key not authorized'));
$ticket = null;
if(!strcasecmp($format, 'email')) {
# Handle remote piped emails - could be a reply...etc.
$ticket = $this->processEmail();
if (!strcasecmp($format, 'email')) {
// Process remotely piped emails - could be a reply...etc.
$ticket = $this->processEmailRequest();
} else {
# Parse request body
// Get and Parse request body data for the format
$ticket = $this->createTicket($this->getRequest($format));
}
if(!$ticket)
return $this->exerr(500, __("Unable to create new ticket: unknown error"));
$this->response(201, $ticket->getNumber());
if ($ticket)
$this->response(201, $ticket->getNumber());
else
$this->exerr(500, _S("unknown error"));
}
/* private helper functions */
function createTicket($data) {
function createTicket($data, $source = 'API') {
# Pull off some meta-data
$alert = (bool) (isset($data['alert']) ? $data['alert'] : true);
$autorespond = (bool) (isset($data['autorespond']) ? $data['autorespond'] : true);
# Assign default value to source if not defined, or defined as NULL
$data['source'] = isset($data['source']) ? $data['source'] : 'API';
// Assign default value to source if not defined, or defined as NULL
$data['source'] ??= $source;
# Create the ticket with the data (attempt to anyway)
// Create the ticket with the data (attempt to anyway)
$errors = array();
if (($ticket = Ticket::create($data, $errors, $data['source'],
$autorespond, $alert)) && !$errors)
return $ticket;
$ticket = Ticket::create($data, $errors, $data['source'], $autorespond, $alert);
# Return errors (?)
// Ticket create failed Bigly - got errors?
$title = null;
// Got errors?
if (count($errors)) {
if(isset($errors['errno']) && $errors['errno'] == 403)
return $this->exerr(403, __('Ticket denied'));
else
return $this->exerr(
400,
__("Unable to create new ticket: validation errors").":\n"
.Format::array_implode(": ", "\n", $errors)
);
} elseif (!$ticket) {
return $this->exerr(500, __("Unable to create new ticket: unknown error"));
// Ticket denied? Say so loudly so it can standout from generic
// validation errors
if (isset($errors['errno']) && $errors['errno'] == 403) {
$title = _S('Ticket denied');
$error = sprintf("%s: %s\n\n%s",
$title, $data['email'], $errors['err']);
} else {
// unpack the errors
$error = Format::array_implode("\n", "\n", $errors);
}
} else {
// unknown reason - default
$error = _S('unknown error');
}
return $ticket;
$error = sprintf('%s :%s',
_S('Unable to create new ticket'), $error);
return $this->exerr($errors['errno'] ?: 500, $error, $title);
}
function processEmail($data=false) {
function processEmailRequest() {
return $this->processEmail();
}
if (!$data)
$data = $this->getEmailRequest();
function processEmail($data=false, array $defaults = []) {
try {
if (!$data)
$data = $this->getEmailRequest();
elseif (!is_array($data))
$data = $this->parseEmail($data);
} catch (Exception $ex) {
throw new EmailParseError($ex->getMessage());
}
$data = array_merge($defaults, $data);
$seen = false;
if (($entry = ThreadEntry::lookupByEmailHeaders($data, $seen))
&& ($message = $entry->postEmail($data))
@@ -183,18 +213,34 @@ class TicketApiController extends ApiController {
// All emails which do not appear to be part of an existing thread
// will always create new "Tickets". All other objects will need to
// be created via the web interface or the API
return $this->createTicket($data);
try {
return $this->createTicket($data, 'Email');
} catch (TicketApiError $err) {
// Check if the ticket was denied by a filter or banlist
if ($err->isDenied() && $data['mid']) {
// We need to log the Message-Id (mid) so we don't
// process the same email again in subsequent fetches
$entry = new ThreadEntry();
$entry->logEmailHeaders(0, $data['mid']);
// throw TicketDenied exception so the caller can handle it
// accordingly
throw new TicketDenied($err->getMessage());
} else {
// otherwise rethrow this bad baby as it is!
throw $err;
}
}
}
}
//Local email piping controller - no API key required!
class PipeApiController extends TicketApiController {
//Overwrite grandparent's (ApiController) response method.
// Overwrite grandparent's (ApiController) response method.
function response($code, $resp) {
//Use postfix exit codes - instead of HTTP
// It's important to use postfix exit codes for local piping instead
// of HTTP's so the piping script can process them accordingly
switch($code) {
case 201: //Success
$exitcode = 0;
@@ -219,19 +265,33 @@ class PipeApiController extends TicketApiController {
default: //Temp (unknown) failure - retry
$exitcode = 75;
}
//echo "$code ($exitcode):$resp";
//We're simply exiting - MTA will take care of the rest based on exit code!
exit($exitcode);
}
function process() {
$pipe = new PipeApiController();
if(($ticket=$pipe->processEmail()))
return $pipe->response(201, $ticket->getNumber());
static function process($sapi=null) {
$pipe = new PipeApiController($sapi);
if (($ticket=$pipe->processEmail()))
return $pipe->response(201,
is_object($ticket) ? $ticket->getNumber() : $ticket);
return $pipe->exerr(416, __('Request failed - retry again!'));
}
static function local() {
return self::process('cli');
}
}
class TicketApiError extends Exception {
// Check if exception is because of denial
public function isDenied() {
return ($this->getCode() === 403);
}
}
class TicketDenied extends Exception {}
class EmailParseError extends Exception {}
?>

View File

@@ -3,17 +3,17 @@
/**
* TwoFactorAuthentication backend
*
* Provides the basis of abstracting 2fa backends
* Provides the basis of abstracting 2fa backends
* The authentication backend should define a validate() method which
* receives a user and OPT.
*/
abstract class TwoFactorAuthenticationBackend {
abstract class TwoFactorAuthenticationBackend extends ServiceRegistry {
// Global registry
static protected $registry = array();
// Grace period in minutes before OTP is expired and user logged out
// It's hardcoded to 6 minutes here but downstream backends can make it
// configurable
// configurable
protected $timeout = 6;
// Maximum number of validation attempts before the user is logged out
@@ -36,14 +36,6 @@ abstract class TwoFactorAuthenticationBackend {
// validate OTP provided by user
abstract function validate($form, $user);
function getId() {
return static::$id;
}
function getName() {
return __(static::$name);
}
function getDescription() {
return __(static::$desc);
}
@@ -64,7 +56,7 @@ abstract class TwoFactorAuthenticationBackend {
return array();
}
// stash OTP info in the session
// stash OTP info in the session
protected function store($otp) {
$store = &$_SESSION['_2fa'][$this->getId()];
$store = ['otp' => $otp, 'time' => time(), 'strikes' => 0];
@@ -76,12 +68,12 @@ abstract class TwoFactorAuthenticationBackend {
protected function _validate($otp, $strict=true) {
$store = &$_SESSION['_2fa'][$this->getId()];
// Track and check the attempts
$store['strikes'] += 1;
$store['strikes'] += 1;
if ($strict && $store['strikes'] > $this->getMaxStrikes())
throw new ExpiredOTP(__('Too many attempts'));
// Check timeout - if expired throw an exception.
if ($strict
if ($strict
&& ($timeout=$this->getTimeout())
&& ($store['time']+($timeout*60)) < time())
throw new ExpiredOTP(__('Expired OTP'));
@@ -120,18 +112,11 @@ abstract class TwoFactorAuthenticationBackend {
|| !($class instanceof TwoFactorAuthenticationBackend))
return false;
return static::_register($class);
}
static function _register($class) {
if (isset(static::$registry[$class::$id]))
return false;
static::$registry[$class::$id] = $class;
static::$registry[$class->getBkId()] = $class;
}
static function allRegistered() {
return static::$registry;
return array_merge(self::$registry, parent::getRegistry());
}
static function getBackend($id) {
@@ -154,38 +139,19 @@ class ExpiredOTP extends Exception {}
*
*/
abstract class Staff2FABackend extends TwoFactorAuthenticationBackend {
static private $_registry = array();
static function _register($class) {
if (isset(static::$_registry[$class::$id]))
return false;
static::$_registry[$class::$id] = $class;
}
static protected $registry = array();
static function allRegistered() {
return array_merge(self::$_registry, parent::allRegistered());
return array_merge(self::$registry, parent::allRegistered());
}
abstract function send($user);
abstract function validate($form, $user);
}
abstract class User2FABackend extends TwoFactorAuthenticationBackend {
static private $_registry = array();
static function _register($class) {
if (isset(static::$_registry[$class::$id]))
return false;
static::$_registry[$class::$id] = $class;
}
static protected $registry = array();
static function allRegistered() {
return array_merge(self::$_registry, parent::allRegistered());
return array_merge(self::$registry, parent::allRegistered());
}
abstract function send($user);
abstract function validate($form, $user);
}
@@ -193,8 +159,8 @@ abstract class User2FABackend extends TwoFactorAuthenticationBackend {
/*
* Email2FABackend
*
* Email based two factor authentication.
*
* Email based two factor authentication.
*
* This is the default 2FA that works out of the box once users configure
* it.
*
@@ -219,7 +185,7 @@ class Email2FABackend extends TwoFactorAuthenticationBackend {
return array(
'token' => new TextboxField(array(
'id'=>1, 'label'=>__('Verification Code'), 'required'=>true, 'default'=>'',
'validator'=>'number',
'validator'=>'number',
'hint'=>__('Please enter the code you were sent'),
'configuration'=>array(
'size'=>40, 'length'=>40,
@@ -234,7 +200,7 @@ class Email2FABackend extends TwoFactorAuthenticationBackend {
function validate($form, $user) {
// Make sure form is valid and token exists
if (!($form->isValid()
if (!($form->isValid()
&& ($clean=$form->getClean())
&& $clean['token']))
return false;

View File

@@ -25,12 +25,18 @@ require_once (INCLUDE_DIR.'class.api.php');
* consistency.
*/
class AjaxController extends ApiController {
function staffOnly() {
global $thisstaff;
if(!$thisstaff || !$thisstaff->isValid()) {
Http::response(401,sprintf(__('Access Denied. IP %s'),$_SERVER['REMOTE_ADDR']));
if (!$thisstaff || !$thisstaff->isValid()) {
$this->exerr(401, sprintf('%s (%s)',
__('Access Denied'), $_SERVER['REMOTE_ADDR']));
}
return true;
}
/**
* Convert a PHP array into a JSON-encoded string
*/
@@ -43,6 +49,6 @@ class AjaxController extends ApiController {
}
function get($var, $default=null) {
return (isset($_GET[$var])) ? $_GET[$var] : $default;
return $_GET[$var] ?: $default;
}
}

View File

@@ -13,6 +13,8 @@
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
include_once INCLUDE_DIR.'class.controller.php';
class API {
var $id;
@@ -91,15 +93,15 @@ class API {
}
/** Static functions **/
function add($vars, &$errors) {
static function add($vars, &$errors) {
return API::save(0, $vars, $errors);
}
function validate($key, $ip) {
static function validate($key, $ip) {
return ($key && $ip && self::getIdByKey($key, $ip));
}
function getIdByKey($key, $ip='') {
static function getIdByKey($key, $ip='') {
$sql='SELECT id FROM '.API_KEY_TABLE.' WHERE apikey='.db_input($key);
if($ip)
@@ -111,15 +113,15 @@ class API {
return $id;
}
function lookupByKey($key, $ip='') {
static function lookupByKey($key, $ip='') {
return self::lookup(self::getIdByKey($key, $ip));
}
function lookup($id) {
static function lookup($id) {
return ($id && is_numeric($id) && ($k= new API($id)) && $k->getId()==$id)?$k:null;
}
function save($id, $vars, &$errors) {
static function save($id, $vars, &$errors) {
if(!$id && (!$vars['ipaddr'] || !Validator::is_ip($vars['ipaddr'])))
$errors['ipaddr'] = __('Valid IP is required');
@@ -144,7 +146,7 @@ class API {
$sql='INSERT INTO '.API_KEY_TABLE.' SET '.$sql
.',created=NOW() '
.',ipaddr='.db_input($vars['ipaddr'])
.',apikey='.db_input(strtoupper(md5(time().$vars['ipaddr'].md5(Misc::randCode(16)))));
.',apikey='.db_input(strtoupper(Misc::randCode(48, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')));
if(db_query($sql) && ($id=db_insert_id()))
return $id;
@@ -165,28 +167,51 @@ class API {
* API request.
*/
class ApiController {
class ApiController extends Controller {
var $apikey;
private $sapi;
private $key;
public function __construct($sapi = null) {
// Set API Interface the request is coming from
$this->sapi = $sapi ?: (osTicket::is_cli() ? 'cli' : 'http');
}
public function isCli() {
return (strcasecmp($this->sapi, 'cli') === 0);
}
private function getInputStream() {
return $this->isCli() ? 'php://stdin' : 'php://input';
}
protected function getRemoteAddr() {
return $_SERVER['REMOTE_ADDR'];
}
protected function getApiKey() {
return $_SERVER['HTTP_X_API_KEY'];
}
function requireApiKey() {
# Validate the API key -- required to be sent via the X-API-Key
# header
if(!($key=$this->getApiKey()))
// Require a valid API key sent as X-API-Key HTTP header
// see getApiKey method.
if (!($key=$this->getKey()))
return $this->exerr(401, __('Valid API key required'));
elseif (!$key->isActive() || $key->getIPAddr()!=$_SERVER['REMOTE_ADDR'])
elseif (!$key->isActive() || $key->getIPAddr() != $this->getRemoteAddr())
return $this->exerr(401, __('API key not found/active or source IP not authorized'));
return $key;
}
function getApiKey() {
function getKey() {
// Lookup record using sent API Key && IP Addr
if (!$this->key
&& ($key=$this->getApiKey())
&& ($ip=$this->getRemoteAddr()))
$this->key = API::lookupByKey($key, $ip);
if (!$this->apikey && isset($_SERVER['HTTP_X_API_KEY']) && isset($_SERVER['REMOTE_ADDR']))
$this->apikey = API::lookupByKey($_SERVER['HTTP_X_API_KEY'], $_SERVER['REMOTE_ADDR']);
return $this->apikey;
return $this->key;
}
/**
@@ -194,20 +219,28 @@ class ApiController {
* hashtable. For JSON formats, this is mostly a noop, the conversion
* work will be done for XML requests
*/
function getRequest($format) {
global $ost;
$input = osTicket::is_cli()?'php://stdin':'php://input';
function getRequest($format, $validate=true) {
$input = $this->getInputStream();
if (!($stream = @fopen($input, 'r')))
$this->exerr(400, __("Unable to read request body"));
$this->exerr(400, sprintf('%s (%s)',
__("Unable to read request body"), $input));
return $this->parseRequest($stream, $format, $validate);
}
function getEmailRequest() {
if (!($data=$this->getRequest('email', false)))
$this->exerr(400, __("Unable to read email request"));
return $data;
}
function parseRequest($stream, $format, $validate=true) {
$parser = null;
switch(strtolower($format)) {
case 'xml':
if (!function_exists('xml_parser_create'))
$this->exerr(501, __('XML extension not supported'));
return $this->exerr(501, __('XML extension not supported'));
$parser = new ApiXmlDataParser();
break;
case 'json':
@@ -217,23 +250,24 @@ class ApiController {
$parser = new ApiEmailDataParser();
break;
default:
$this->exerr(415, __('Unsupported data format'));
return $this->exerr(415, __('Unsupported data format'));
}
if (!($data = $parser->parse($stream)))
if (!($data = $parser->parse($stream)) || !is_array($data)) {
$this->exerr(400, $parser->lastError());
}
//Validate structure of the request.
$this->validate($data, $format, false);
if ($validate && $data)
$this->validate($data, $format, false);
return $data;
}
function getEmailRequest() {
return $this->getRequest('email');
function parseEmail($content) {
return $this->parseRequest($content, 'email', false);
}
/**
* Structure to validate the request against -- must be overridden to be
* useful
@@ -257,14 +291,11 @@ class ApiController {
} elseif (in_array($key, $structure)) {
continue;
}
if ($strict)
return $this->exerr(400, sprintf(__("%s: Unexpected data received in API request"), "$prefix$key"));
else
$ost->logWarning(__('API Unexpected Data'),
sprintf(__("%s: Unexpected data received in API request"), "$prefix$key"),
false);
}
$error = sprintf(__("%s: Unexpected data received in API request"),
"$prefix$key");
$this->onError(400, $error, __('Unexpected Data'), !$strict);
}
return true;
}
@@ -280,37 +311,66 @@ class ApiController {
$strict);
}
protected function debug($subj, $msg) {
return $this->log($subj, $msg, LOG_DEBUG);
}
protected function logError($subj, $msg, $fatal=false) {
// If error is not fatal then log it as a warning
return $this->log($subj, $msg, $fatal ? LOG_ERR : LOG_WARN);
}
protected function log($title, $msg, $level = LOG_WARN) {
global $ost;
switch ($level) {
case LOG_WARN:
case LOG_ERR:
// We are disabling email alerts on API errors / warnings
// due to potential abuse or loops that might cause email DOS
$ost->log($level, $title, $msg, false);
break;
case LOG_DEBUG:
default:
$ost->logDebug($title, $msg);
}
return true;
}
/**
* API error & logging and response!
*
* final - don not override downstream.
*/
final public function onError($code, $error, $title = null,
$logOnly = false) {
/* If possible - DO NOT - overwrite the method downstream */
function exerr($code, $error='') {
global $ost;
if($error && is_array($error))
// Unpack the errors to string if error is an array
if ($error && is_array($error))
$error = Format::array_implode(": ", "\n", $error);
//Log the error as a warning - include api key if available.
// Log the error
$msg = $error;
if($_SERVER['HTTP_X_API_KEY'])
$msg.="\n*[".$_SERVER['HTTP_X_API_KEY']."]*\n";
$ost->logWarning(__('API Error')." ($code)", $msg, false);
// TODO: Only include API Key when in debug mode to avoid
// potentialialy leaking a valid key in system logs
if (($key=$this->getApiKey()))
$msg .= sprintf("\n[%s]\n", Format::shroud($key, 8, 16));
if (PHP_SAPI == 'cli') {
fwrite(STDERR, "({$code}) $error\n");
}
else {
$this->response($code, $error); //Responder should exit...
}
return false;
}
$title = sprintf('%s (%s)', $title ?: __('API Error'), $code);
$this->logError($title, $msg, $logOnly);
//Default response method - can be overwritten in subclasses.
function response($code, $resp) {
Http::response($code, $resp);
exit();
// If the error is not deemed fatal then simply return
if ($logOnly)
return;
// If the API Interface is CLI then throw a TicketApiError exception
// so the caller can handle the error gracefully.
// Note that we set the error code as well
if ($this->isCli())
throw new TicketApiError($error, $code);
// Respond and exit since HTTP endpoint requests
// are considered stateless
$this->response($code, $error);
}
}
@@ -327,8 +387,8 @@ class ApiXmlDataParser extends XmlDataParser {
function fixup($current) {
global $cfg;
if($current['ticket'])
$current = $current['ticket'];
if (isset($current['ticket']))
$current = $current['ticket'];
if (!is_array($current))
return $current;
@@ -381,10 +441,10 @@ class ApiXmlDataParser extends XmlDataParser {
include_once "class.json.php";
class ApiJsonDataParser extends JsonDataParser {
function parse($stream, $tidy=false) {
return $this->fixup(parent::parse($stream));
static function parse($stream, $tidy=false) {
return self::fixup(parent::parse($stream));
}
function fixup($current) {
static function fixup($current) {
if (!is_array($current))
return $current;
foreach ($current as $key=>&$value) {
@@ -434,17 +494,17 @@ class ApiEmailDataParser extends EmailDataParser {
function fixup($data) {
global $cfg;
if(!$data) return $data;
if (!$data) return $data;
$data['source'] = 'Email';
if(!$data['subject'])
if (!$data['subject'])
$data['subject'] = '[No Subject]';
if(!$data['emailId'])
if (!$data['emailId'])
$data['emailId'] = $cfg->getDefaultEmailId();
if(!$cfg->useEmailPriority())
if( !$cfg->useEmailPriority())
unset($data['priorityId']);
return $data;

View File

@@ -28,7 +28,7 @@ class Application {
array('desc'=>$desc, 'href'=>$href));
}
function getStaffApps() {
static function getStaffApps() {
return self::$staff_apps;
}
@@ -37,7 +37,7 @@ class Application {
array('desc'=>$desc, 'href'=>$href));
}
function getClientApps() {
static function getClientApps() {
return self::$client_apps;
}
@@ -46,7 +46,7 @@ class Application {
array('desc'=>$desc, 'href'=>$href));
}
function getAdminApps() {
static function getAdminApps() {
return self::$admin_apps;
}
}

View File

@@ -120,10 +120,11 @@ extends InstrumentedList {
}
$attachments = array();
// Format $new for upload() with new name
foreach ($ids as $id=>$name) {
foreach ($ids as $id=>$value) {
if (is_array($value)) list('id' => $id, 'name' => $value) = $value;
$attachments[] = array(
'id' => $id,
'name' => $name
'name' => $value
);
}
// Everything remaining in $attachments is truly new
@@ -191,7 +192,7 @@ extends InstrumentedList {
function getInlines($lang=false) { return $this->_getList(false, true, $lang); }
function getSeparates($lang=false) { return $this->_getList(true, false, $lang); }
function getAll($lang=false) { return $this->_getList(true, true, $lang); }
function count($lang=false) { return count($this->getSeparates($lang)); }
function count($lang=false): int { return count($this->getSeparates($lang)); }
function _getList($separates=false, $inlines=false, $lang=false) {
$base = $this;

View File

@@ -134,7 +134,7 @@ class ClientCreateRequest {
function attemptAutoRegister() {
global $cfg;
if (!$cfg || !$cfg->isClientRegistrationEnabled())
if (!$cfg || $cfg->isClientRegistrationMode(['disabled']))
return false;
// Attempt to automatically register
@@ -146,7 +146,7 @@ class ClientCreateRequest {
);
if ($bk->supportsInteractiveAuthentication())
// User can only be authenticated against this backend
$defaults['backend'] = $bk::$id;
$defaults['backend'] = $bk->getBkId();
if ($this_form->isValid(function($f) { return !$f->isVisibleToUsers(); })
&& ($U = User::fromVars($this_form->getClean()))
&& ($acct = ClientAccount::createForUser($U, $defaults))
@@ -159,6 +159,78 @@ class ClientCreateRequest {
}
}
/**
* Authorization backend
*
* Abstract service registry to provide authorization backends.
*/
abstract class OAuth2Backend extends ServiceRegistry {
static $name;
static $id;
protected $config;
static function register($bk) {
if (is_string($bk) && class_exists($bk))
$bk = new $bk();
if (!is_object($bk)
|| !($bk instanceof OAuth2Backend))
return false;
static::$registry[$bk->getBkId()] = $bk;
}
static function allRegistered() {
return self::getRegistry();
}
static function getBackend($id) {
if ($id && ($registry = static::allRegistered())
&& isset($registry[$id]))
return $registry[$id];
}
}
abstract class OAuth2AuthorizationBackend extends OAuth2Backend {
static protected $registry = array();
static function allRegistered() {
return array_merge(self::$registry, parent::allRegistered());
}
static function getBackend($id) {
if (($bk=parent::getBackend($id)))
return $bk;
// Overloaded backend load.
list($auth, $a, $i) = self::parseId($id);
if ($auth && ($bk=parent::getBackend($auth)))
return $bk;
}
static function parseId($id) {
return preg_split('/(?(?<=oauth2):(?!\w+)|:)/', $id);
}
abstract function getConfigForm($configId);
abstract function triggerEmailAuth($id);
abstract function callback($resp, $ref);
}
abstract class OAuth2AuthenticationBackend extends OAuth2Backend {
static protected $registry = array();
static function allRegistered() {
return array_merge(self::$registry, parent::allRegistered());
}
static function getBackend($id) {
if (($bk=parent::getBackend($id)))
return $bk;
}
}
/**
* Authentication backend
*
@@ -174,41 +246,61 @@ class ClientCreateRequest {
* receives a username and optional password. If the authentication
* succeeds, an instance deriving from <User> should be returned.
*/
abstract class AuthenticationBackend {
static protected $registry = array();
abstract class AuthenticationBackend extends ServiceRegistry {
static $name;
static $id;
protected $config;
static function register($bk) {
if (is_string($bk) && class_exists($bk))
$bk = new $bk();
/* static */
static function register($class) {
if (is_string($class) && class_exists($class))
$class = new $class();
if (!is_object($class)
|| !($class instanceof AuthenticationBackend))
if (!is_object($bk)
|| !($bk instanceof AuthenticationBackend))
return false;
return static::_register($class);
}
static function _register($class) {
// XXX: Raise error if $class::id is already in the registry
static::$registry[$class::$id] = $class;
static::$registry[$bk->getBkId()] = $bk;
}
static function allRegistered() {
return static::$registry;
return self::getRegistry();
}
static function getInteractive() {
$backends = [];
foreach (static::allRegistered() as $bk)
if ($bk->isInteractive())
$backends[] = $bk;
return $backends;
}
static function getExternal() {
$backends = [];
foreach (static::allRegistered() as $bk)
if ($bk->isExternal())
$backends[] = $bk;
return $backends;
}
static function getBackend($id) {
if ($id
&& ($backends = static::allRegistered())
&& isset($backends[$id]))
return $backends[$id];
}
static function lookupBackend($id, $class=null) {
// Lookup both registries
if (!($bk = StaffAuthenticationBackend::getBackend($id)))
$bk = UserAuthenticationBackend::getBackend($id);
// See if specific type is requesred.
if ($class && !($bk instanceof $class))
return null;
return $bk;
}
static function getSearchDirectoryBackend($id) {
if ($id
@@ -238,7 +330,7 @@ abstract class AuthenticationBackend {
foreach (static::allRegistered() as $bk) {
if ($backends //Allowed backends
&& $bk->supportsInteractiveAuthentication()
&& !in_array($bk::$id, $backends))
&& !in_array($bk->getBkId(), $backends))
// User cannot be authenticated against this backend
continue;
@@ -280,7 +372,7 @@ abstract class AuthenticationBackend {
* $forcedAuth - indicate if authentication is required.
*
*/
function processSignOn(&$errors, $forcedAuth=true) {
static function processSignOn(&$errors, $forcedAuth=true) {
foreach (static::allRegistered() as $bk) {
// All backends are queried here, even if they don't support
@@ -321,13 +413,13 @@ abstract class AuthenticationBackend {
$backends = array();
foreach (StaffAuthenticationBackend::allRegistered() as $bk)
if ($bk instanceof AuthDirectorySearch)
$backends[$bk::$id] = $bk;
$backends[$bk->getBkId()] = $bk;
foreach (UserAuthenticationBackend::allRegistered() as $bk)
if ($bk instanceof AuthDirectorySearch)
$backends[$bk::$id] = $bk;
$backends[$bk->getBkId()] = $bk;
return array_unique($backends);
return array_unique($backends, SORT_REGULAR);
}
static function searchUsers($query) {
@@ -338,13 +430,6 @@ abstract class AuthenticationBackend {
return $users;
}
/**
* Fetches the friendly name of the backend
*/
function getName() {
return static::$name;
}
/**
* Indicates if the backed supports authentication. Useful if the
* backend is used for logging or lockout only
@@ -353,6 +438,21 @@ abstract class AuthenticationBackend {
return true;
}
/**
* Alias for supportsInteractiveAuthentication()
*
*/
function isInteractive() {
return $this->supportsInteractiveAuthentication();
}
/**
* Indicates of the backend is External Authentication
*/
function isExternal() {
return ($this instanceof ExternalAuthentication);
}
/**
* Indicates if the backend supports changing a user's password. This
* would be done in two fashions. Either the currently-logged in user
@@ -437,7 +537,7 @@ abstract class AuthenticationBackend {
abstract function authenticate($username, $password);
abstract function login($user, $bk);
abstract static function getUser(); //Validates authenticated users.
abstract function getAllowedBackends($userid);
abstract static function getAllowedBackends($userid);
abstract protected function getAuthKey($user);
abstract static function signOut($user);
}
@@ -478,23 +578,18 @@ interface ExternalAuthentication {
}
abstract class StaffAuthenticationBackend extends AuthenticationBackend {
static private $_registry = array();
static function _register($class) {
static::$_registry[$class::$id] = $class;
}
static protected $registry = array();
static function allRegistered() {
return array_merge(self::$_registry, parent::allRegistered());
return array_merge(self::$registry, parent::allRegistered());
}
function isBackendAllowed($staff, $bk) {
static function isBackendAllowed($staff, $bk) {
if (!($backends=self::getAllowedBackends($staff->getId())))
return true; //No restrictions
return in_array($bk::$id, array_map('strtolower', $backends));
return in_array($bk->getBkId(), array_map('strtolower', $backends));
}
function getPasswordPolicies($user=null) {
@@ -502,14 +597,14 @@ abstract class StaffAuthenticationBackend extends AuthenticationBackend {
$policies = PasswordPolicy::allActivePolicies();
if ($cfg && ($policy = $cfg->getStaffPasswordPolicy())) {
foreach ($policies as $P)
if ($policy == $P::$id)
if ($policy == $P->getBkId())
return array($P);
}
return $policies;
}
function getAllowedBackends($userid) {
static function getAllowedBackends($userid) {
$backends =array();
//XXX: Only one backend can be specified at the moment.
@@ -556,16 +651,19 @@ abstract class StaffAuthenticationBackend extends AuthenticationBackend {
}
// Tag the authkey.
$authkey = $bk::$id.':'.$authkey;
$authkey = $bk->getBkId().':'.$authkey;
// Now set session crap and lets roll baby!
$authsession = &$_SESSION['_auth']['staff'];
$authsession = array(); //clear.
$authsession['id'] = $staff->getId();
$authsession['key'] = $authkey;
$authsession['2fa'] = $auth2fa;
// Set TIME_BOMB to regenerate the session 10 seconds after login
$_SESSION['TIME_BOMB'] = time() + 10;
// Set session token
$staff->setSessionToken();
// Set Auth Key
$staff->setAuthKey($authkey);
$staff->refreshSession(true); //set the hash.
Signal::send('auth.login.succeeded', $staff);
if ($bk->supportsInteractiveAuthentication())
@@ -612,6 +710,7 @@ abstract class StaffAuthenticationBackend extends AuthenticationBackend {
if (!($bk=static::getBackend($id)) //get the backend
|| !($staff = $bk->validate($auth)) //Get AuthicatedUser
|| !($staff instanceof Staff)
|| !$staff->isActive()
|| $staff->getId() != $_SESSION['_auth']['staff']['id'] // check ID
)
return null;
@@ -636,7 +735,9 @@ abstract class StaffAuthenticationBackend extends AuthenticationBackend {
protected function validate($authkey) {
if (($staff = StaffSession::lookup($authkey)) && $staff->getId())
if (($staff = StaffSession::lookup($authkey))
&& $staff->getId()
&& $staff->isActive())
return $staff;
}
}
@@ -659,7 +760,8 @@ abstract class ExternalStaffAuthenticationBackend
$this->getServiceName());
?>
<a class="external-sign-in" title="<?php echo $service; ?>"
href="login.php?do=ext&amp;bk=<?php echo urlencode(static::$id); ?>">
href="login.php?do=ext&amp;bk=<?php echo
urlencode($this->getBkId()); ?>">
<?php if (static::$sign_in_image_url) { ?>
<img class="sign-in-image" src="<?php echo static::$sign_in_image_url;
?>" alt="<?php echo $service; ?>"/>
@@ -677,15 +779,17 @@ abstract class ExternalStaffAuthenticationBackend
}
function triggerAuth() {
$_SESSION['ext:bk:id'] = $this->getBkId();
// For legacy plugins prior to v1.17
$_SESSION['ext:bk:class'] = get_class($this);
}
}
Signal::connect('api', function($dispatcher) {
$dispatcher->append(
url('^/auth/ext$', function() {
if ($class = $_SESSION['ext:bk:class']) {
$bk = StaffAuthenticationBackend::getBackend($class::$id)
?: UserAuthenticationBackend::getBackend($class::$id);
if ($id = $_SESSION['ext:bk:id']) {
$bk = StaffAuthenticationBackend::getBackend($id)
?: UserAuthenticationBackend::getBackend($id);
if ($bk instanceof ExternalAuthentication)
$bk->triggerAuth();
}
@@ -694,31 +798,25 @@ Signal::connect('api', function($dispatcher) {
});
abstract class UserAuthenticationBackend extends AuthenticationBackend {
static private $_registry = array();
static function _register($class) {
static::$_registry[$class::$id] = $class;
}
static protected $registry = array();
static function allRegistered() {
return array_merge(self::$_registry, parent::allRegistered());
return array_merge(self::$registry, parent::allRegistered());
}
function getPasswordPolicies($user=null) {
global $cfg;
$policies = PasswordPolicy::allActivePolicies();
if ($cfg && ($policy = $cfg->getClientPasswordPolicy())) {
foreach ($policies as $P)
if ($policy == $P::$id)
if ($policy == $P->getId())
return array($P);
}
return $policies;
}
function getAllowedBackends($userid) {
static function getAllowedBackends($userid) {
$backends = array();
$sql = 'SELECT A1.backend FROM '.USER_ACCOUNT_TABLE
.' A1 INNER JOIN '.USER_EMAIL_TABLE.' A2 ON (A2.user_id = A1.user_id)'
@@ -739,7 +837,7 @@ abstract class UserAuthenticationBackend extends AuthenticationBackend {
global $ost;
if (!$user || !$bk
|| !$bk::$id //Must have ID
|| !$bk->getBkId() //Must have ID
|| !($authkey = $bk->getAuthKey($user)))
return false;
@@ -754,13 +852,11 @@ abstract class UserAuthenticationBackend extends AuthenticationBackend {
// Tag the user and associated ticket in the SESSION
$this->setAuthKey($user, $bk, $authkey);
// Set Session Token
$user->setSessionToken();
//The backend used decides the format of the auth key.
// XXX: encrypt to hide the bk??
$user->setAuthKey($authkey);
$user->refreshSession(true); //set the hash.
//Log login info...
$msg=sprintf(_S('%1$s (%2$s) logged in [%3$s]'
/* Tokens are <username>, <id>, and <ip> */),
@@ -771,6 +867,9 @@ abstract class UserAuthenticationBackend extends AuthenticationBackend {
$type = array('type' => 'login');
Signal::send('person.login', $u, $type);
// Set TIME_BOMB to regenerate the session 10 seconds after login
$_SESSION['TIME_BOMB'] = time() + 10;
if ($bk->supportsInteractiveAuthentication() && ($acct=$user->getAccount()))
$acct->cancelResetTokens();
@@ -784,7 +883,7 @@ abstract class UserAuthenticationBackend extends AuthenticationBackend {
$authkey = $key ?: $bk->getAuthKey($user);
//Tag the authkey.
$authkey = $bk::$id.':'.$authkey;
$authkey = $bk->getBkId().':'.$authkey;
//Set the session goodies
$authsession = &$_SESSION['_auth']['user'];
@@ -831,6 +930,9 @@ abstract class UserAuthenticationBackend extends AuthenticationBackend {
)
return null;
if (($account=$user->getAccount()) && !$account->isActive())
return null;
$user->setAuthKey($_SESSION['_auth']['user']['key']);
return $user;
@@ -839,7 +941,9 @@ abstract class UserAuthenticationBackend extends AuthenticationBackend {
protected function validate($userid) {
if (!($user = User::lookup($userid)))
return false;
elseif (!$user->getAccount())
elseif (!($account=$user->getAccount()))
return false;
elseif (!$account->isActive())
return false;
return new ClientSession(new EndUser($user));
@@ -865,10 +969,11 @@ abstract class ExternalUserAuthenticationBackend
?>
<a class="external-sign-in" title="<?php echo $service; ?>"
href="login.php?do=ext&amp;bk=<?php echo urlencode(static::$id); ?>">
href="login.php?do=ext&amp;bk=<?php echo
urlencode($this->getBkId()); ?>">
<?php if (static::$sign_in_image_url) { ?>
<img class="sign-in-image" src="<?php echo static::$sign_in_image_url;
?>" alt="<?php $service; ?>"/>
?>" alt="<?php echo $service; ?>"/>
<?php } else { ?>
<div class="external-auth-box">
<span class="external-auth-icon">
@@ -883,6 +988,8 @@ abstract class ExternalUserAuthenticationBackend
}
function triggerAuth() {
$_SESSION['ext:bk:id'] = $this->getBkId();
// Legacy for plugins prior to v1.17
$_SESSION['ext:bk:class'] = get_class($this);
}
}
@@ -928,7 +1035,7 @@ abstract class AuthStrikeBackend extends AuthenticationBackend {
return false;
}
function getAllowedBackends($userid) {
static function getAllowedBackends($userid) {
return array();
}
@@ -945,22 +1052,23 @@ abstract class AuthStrikeBackend extends AuthenticationBackend {
}
abstract function authStrike($credentials);
abstract function authTimeout();
abstract static function authStrike($credentials);
abstract static function authTimeout();
}
/*
* Backend to monitor staff's failed login attempts
*/
class StaffAuthStrikeBackend extends AuthStrikeBackend {
static $id = "authstrike.staff";
function authTimeout() {
static function authTimeout() {
global $ost;
$cfg = $ost->getConfig();
$authsession = &$_SESSION['_auth']['staff'];
if (!$authsession['laststrike'])
if (!isset($authsession['laststrike']))
return;
//Veto login due to excessive login attempts.
@@ -975,7 +1083,7 @@ class StaffAuthStrikeBackend extends AuthStrikeBackend {
$authsession['strikes']=0;
}
function authstrike($credentials) {
static function authstrike($credentials) {
global $ost;
$cfg = $ost->getConfig();
@@ -1022,8 +1130,9 @@ StaffAuthenticationBackend::register('StaffAuthStrikeBackend');
* Backend to monitor user's failed login attempts
*/
class UserAuthStrikeBackend extends AuthStrikeBackend {
static $id = "authstrike.user";
function authTimeout() {
static function authTimeout() {
global $ost;
$cfg = $ost->getConfig();
@@ -1044,7 +1153,7 @@ class UserAuthStrikeBackend extends AuthStrikeBackend {
$authsession['strikes']=0;
}
function authstrike($credentials) {
static function authstrike($credentials) {
global $ost;
$cfg = $ost->getConfig();
@@ -1146,7 +1255,7 @@ class PasswordResetTokenBackend extends StaffAuthenticationBackend {
|| $id != $staff->getId())
$errors['msg'] = __('Invalid reset token');
elseif (!($ts = $_config->lastModified($_POST['token']))
&& ($ost->getConfig()->getPwResetWindow() < (time() - strtotime($ts))))
|| ($ost->getConfig()->getPwResetWindow() < (time() - strtotime($ts))))
$errors['msg'] = __('Invalid reset token');
elseif (!$staff->forcePasswdRest())
$errors['msg'] = __('Unable to reset password');
@@ -1373,7 +1482,7 @@ class ClientPasswordResetTokenBackend extends UserAuthenticationBackend {
|| $id != 'c'.$client->getId())
$errors['msg'] = __('Invalid reset token');
elseif (!($ts = $_config->lastModified($_POST['token']))
&& ($ost->getConfig()->getPwResetWindow() < (time() - strtotime($ts))))
|| ($ost->getConfig()->getPwResetWindow() < (time() - strtotime($ts))))
$errors['msg'] = __('Invalid reset token');
elseif (!$acct->forcePasswdReset())
$errors['msg'] = __('Unable to reset password');
@@ -1422,9 +1531,7 @@ class BadPassword extends Exception {}
class ExpiredPassword extends Exception {}
class PasswordUpdateFailed extends Exception {}
abstract class PasswordPolicy {
static protected $registry = array();
abstract class PasswordPolicy extends ServiceRegistry {
static $id;
static $name;
@@ -1440,14 +1547,7 @@ abstract class PasswordPolicy {
abstract function onLogin($user, $password);
/*
* get friendly name of the policy
*/
function getName() {
return static::$name;
}
/*
* Check a password aganist all available policies
* Check a password aganist all available policies
*/
static function checkPassword($new, $current, $bk=null) {
if ($bk && is_a($bk, 'AuthenticationBackend'))
@@ -1461,7 +1561,7 @@ abstract class PasswordPolicy {
static function allActivePolicies() {
$policies = array();
foreach (array_reverse(static::$registry) as $P) {
foreach (array_reverse(self::getRegistry()) as $P) {
if (is_string($P) && class_exists($P))
$P = new $P();
if ($P instanceof PasswordPolicy)
@@ -1470,10 +1570,6 @@ abstract class PasswordPolicy {
return $policies;
}
static function register($policy) {
static::$registry[] = $policy;
}
static function cleanSessions($model, $user=null) {
$criteria = array();
@@ -1498,7 +1594,7 @@ abstract class PasswordPolicy {
return false;
}
return SessionData::objects()->filter($criteria)->delete();
return DatabaseSessionRecord::objects()->filter($criteria)->delete();
}
}
Signal::connect('auth.clean', array('PasswordPolicy', 'cleanSessions'));
@@ -1526,9 +1622,13 @@ extends PasswordPolicy {
}
function onSet($passwd, $current) {
if (strlen($passwd) < 6) {
$pwdlen = strlen($passwd);
if ($pwdlen < 6) {
throw new BadPassword(
__('Password must be at least 6 characters'));
} elseif ($pwdlen > 128) {
throw new BadPassword(
sprintf('%s - %s', __('Password is too long'), __('maximum of 128 characters allowed')));
}
// XXX: Changing case is technicall changing the password
if (0 === strcasecmp($passwd, $current)) {

View File

@@ -52,7 +52,7 @@ abstract class AvatarSource {
$this->mode = $mode;
}
function getName() {
static function getName() {
return __(static::$name);
}

View File

@@ -17,11 +17,11 @@
require_once "class.filter.php";
class Banlist {
function add($email,$submitter='') {
static function add($email,$submitter='') {
return self::getSystemBanList()->addRule('email','equal',$email);
}
function remove($email) {
static function remove($email) {
return self::getSystemBanList()->removeRule('email','equal',$email);
}
@@ -52,11 +52,11 @@ class Banlist {
return false;
}
function includes($email) {
static function includes($email) {
return self::getSystemBanList()->containsRule('email','equal',$email);
}
function ensureSystemBanList() {
static function ensureSystemBanList() {
if (!($id=Filter::getByName('SYSTEM BAN LIST')))
$id=self::createSystemBanList();
@@ -64,7 +64,7 @@ class Banlist {
return $id;
}
function createSystemBanList() {
static function createSystemBanList() {
# XXX: Filter::create should return the ID!!!
$errors=array();
return Filter::create(array(
@@ -80,7 +80,7 @@ class Banlist {
), $errors);
}
function getSystemBanList() {
static function getSystemBanList() {
return self::ensureSystemBanList();
}

View File

@@ -20,7 +20,7 @@ class Base32 {
* to double performance
*/
function encode($inString)
static function encode($inString)
{
$outString = "";
$compBits = "";
@@ -68,7 +68,7 @@ class Base32 {
*
*/
function decode($inString) {
static function decode($inString) {
/* declaration */
$deCompBits = '';
$outString = '';

View File

@@ -261,7 +261,7 @@ extends VerySimpleModel {
return $responses;
}
function responsesByDeptId($deptId, $explicit=false) {
static function responsesByDeptId($deptId, $explicit=false) {
return self::getCannedResponses($deptId, $explicit);
}
@@ -311,6 +311,12 @@ extends VerySimpleModel {
return true;
}
function staffCanAccess($staff) {
if (!$staff instanceof Staff)
return false;
return (!$this->dept || (($role = $staff->getRole($this->dept)) && $role->hasPerm(Canned::PERM_MANAGE)));
}
}
RolePermission::register( /* @trans */ 'Knowledgebase', Canned::getPermissions());

View File

@@ -74,7 +74,7 @@ class Charset {
}
//Wrapper for utf-8 transcoding.
function utf8($text, $charset=null) {
static function utf8($text, $charset=null) {
return self::transcode($text, $charset, self::UTF8);
}
}

View File

@@ -151,6 +151,19 @@ implements EmailContact, ITicketUser, TemplateVariable {
return $this->user->getId();
}
function getEmailAddress() {
$emailaddr = (string) $this->getEmail();
if (($name=$this->getName()))
$emailaddr = sprintf('"%s" <%s>',
(string) $name,
$emailaddr);
return $emailaddr;
}
function __toString() {
return $this->getEmailAddress();
}
abstract function getTicketId();
abstract function getTicket();
}
@@ -484,7 +497,7 @@ class ClientAccount extends UserAccount {
if ($vars['backend']) {
$this->set('backend', $vars['backend']);
if ($vars['username'])
$this->set('username', $vars['username']);
$this->set('username', Format::sanitize($vars['username']));
}
if ($vars['passwd1']) {

View File

@@ -39,12 +39,6 @@ implements EmailContact, ITicketUser {
var $active;
function __toString() {
return Format::htmlchars($this->toString());
}
function toString() {
return sprintf('"%s" <%s>', $this->getName(), $this->getEmail());
}
function getId() {
return $this->id;
@@ -81,6 +75,16 @@ implements EmailContact, ITicketUser {
function getEmail() {
return $this->user->getEmail();
}
function getEmailAddress() {
$emailaddr = (string) $this->getEmail();
if (($name=$this->getName()))
$emailaddr = sprintf('"%s" <%s>',
(string) $name,
$emailaddr);
return $emailaddr;
}
function getName() {
return $this->user->getName();
}
@@ -135,6 +139,10 @@ implements EmailContact, ITicketUser {
return $this->user_id;
}
function __toString() {
return Format::htmlchars($this->getEmailAddress());
}
function hasFlag($flag) {
return ($this->get('flags', 0) & $flag) != 0;
}

View File

@@ -31,14 +31,13 @@ class Config {
var $defaults = array(); # List of default values
function __construct($section=null, $defaults=array()) {
if ($defaults)
$this->defaults = $defaults;
if ($section)
$this->section = $section;
if ($this->section === null)
return false;
if ($defaults)
$this->defaults = $defaults;
if (isset($_SESSION['cfg:'.$this->section]))
$this->session = &$_SESSION['cfg:'.$this->section];
@@ -62,6 +61,10 @@ class Config {
return $info;
}
function toArray() {
return $this->getInfo();
}
function get($key, $default=null) {
if (isset($this->session) && isset($this->session[$key]))
return $this->session[$key];
@@ -74,7 +77,12 @@ class Config {
}
function exists($key) {
return $this->get($key, null) ? true : false;
if ((isset($this->session) && array_key_exists($key, $this->session))
|| array_key_exists($key, $this->config)
|| array_key_exists($key, $this->defaults))
return true;
return false;
}
function set($key, $value) {
@@ -187,7 +195,9 @@ extends VerySimpleModel {
))->delete();
}
function getConfigsByNamespace($namespace=false, $key, $value=false) {
static function getConfigsByNamespace(?string $namespace, $key, $value=false) {
$filter = array();
$filter['key'] = $key;
@@ -659,12 +669,15 @@ class OsticketConfig extends Config {
return $this->alertEmail;
}
function getDefaultSMTPEmail() {
function getDefaultSmtpAccount() {
if (!$this->defaultSmtpAccount && $this->get('default_smtp_id'))
$this->defaultSmtpAccount = SmtpAccount::lookup($this->get('default_smtp_id'));
if(!$this->defaultSMTPEmail && $this->get('default_smtp_id'))
$this->defaultSMTPEmail = Email::lookup($this->get('default_smtp_id'));
return $this->defaultSmtpAccount;
}
return $this->defaultSMTPEmail;
function getDefaultMTA() {
return $this->getDefaultSmtpAccount();
}
function getDefaultPriorityId() {
@@ -1778,7 +1791,7 @@ class OsticketConfig extends Config {
}
//Used to detect version prior to 1.7 (useful during upgrade)
/* static */ function getDBVersion() {
static function getDBVersion() {
$sql='SELECT `ostversion` FROM '.TABLE_PREFIX.'config '
.'WHERE id=1';
return db_result(db_query($sql));

View File

@@ -0,0 +1,44 @@
<?php
/*********************************************************************
class.controller.php
Peter Rotich
Copyright (c) osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
abstract class Controller {
/*
* access
*
* This routine can be defined downstream to check if user has
* permission to call routines in the controller.
*
*/
function access() {
return true;
}
// Wrapper for onFatalError
public function exerr($code, $error='') {
return $this->onFatalError($code, $error);
}
public function onFatalError($code, $error) {
$this->onError($code, $error);
// On error should exit but we're making doubly sure
$this->response($code, $error);
exit();
}
protected function response($code, $response) {
Http::response($code, $response);
exit();
}
abstract function onError($code, $error, $title=null, $logOnly=false);
}

View File

@@ -20,12 +20,12 @@ require_once INCLUDE_DIR.'class.signal.php';
class Cron {
function MailFetcher() {
require_once(INCLUDE_DIR.'class.mailfetch.php');
MailFetcher::run(); //Fetch mail..frequency is limited by email account setting.
static function MailFetcher() {
require_once(INCLUDE_DIR.'class.email.php');
osTicket\Mail\Fetcher::run(); //Fetch mail..frequency is limited by email account setting.
}
function TicketMonitor() {
static function TicketMonitor() {
require_once(INCLUDE_DIR.'class.ticket.php');
Ticket::checkOverdue(); //Make stale tickets overdue
// Cleanup any expired locks
@@ -34,34 +34,34 @@ class Cron {
}
function PurgeLogs() {
static function PurgeLogs() {
global $ost;
// Once a day on a 5-minute cron
if (rand(1,300) == 42)
if($ost) $ost->purgeLogs();
}
function PurgeDrafts() {
static function PurgeDrafts() {
require_once(INCLUDE_DIR.'class.draft.php');
Draft::cleanup();
}
function CleanOrphanedFiles() {
static function CleanOrphanedFiles() {
require_once(INCLUDE_DIR.'class.file.php');
AttachmentFile::deleteOrphans();
}
function CleanExpiredSessions() {
static function CleanExpiredSessions() {
require_once(INCLUDE_DIR.'class.ostsession.php');
DbSessionBackend::cleanup();
osTicketSession::cleanup();
}
function CleanPwResets() {
static function CleanPwResets() {
require_once(INCLUDE_DIR.'class.config.php');
ConfigItem::cleanPwResets();
}
function MaybeOptimizeTables() {
static function MaybeOptimizeTables() {
// Once a week on a 5-minute cron
$chance = rand(1,2000);
switch ($chance) {
@@ -102,7 +102,7 @@ class Cron {
}
}
function run(){ //called by outside cron NOT autocron
static function run(){ //called by outside cron NOT autocron
global $ost;
if (!$ost || $ost->isUpgradePending())
return;

View File

@@ -104,7 +104,7 @@ class Crypto {
list(, $crypt, $ciphertext) = explode('$', $ciphertext, 3);
//Get crypto.... based on crypt tag.
if(!$crypt || !($crypto=Crypto::get($crypt)) || !$crypto->exists())
if(!$crypt || !($crypto=Crypto::get($crypt)))
return false;
$crypto->setKeys($key, $skey);
@@ -221,8 +221,7 @@ class Crypto {
* decrypt() which will perform the respective operations on the text
* subjects using a specific library.
*/
/* abstract */
class CryptoAlgo {
abstract class CryptoAlgo {
var $master_key;
var $sub_key;
@@ -300,8 +299,7 @@ class CryptoAlgo {
* available and supported. This method is abstract and should be
* defined in extension classes.
*/
/* abstract */
function exists() { return false; }
abstract static function exists();
}
@@ -341,7 +339,7 @@ Class CryptoMcrypt extends CryptoAlgo {
return ($c
&& $c['name']
&& $c['mode']
&& $this->exists()
&& self::exists()
&& mcrypt_module_open($c['name'], '', $c['mode'], '')
);
}
@@ -357,7 +355,7 @@ Class CryptoMcrypt extends CryptoAlgo {
*/
function encrypt($text, $cid=0) {
if(!$this->exists()
if(!self::exists()
|| !$text
|| !($cipher=$this->getCipher($cid))
|| !$cipher['cid'])
@@ -394,7 +392,7 @@ Class CryptoMcrypt extends CryptoAlgo {
*/
function decrypt($ciphertext) {
if(!$this->exists()
if(!self::exists()
|| !$ciphertext
|| $ciphertext[0] != '$'
)
@@ -431,7 +429,7 @@ Class CryptoMcrypt extends CryptoAlgo {
return $plaintext;
}
function exists() {
static function exists() {
return (extension_loaded('mcrypt')
&& function_exists('mcrypt_module_open'));
}
@@ -473,7 +471,7 @@ class CryptoOpenSSL extends CryptoAlgo {
return ($c
&& $c['method']
&& $this->exists()
&& self::exists()
&& openssl_cipher_iv_length($c['method'])
);
}
@@ -489,7 +487,7 @@ class CryptoOpenSSL extends CryptoAlgo {
*/
function encrypt($text, $cid=0) {
if(!$this->exists()
if(!self::exists()
|| !$text
|| !($cipher=$this->getCipher($cid)))
return false;
@@ -517,7 +515,7 @@ class CryptoOpenSSL extends CryptoAlgo {
function decrypt($ciphertext) {
if(!$this->exists() || !$ciphertext || $ciphertext[0] != '$')
if(!self::exists() || !$ciphertext || $ciphertext[0] != '$')
return false;
list(, $cid, $ciphertext) = explode('$', $ciphertext, 3);
@@ -539,7 +537,7 @@ class CryptoOpenSSL extends CryptoAlgo {
return $plaintext;
}
function exists() {
static function exists() {
return (extension_loaded('openssl') && function_exists('openssl_cipher_iv_length'));
}
}
@@ -595,7 +593,7 @@ class CryptoPHPSecLib extends CryptoAlgo {
function encrypt($text, $cid=0) {
if(!$this->exists()
if(!self::exists()
|| !$text
|| !($cipher=$this->getCipher($cid))
|| !($crypto=$this->getCrypto($cipher['cid']))
@@ -612,7 +610,7 @@ class CryptoPHPSecLib extends CryptoAlgo {
function decrypt($ciphertext) {
if(!$this->exists() || !$ciphertext || $ciphertext[0] != '$')
if(!self::exists() || !$ciphertext || $ciphertext[0] != '$')
return false;
list(, $cid, $ciphertext) = explode('$', $ciphertext, 3);
@@ -634,7 +632,7 @@ class CryptoPHPSecLib extends CryptoAlgo {
return $crypto->decrypt($ciphertext);
}
function exists() {
static function exists() {
return class_exists('Crypt_AES');
}
}

View File

@@ -60,7 +60,7 @@ Class CSRF {
function getToken() {
if (!$this->csrf['token'] || $this->isExpired()) {
if ((!is_array($this->csrf) || !$this->csrf['token']) || $this->isExpired()) {
$this->rotate();
} else {
//Reset the timer

View File

@@ -604,7 +604,7 @@ implements TemplateVariable, Searchable {
return true;
}
function export($dept, $criteria=null, $filename='') {
static function export($dept, $criteria=null, $filename='') {
include_once(INCLUDE_DIR.'class.error.php');
$members = $dept->getMembers();
@@ -634,12 +634,12 @@ implements TemplateVariable, Searchable {
return $row ? $row[0] : 0;
}
function getNameById($id) {
static function getNameById($id) {
$names = Dept::getDepartments();
return $names[$id];
}
function getDefaultDeptName() {
static function getDefaultDeptName() {
global $cfg;
return ($cfg
@@ -941,7 +941,8 @@ implements TemplateVariable, Searchable {
$dropped = array();
foreach ($this->extended as $DA)
$dropped[$DA->staff_id] = 1;
while (list(, list($staff_id, $role_id, $alerts)) = each($access)) {
foreach ($access as $acc) {
list ($staff_id, $role_id, $alerts) = $acc;
unset($dropped[$staff_id]);
if (!$role_id || !Role::lookup($role_id))
$errors['members'][$staff_id] = __('Select a valid role');

View File

@@ -29,10 +29,14 @@ class Dispatcher {
function resolve($url, $args=null) {
if ($this->file) { $this->lazy_load(); }
# Support HTTP method emulation with the _method GET argument
if (isset($_GET['_method'])) {
$_SERVER['REQUEST_METHOD'] = strtoupper($_GET['_method']);
unset($_GET['_method']);
if ($_SERVER['REQUEST_METHOD'] == 'POST'
&& isset($_POST['_method'])
&& in_array(strtoupper($_POST['_method']), array('PUT', 'PATCH', 'DELETE'))) {
$_SERVER['REQUEST_METHOD'] = strtoupper($_POST['_method']);
unset($_POST['_method']);
}
// Decode URL for accurate matching
$url = urldecode($url);
foreach ($this->urls as $matcher) {
if ($matcher->matches($url)) {
return $matcher->dispatch($url, $args);
@@ -105,7 +109,7 @@ class UrlMatcher {
$f = array_filter(array_keys($this->matches), 'is_numeric');
$this->matches = array_intersect_key($this->matches, array_flip($f));
if (@get_class($this->func) == "Dispatcher") {
if (is_object($this->func) && (@get_class($this->func) == "Dispatcher")) {
# Trim the leading match off the $url and call the
# sub-dispatcher. This will be the case for lines in the URL
# file like
@@ -136,7 +140,13 @@ class UrlMatcher {
if ($class) {
# Create instance of the class, which is the first item,
# then call the method which is the second item
$func = array(new $class, $func);
$class = new $class;
# Check access at the controller level
if (!$class->access())
Http::response(403, __('Access Denied!!!'));
# Create callable function, class is the first item,
# then call the method is the second item
$func = array($class, $func);
}
if (!is_callable($func))
@@ -160,7 +170,7 @@ class UrlMatcher {
if (strpos($class, ":")) {
list($file, $class) = explode(":", $class, 2);
include $file;
include_once $file;
}
return array($class, $func);
}

View File

@@ -65,16 +65,14 @@ class Draft extends VerySimpleModel {
$draft_body = $draft->getBody();
}
$attrs[] = sprintf('data-draft-original="%s"',
Format::htmlchars(Format::viewableImages($original)));
Format::viewableImages($original, [], true));
return array(Format::htmlchars(Format::viewableImages($draft_body)),
return array(Format::viewableImages($draft_body, [], true),
implode(' ', $attrs));
}
function getAttachmentIds($body=false) {
static function getAttachmentIds($body=false) {
$attachments = array();
if (!$body)
$body = $this->getBody();
$body = Format::localizeInlineImages($body);
$matches = array();
if (preg_match_all('/"cid:([\\w.-]{32})"/', $body, $matches)) {
@@ -83,6 +81,7 @@ class Draft extends VerySimpleModel {
foreach ($files as $F) {
$attachments[] = array(
'id' => $F->getId(),
'name' => $F->getName(),
'inline' => true
);
}

View File

@@ -194,7 +194,7 @@ class DynamicForm extends VerySimpleModel {
function save($refetch=false) {
if (count($this->dirty))
$this->set('updated', new SqlFunction('NOW'));
if ($rv = parent::save($refetch | $this->dirty))
if ($rv = parent::save($refetch || $this->dirty))
return $this->saveTranslations();
return $rv;
}
@@ -291,7 +291,15 @@ class DynamicForm extends VerySimpleModel {
return true;
}
static function getNewInstance() {
return static::objects()
->one()
->instanciate();
}
static function rebuildDynamicDataViews() {
// Flush the object cache
ModelInstanceManager::flushCache();
return self::ensureDynamicDataViews(true, true);
}
@@ -431,7 +439,7 @@ class DynamicForm extends VerySimpleModel {
// @see http://code.google.com/p/flexviews/
static function getDynamicDataViewFields($exclude) {
$fields = array();
foreach (static::getInstance()->getFields() as $f) {
foreach (static::getNewInstance()->getFields() as $f) {
if ($exclude && in_array($f->get('name'), $exclude))
continue;
@@ -485,13 +493,7 @@ class UserForm extends DynamicForm {
static function getInstance() {
if (!isset(static::$instance))
static::$instance = static::getUserForm()->instanciate();
return static::$instance;
}
static function getNewInstance() {
$o = static::objects()->one();
static::$instance = $o->instanciate();
static::$instance = self::getNewInstance();
return static::$instance;
}
}
@@ -527,16 +529,9 @@ class TicketForm extends DynamicForm {
static function getInstance() {
if (!isset(static::$instance))
self::getNewInstance();
static::$instance = self::getNewInstance();
return static::$instance;
}
static function getNewInstance() {
$o = static::objects()->one();
static::$instance = $o->instanciate();
return static::$instance;
}
}
// Add fields from the standard ticket form to the ticket filterable fields
Filter::addSupportedMatches(/* @trans */ 'Ticket Data', function() {
@@ -799,7 +794,7 @@ class DynamicFormField extends VerySimpleModel {
* flags which should be set on the new field to implement the
* requirement / visibility mode.
*/
function allRequirementModes() {
static function allRequirementModes() {
return array(
'a' => array('desc' => __('Optional'),
'flags' => self::FLAG_CLIENT_VIEW | self::FLAG_AGENT_VIEW
@@ -919,7 +914,7 @@ class DynamicFormField extends VerySimpleModel {
/* `variable` is used for automation. Internally it's called `name` */
), "name");
}
if (preg_match('/[.{}\'"`; ]/u', $this->get('name')))
if ($this->get('name') && !preg_match('/^(?!\d)([[:alnum:]]|_)+$/u', $this->get('name')))
$this->addError(__(
'Invalid character in variable name. Please use letters and numbers only.'
), 'name');
@@ -1039,6 +1034,10 @@ class DynamicFormEntry extends VerySimpleModel {
return $this->form->getInstructions();
}
function getNotice() {
return $this->form->getNotice();
}
function getDynamicForm() {
return $this->form;
}
@@ -1203,7 +1202,7 @@ class DynamicFormEntry extends VerySimpleModel {
return $vars;
}
function forTicket($ticket_id, $force=false) {
static function forTicket($ticket_id, $force=false) {
static $entries = array();
if (!isset($entries[$ticket_id]) || $force) {
$stuff = DynamicFormEntry::objects()
@@ -1216,7 +1215,7 @@ class DynamicFormEntry extends VerySimpleModel {
return $entries[$ticket_id];
}
function forTask($id, $force=false) {
static function forTask($id, $force=false) {
static $entries = array();
if (!isset($entries[$id]) || $force) {
$stuff = DynamicFormEntry::objects()->filter(array(
@@ -1246,7 +1245,7 @@ class DynamicFormEntry extends VerySimpleModel {
$this->object_id = $object_id;
}
function forObject($object_id, $object_type) {
static function forObject($object_id, $object_type) {
return DynamicFormEntry::objects()
->filter(array('object_id'=>$object_id, 'object_type'=>$object_type));
}
@@ -1356,7 +1355,7 @@ class DynamicFormEntry extends VerySimpleModel {
//use getChanges if getClean returns an empty array
$fieldClean = $field->getClean() ?: $field->getChanges();
if (is_array($fieldClean) && $fieldClean[0])
$fieldClean = json_decode($fieldClean[0], true);
$fieldClean = is_string($fieldClean[0]) ? json_decode($fieldClean[0], true) : $fieldClean[0];
} else
$fieldClean = $field->getClean();
@@ -1594,7 +1593,7 @@ class SelectionField extends FormField {
$selection = array();
if ($value && !is_array($value))
$value = array($value);
$value = JsonDataParser::parse($value) ?: array($value);
if ($value && is_array($value)) {
foreach ($value as $k=>$v) {
@@ -1634,6 +1633,7 @@ class SelectionField extends FormField {
$values = array();
$choices = $this->getChoices();
foreach (explode(',', $value) as $V) {
$V = trim($V);
if (isset($choices[$V]))
$values[$V] = $choices[$V];
}
@@ -1734,6 +1734,7 @@ class SelectionField extends FormField {
?: __('Unknown or invalid input');
}
} elseif ($config['typeahead']
&& $entry
&& ($entered = $this->getWidget()->getEnteredValue())
&& !in_array($entered, $entry)
&& $entered != $entry) {
@@ -1917,7 +1918,7 @@ class SelectionField extends FormField {
$name = $name ?: $this->get('name');
$val = $value;
if ($value && is_array($value))
$val = '"?'.implode('("|,|$)|"?', array_keys($value)).'("|,|$)';
$val = '"?(?<![0-9])'.implode('("|,|$)|"?(?<![0-9])', array_keys($value)).'("|,|$)';
switch ($method) {
case '!includes':
return Q::not(array("{$name}__regex" => $val));

File diff suppressed because it is too large Load Diff

View File

@@ -259,7 +259,7 @@ class Export {
// Filename or stream to export agents to
$filename = $filename ?: sprintf('Agents-%s.csv',
strftime('%Y%m%d'));
date('Ymd'));
Http::download($filename, "text/$how");
$depts = Dept::getDepartments(null, true, Dept::DISPLAY_DISABLED);
echo self::dumpQuery($agents, array(
@@ -296,7 +296,7 @@ static function departmentMembers($dept, $agents, $filename='', $how='csv') {
// Filename or stream to export depts' agents to
$filename = $filename ?: sprintf('%s-%s.csv', $dept->getName(),
strftime('%Y%m%d'));
date('Ymd'));
Http::download($filename, "text/$how");
echo self::dumpQuery($agents, array(
'::getName' => 'Name',
@@ -322,7 +322,7 @@ static function departmentMembers($dept, $agents, $filename='', $how='csv') {
exit;
}
static function audits($type, $filename='', $tableInfo='', $object='', $how='csv', $show_viewed=true, $data=array(), CsvExporter $exporter) {
static function audits($type, ?string $filename, ?string $tableInfo, ?string $object, ?string $how='csv', ?bool $show_viewed=true, ?array $data=array(), CsvExporter $exporter) {
$headings = array('Description', 'Timestamp', 'IP');
switch ($type) {
case 'audit':
@@ -535,7 +535,7 @@ abstract class Exporter {
|| !($email=$cfg->getDefaultEmail()))
return false;
$mailer = new Mailer($email);
$mailer = new osTicket\Mail\Mailer($email);
$mailer->addFileObject($file);
$subject = __("Export");
$body = __("Attached is file containing the export you asked us to send you!");
@@ -620,7 +620,7 @@ class CsvExporter extends Exporter {
}
function write($data) {
fputcsv($this->fp, $this->escape($data), $this->getDelimiter());
fputcsv($this->fp, $this->escape($data), $this->getDelimiter(), "\"", "");
}
}
@@ -680,7 +680,7 @@ class ResultSetExporter {
}
}
// Evalutate :: function call on target current
if ($func && (method_exists($current, $func) || method_exists($current, '__call'))) {
if (($current && $func) && (method_exists($current, $func) || method_exists($current, '__call'))) {
$current = $current->{$func}();
}
@@ -725,7 +725,7 @@ class CsvResultsExporter extends ResultSetExporter {
$delimiter = $this->getDelimiter();
// Output a UTF-8 BOM (byte order mark)
fputs($this->output, chr(0xEF) . chr(0xBB) . chr(0xBF));
fputcsv($this->output, $this->getHeaders(), $delimiter);
fputcsv($this->output, $this->getHeaders(), $delimiter, "\"", "");
while ($row=$this->next())
fputcsv($this->output, array_map(
function($v){
@@ -733,7 +733,7 @@ class CsvResultsExporter extends ResultSetExporter {
return "'".$v;
return $v;
}, $row),
$delimiter);
$delimiter, "\"", "");
if (!$tmp)
fclose($this->output);
@@ -743,12 +743,11 @@ class CsvResultsExporter extends ResultSetExporter {
class JsonResultsExporter extends ResultSetExporter {
function dump() {
require_once(INCLUDE_DIR.'class.json.php');
$exp = new JsonDataEncoder();
$rows = array();
while ($row=$this->nextArray()) {
$rows[] = $row;
}
echo $exp->encode($rows);
echo JsonDataEncoder::encode($rows);
}
}

View File

@@ -347,7 +347,7 @@ class FAQ extends VerySimpleModel {
static function getFeatured() {
return self::objects()
->filter(array('ispublished__in'=>array(1,2), 'category__ispublic'=>1))
->filter(array('ispublished'=>2, 'category__ispublic__in'=>[1,2]))
->order_by('-ispublished');
}

View File

@@ -15,6 +15,19 @@ require_once(INCLUDE_DIR.'class.signal.php');
require_once(INCLUDE_DIR.'class.error.php');
/**
* FileObject Interface
*
* Methods File Objects should support
**/
interface FileObjectInterface {
function getUId();
function getKey();
function getName();
function getData();
function getMimeType();
}
/**
* Represents a file stored in a storage backend. It is generally attached
* to something; however company logos, login page backdrops, and other
@@ -29,8 +42,8 @@ require_once(INCLUDE_DIR.'class.error.php');
* - 'L' => Logo
* - 'B' => Backdrop
*/
class AttachmentFile extends VerySimpleModel {
class AttachmentFile extends VerySimpleModel
implements FileObjectInterface {
static $meta = array(
'table' => FILE_TABLE,
'pk' => array('id'),
@@ -71,6 +84,10 @@ class AttachmentFile extends VerySimpleModel {
return $this->id;
}
function getUId() {
return $this->getId();
}
function getType() {
return $this->type;
}
@@ -158,10 +175,14 @@ class AttachmentFile extends VerySimpleModel {
}
function display($scale=false, $ttl=86400) {
// Force download if not safe viewable image
if (!$this->isInlineSafeImage())
return $this->download(false, 'attachment');
$this->makeCacheable($ttl);
if ($scale && extension_loaded('gd')) {
$image = imagecreatefromstring($this->getData());
if ($scale && extension_loaded('gd')
&& ($image = imagecreatefromstring($this->getData()))) {
$width = imagesx($image);
if ($scale <= $width) {
$height = imagesy($image);
@@ -197,6 +218,12 @@ class AttachmentFile extends VerySimpleModel {
'type' => $options['type']))))
$options['id'] = $a->getId();
// Force attachment disposition if not inline-safe image
if (isset($options['disposition'])
&& strcasecmp($options['disposition'], 'inline') == 0
&& !$this->isInlineSafeImage())
$options['disposition'] = 'attachment';
return static::generateDownloadUrl($this->getId(),
strtolower($this->getKey()), $this->getSignature(),
$options);
@@ -207,7 +234,7 @@ class AttachmentFile extends VerySimpleModel {
function getExternalDownloadUrl($options=array()) {
global $cfg;
$download = self::getDownloadUrl($options);
$download = $this->getDownloadUrl($options);
// Separate URL handle and args
list($handle, $args) = explode('file.php?', $download);
@@ -250,7 +277,8 @@ class AttachmentFile extends VerySimpleModel {
$check = static::_genUrlSignature($this->getId(), $this->getKey(),
$this->getSignature(), $expires);
return $signature == $check;
return hash_equals($check, $signature);
}
static function _genUrlSignature($id, $key, $signature, $expires) {
@@ -270,7 +298,7 @@ class AttachmentFile extends VerySimpleModel {
$inline = ($thisstaff ? ($thisstaff->getImageAttachmentView() === 'inline') : false);
$disposition = ((($disposition && strcasecmp($disposition, 'inline') == 0)
|| $inline)
&& strpos($this->getType(), 'image/') !== false)
&& $this->isInlineSafeImage())
? 'inline' : 'attachment';
$ttl = ($expires) ? $expires - Misc::gmtime() : false;
$bk = $this->open();
@@ -284,7 +312,7 @@ class AttachmentFile extends VerySimpleModel {
exit();
}
function _getKeyAndHash($data=false, $file=false) {
static function _getKeyAndHash($data=false, $file=false) {
if ($file) {
$sha1 = base64_encode(sha1_file($data, true));
$md5 = base64_encode(md5_file($data, true));
@@ -349,6 +377,7 @@ class AttachmentFile extends VerySimpleModel {
case IMAGETYPE_GIF:
case IMAGETYPE_JPEG:
case IMAGETYPE_PNG:
case IMAGETYPE_WEBP:
break;
default:
$error = __('Invalid image file type');
@@ -370,6 +399,7 @@ class AttachmentFile extends VerySimpleModel {
case IMAGETYPE_GIF:
case IMAGETYPE_JPEG:
case IMAGETYPE_PNG:
case IMAGETYPE_WEBP:
break;
default:
$error = __('Invalid image file type');
@@ -477,9 +507,11 @@ class AttachmentFile extends VerySimpleModel {
elseif ($bk->write($file['data']) && $bk->flush()) {
$succeeded = true; break;
}
}
catch (Exception $e) {
} catch (Throwable $t) {
// Try next backend
// Backends can throw an exception or error.
// TODO: Log any exceptions and errors for debugging
// purposes.
}
// Fallthrough to default backend if different?
}
@@ -610,9 +642,16 @@ class AttachmentFile extends VerySimpleModel {
static function lookupByHash($hash) {
if (isset(static::$keyCache[$hash]))
return static::$keyCache[$hash];
// Cache a negative lookup if no such file exists
return parent::lookup(array('key' => $hash));
try {
return parent::lookup(array('key' => $hash));
} catch (ObjectNotUnique $e) {
// TODO: Figure out why key collission might be happening AND
// make key (hash) unique field in the file table as a
// protection measure. For now we're returning null to avoid possible wrong file
// being displayed.
return null;
}
}
static function lookup($id) {
@@ -624,7 +663,7 @@ class AttachmentFile extends VerySimpleModel {
/*
Method formats http based $_FILE uploads - plus basic validation.
*/
function format($files) {
static function format($files) {
global $ost;
if(!$files || !is_array($files))
@@ -693,6 +732,29 @@ class AttachmentFile extends VerySimpleModel {
->filter(array('ft' => 'B'))
->order_by('created');
}
/**
* Determines whether a MIME type is safe to render inline as a passive image.
*
* @param string $type
* @return bool
*/
static function isInlineSafeImageType($type) {
$type = strtolower(trim((string) $type));
return strpos($type, 'image/') === 0
&& $type !== 'image/svg+xml'
&& $type !== 'image/svg';
}
/**
* Determines whether this file is safe to render inline as a passive image.
*
* @return bool
*/
function isInlineSafeImage() {
return static::isInlineSafeImageType($this->getType());
}
}
class FileStorageBackend {
@@ -1020,14 +1082,29 @@ class OneSixAttachments extends FileStorageBackend {
FileStorageBackend::register('6', 'OneSixAttachments');
// FileObject - wrapper for SplFileObject class
class FileObject extends SplFileObject {
class FileObject extends SplFileObject
implements FileObjectInterface {
protected $_key;
protected $_sig;
protected $_filename;
function __construct($file, $mode='r') {
parent::__construct($file, $mode);
}
function getUId() {
return $this->getKey();
}
function getKey() {
if (!isset($this->_key))
list($this->_key, $this->_sig) = AttachmentFile::_getKeyAndHash(
$this->getContents());
return $this->_key;
}
/* This allows us to set REAL file name as opposed to basename of the
* FS file in question
*/
@@ -1039,6 +1116,10 @@ class FileObject extends SplFileObject {
return $this->_filename ?: parent::getFilename();
}
function getName() {
return $this->getFilename();
}
/*
* Set mime type - well formated mime is expected.
*/
@@ -1047,12 +1128,8 @@ class FileObject extends SplFileObject {
}
function getMimeType() {
if (!isset($this->_mimetype)) {
// Try to to auto-detect mime type
$finfo = new finfo(FILEINFO_MIME);
$this->_mimetype = $finfo->buffer($this->getContents(),
FILEINFO_MIME_TYPE);
}
if (!isset($this->_mimetype))
$this->_mimetype = self::mime_type($this->getRealPath());
return $this->_mimetype;
}
@@ -1068,6 +1145,28 @@ class FileObject extends SplFileObject {
function getData() {
return $this->getContents();
}
/*
* Given a filepath - auto detect the mime type
*
*/
static function mime_type($filepath) {
// Try to to auto-detect mime type
$type = null;
if (function_exists('finfo_open')) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$type = finfo_file($finfo, $filepath);
finfo_close($finfo);
}
return $type ?: mime_content_type($filepath);
}
/*
* Compare mime type of file content to a given mime
*/
static function mimecmp($filepath, $mime) {
return strcasecmp(self::mime_type($filepath), $mime) !== 0;
}
}
?>

View File

@@ -165,7 +165,7 @@ extends VerySimpleModel {
return ($this->use_replyto_email);
}
function disableFilters($object) {
static function disableFilters($object) {
switch (get_class($object)) {
case 'Topic':
$object_id = 'topic_id';
@@ -207,7 +207,7 @@ extends VerySimpleModel {
'configuration__like' => sprintf('%%"%s":%s}', $object_id, $id)));
foreach($actions as $fa) {
// Put a flag on the filter
$fa->setFilterFlags(false, 'Filter::FLAG_DELETED_OBJECT', true);
FilterAction::setFilterFlags($fa, 'Filter::FLAG_DELETED_OBJECT', true);
$fa->save();
// Disable the filter
@@ -238,6 +238,7 @@ extends VerySimpleModel {
$extra['notes'] = Format::sanitize($extra['notes']);
$rule = array_merge($extra,array('what'=>$what, 'how'=>$how, 'val'=>$val));
$rule = new FilterRule($rule);
$rule->created = SqlFunction::NOW();
$this->rules->add($rule);
if ($rule->save())
return true;
@@ -374,7 +375,7 @@ extends VerySimpleModel {
return $keys;
}
/* static */ function getSupportedMatchTypes() {
static function getSupportedMatchTypes() {
return array(
'equal'=> __('Equal'),
'not_equal'=> __('Not Equal'),
@@ -404,7 +405,7 @@ extends VerySimpleModel {
elseif(($filter=static::getByName($vars['name'])) && $filter->id!=$this->id)
$errors['name'] = __('Name already in use');
if(!$errors && !self::validate_rules($vars,$errors) && !$errors['rules'])
if(!$errors && !$this->validate_rules($vars,$errors) && !$errors['rules'])
$errors['rules'] = __('Unable to validate rules as entered');
$targets = self::getTargets();
@@ -475,7 +476,7 @@ extends VerySimpleModel {
}
/** static functions **/
function getTargets() {
static function getTargets() {
return array(
'Any' => __('Any'),
'Web' => __('Web Forms'),
@@ -488,7 +489,7 @@ extends VerySimpleModel {
}
function validate_rules($vars,&$errors) {
$matches = array_keys(self::getSupportedMatchFields());
$matches = self::getSupportedMatchFields();
$types = array_keys(self::getSupportedMatchTypes());
$rules = array();
foreach ($vars['rules'] as $i=>$rule) {
@@ -501,36 +502,34 @@ extends VerySimpleModel {
if (is_array($rule)) {
if($rule["w"] || $rule["h"]) {
// Check for REGEX compile errors
if (in_array($rule["h"], array('match','not_match'))) {
$wrapped = "/".$rule["v"]."/iu";
if (false === @preg_match($rule["v"], ' ')
&& (false !== @preg_match($wrapped, ' ')))
$rule["v"] = $wrapped;
// Check for REGEX compile errors
if (in_array($rule["h"], array('match','not_match'))) {
$wrapped = "/".$rule["v"]."/iu";
if (false === @preg_match($rule["v"], ' ')
&& (false !== @preg_match($wrapped, ' ')))
$rule["v"] = $wrapped;
}
if(!$rule["w"] || !in_array($rule["w"],$matches))
$errors["rule_$i"]=__('Invalid match selection');
elseif(!$rule["h"] || !in_array($rule["h"],$types))
$errors["rule_$i"]=__('Invalid match type selection');
elseif(!$rule["v"])
$errors["rule_$i"]=__('Value required');
elseif($rule["w"]=='email'
&& $rule["h"]=='equal'
&& !Validator::is_email($rule["v"]))
$errors["rule_$i"]=__('Valid email required for the match type');
elseif (in_array($rule["h"], array('match','not_match'))
&& (false === @preg_match($rule["v"], ' ')))
$errors["rule_$i"] = sprintf(__('Regex compile error: (#%s)'),
preg_last_error());
else //for everything-else...we assume it's valid.
$rules[]=array('what'=>$rule["w"],
'how'=>$rule["h"],'val'=>trim($rule["v"]));
} elseif($rule["v"]) {
$errors["rule_$i"]=__('Incomplete selection');
}
if(!$rule["w"] || !in_array($rule["w"],$matches))
$errors["rule_$i"]=__('Invalid match selection');
elseif(!$rule["h"] || !in_array($rule["h"],$types))
$errors["rule_$i"]=__('Invalid match type selection');
elseif(!$rule["v"])
$errors["rule_$i"]=__('Value required');
elseif($rule["w"]=='email'
&& $rule["h"]=='equal'
&& !Validator::is_email($rule["v"]))
$errors["rule_$i"]=__('Valid email required for the match type');
elseif (in_array($rule["h"], array('match','not_match'))
&& (false === @preg_match($rule["v"], ' ')))
$errors["rule_$i"] = sprintf(__('Regex compile error: (#%s)'),
preg_last_error());
else //for everything-else...we assume it's valid.
$rules[]=array('what'=>$rule["w"],
'how'=>$rule["h"],'val'=>trim($rule["v"]));
}elseif($rule["v"]) {
$errors["rule_$i"]=__('Incomplete selection');
}
}
}
@@ -565,13 +564,22 @@ extends VerySimpleModel {
return parent::save($refetch || $this->dirty);
}
static function __create($vars,&$errors) {
$filter = new static($vars);
if ($filter->save()) {
$filter->save_rules($vars, $errors);
$filter->save_actions($filter->getId(), $vars, $errors);
return $filter;
}
}
static function create($vars,&$errors) {
$filter = new static($vars);
if ($filter->save())
return $filter;
}
function validate_actions($vars, &$errors) {
static function validate_actions($vars, &$errors) {
//allow the save if it is to set a filter flag
if ($vars['pass'])
return true;
@@ -907,6 +915,7 @@ class TicketFilter {
);
foreach ($auto_headers as $header=>$find) {
$header = strtolower($header);
if(!isset($headers[$header])) continue;
$value = strtoupper($headers[$header]);
@@ -930,8 +939,7 @@ class TicketFilter {
}
static function isBounce($headers) {
if($headers && !is_array($headers))
if ($headers && !is_array($headers))
$headers = Mail_Parse::splitHeaders($headers);
$bounce_headers = array(
@@ -946,6 +954,7 @@ class TicketFilter {
);
foreach ($bounce_headers as $header => $find) {
$header = strtolower($header);
if(!isset($headers[$header])) continue;
@list($func, $searches, $pos, $neg) = $find;
@@ -967,7 +976,7 @@ class TicketFilter {
* Normalize ticket source to supported filter target
*
*/
function origin2target($origin) {
static function origin2target($origin) {
$sources=array('web' => 'Web', 'email' => 'Email', 'phone' => 'Web', 'staff' => 'Web', 'api' => 'API');
return $sources[strtolower($origin)];

View File

@@ -89,13 +89,11 @@ class FilterAction extends VerySimpleModel {
return $this->_impl;
}
function setFilterFlags($actions=false, $flag, $bool) {
static function setFilterFlags(?object $actions, $flag, $bool) {
$flag = constant($flag);
if ($actions) {
if ($actions)
foreach ($actions as $action)
$action->setFilterFlag($flag, $bool);
} else
$this->setFilterFlag($flag, $bool);
}
function setFilterFlag($flag, $bool) {
@@ -644,7 +642,7 @@ class FA_SendEmail extends TriggerAction {
// Honor FROM address settings
if (!$config['from'] || !($mailer = Email::lookup($config['from'])))
$mailer = new Mailer();
$mailer = new osTicket\Mail\Mailer();
// Allow %{user} in the To: line
$replacer = new VariableReplacer();
@@ -653,10 +651,9 @@ class FA_SendEmail extends TriggerAction {
));
$to = $replacer->replaceVars($config['recipients']);
require_once PEAR_DIR . 'Mail/RFC822.php';
require_once PEAR_DIR . 'PEAR.php';
if (!($mails = Mail_RFC822::parseAddressList($to)) || PEAR::isError($mails))
if (!($mails = Mail_Parse::parseAddressList($to)) || PEAR::isError($mails))
return false;
// Allow %{recipient} in the body
@@ -695,7 +692,7 @@ class FA_SendEmail extends TriggerAction {
'size' => 80, 'length' => 1000,
),
'validators' => function($self, $value) {
if (!($mails = Mail_RFC822::parseAddressList($value)) || PEAR::isError($mails))
if (!($mails = Mail_Parse::parseAddressList($value)) || PEAR::isError($mails))
$self->addError('Unable to parse address list. '
.'Use commas to separate addresses.');

View File

@@ -20,7 +20,7 @@ require_once INCLUDE_DIR.'class.variable.php';
class Format {
function file_size($bytes) {
static function file_size($bytes) {
if(!is_numeric($bytes))
return $bytes;
@@ -32,7 +32,7 @@ class Format {
return round(($bytes/1048576),1).' mb';
}
function filesize2bytes($size) {
static function filesize2bytes($size) {
switch (substr($size, -1)) {
case 'M': case 'm': return (int)$size <<= 20;
case 'K': case 'k': return (int)$size <<= 10;
@@ -42,11 +42,11 @@ class Format {
return $size;
}
function filename($filename) {
static function filename($filename) {
return preg_replace('/[^a-zA-Z0-9\-\._]/', '-', $filename);
}
function mimedecode($text, $encoding='UTF-8') {
static function mimedecode($text, $encoding='UTF-8') {
// Handle poorly or completely un-encoded header values (
if (function_exists('mb_detect_encoding'))
if (($src_enc = mb_detect_encoding($text))
@@ -76,7 +76,7 @@ class Format {
* language sub-component is defined in RFC5646, and that the filename
* is URL encoded (in the charset specified)
*/
function decodeRfc5987($filename) {
static function decodeRfc5987($filename) {
$match = array();
if (preg_match("/([\w!#$%&+^_`{}~-]+)'([\w-]*)'(.*)$/",
$filename, $match))
@@ -91,12 +91,12 @@ class Format {
* Json Encoder
*
*/
function json_encode($what) {
static function json_encode($what) {
require_once (INCLUDE_DIR.'class.json.php');
return JsonDataEncoder::encode($what);
}
function phone($phone) {
static function phone($phone) {
$stripped= preg_replace("/[^0-9]/", "", $phone);
if(strlen($stripped) == 7)
@@ -107,7 +107,23 @@ class Format {
return $phone;
}
function truncate($string,$len,$hard=false) {
static function mask($str, $start = 0, $length = null, $mask="*") {
$mask = preg_replace("/\S/", $mask, $str);
if ($length)
$str = substr_replace($str, substr($mask, $start, $length), $start, $length);
else
$str = substr_replace ($str, substr($mask, $start), $start);
return $str;
}
static function shroud($str, $start=0, $length=null) {
$str = $length ? substr($str, 0, $length) : $str;
return self::mask($str, $start, $length);
}
static function truncate($string,$len,$hard=false) {
if(!$len || $len>strlen($string))
return $string;
@@ -117,15 +133,15 @@ class Format {
return $hard?$string:(substr($string,0,strrpos($string,' ')).' ...');
}
function strip_slashes($var) {
static function strip_slashes($var) {
return is_array($var)?array_map(array('Format','strip_slashes'),$var):stripslashes($var);
}
function wrap($text, $len=75) {
static function wrap($text, $len=75) {
return $len ? wordwrap($text, $len, "\n", true) : $text;
}
function html_balance($html, $remove_empty=true) {
static function html_balance($html, $remove_empty=true) {
if (!extension_loaded('dom'))
return $html;
@@ -178,7 +194,7 @@ class Format {
return preg_replace('`^<div>|</div>$`', '', trim($html));
}
function html($html, $config=array()) {
static function html($html, $config=array()) {
require_once(INCLUDE_DIR.'htmLawed.php');
$spec = false;
if (isset($config['spec']))
@@ -202,7 +218,7 @@ class Format {
return htmLawed($html, $config, $spec);
}
function html2text($html, $width=74, $tidy=true) {
static function html2text($html, $width=74, $tidy=true) {
if (!$html)
return $html;
@@ -262,6 +278,11 @@ class Format {
$props = array();
foreach ($styles as $i=>&$s) {
@list($prop, $val) = explode(':', $s);
// Strip all CSS url() usages in inline styles
if (preg_match('/\burl\s*\(/i', $val)) {
unset($styles[$i]);
continue;
}
if (isset($props[$prop])) {
unset($styles[$i]);
continue;
@@ -294,7 +315,7 @@ class Format {
}
}
function safe_html($html, $options=array()) {
static function safe_html($html, $options=array()) {
global $cfg;
$options = array_merge(array(
@@ -318,9 +339,10 @@ class Format {
':<html[^>]+:i', # drop html attributes
':<(a|span) (name|style)="(mso-bookmark\:)?_MailEndCompose">(.+)?<\/(a|span)>:', # Drop _MailEndCompose
':<div dir=(3D)?"ltr">(.*?)<\/div>(.*):is', # drop Gmail "ltr" attributes
':data-cid="[^"]*":', # drop image cid attributes
'(position: ?(-webkit-)?(static|relative|fixed|absolute|sticky|initial|inherit);?)', # Position styling
':[\x{2002}-\x{200B}]+:u', # unicode spaces
),
array('', '', '', '', '<html', '$4', '$2 $3', ''),
array('', '', '', '', '<html', '$4', '$2 $3', '', ' '),
$html);
// HtmLawed specific config only
@@ -329,7 +351,8 @@ class Format {
'balance' => $options['balance'],
'comment' => 1, //Remove html comments (OUTLOOK LOVE THEM)
'tidy' => -1,
'deny_attribute' => 'id, formaction, on*',
'elements' => '*-form-input-button',
'deny_attribute' => 'id, formaction, action, srcset, data*, on*',
'schemes' => 'href: aim, feed, file, ftp, gopher, http, https, irc, mailto, news, nntp, sftp, ssh, telnet; *:file, http, https; src: cid, http, https, data',
'hook_tag' => function($e, $a=0) { return Format::__html_cleanup($e, $a); },
);
@@ -338,34 +361,37 @@ class Format {
if ($cfg)
$whitelist = $cfg->getIframeWhitelist();
if (!empty($whitelist)) {
$config['elements'] = '*+iframe';
$whitelist_quoted = array_map(function ($d) {
return preg_quote($d, '`');
}, $whitelist);
$config['elements'] .= '+iframe';
$config['spec'] = 'iframe=-*,height,width,type,style,src(match="`^(https?:)?//(www\.)?('
.implode('|', $whitelist)
.')/?([^@]*)$`i"),frameborder'.($options['spec'] ? '; '.$options['spec'] : '').',allowfullscreen';
.implode('|', $whitelist_quoted)
.')(\?|/|#)([^@]*)$`i"),frameborder'.($options['spec'] ? '; '.$options['spec'] : '').',allowfullscreen';
}
return Format::html($html, $config);
}
function localizeInlineImages($text) {
static function localizeInlineImages($text) {
// Change file.php urls back to content-id's
return preg_replace(
'`src="(?:https?:/)?(?:/[^/"]+)*?/file\\.php\\?(?:\w+=[^&]+&(?:amp;)?)*?key=([^&]+)[^"]*`',
'src="cid:$1', $text);
'`<img src="(?:https?:/)?(?:/[^/"]+)*?/file\\.php\\?(?:\w+=[^&"]+&(?:amp;)?)*?key=([^&]+)[^"]*`',
'<img src="cid:$1', $text);
}
function sanitize($text, $striptags=false, $spec=false) {
static function sanitize($text, $striptags=false, $spec=false) {
// Localize inline images before sanitizing content
$text = self::localizeInlineImages($text);
//balance and neutralize unsafe tags.
$text = Format::safe_html($text, array('spec' => $spec));
$text = self::localizeInlineImages($text);
//If requested - strip tags with decoding disabled.
return $striptags?Format::striptags($text, false):$text;
}
function htmlchars($var, $sanitize = false) {
static function htmlchars($var, $sanitize = false) {
static $phpversion = null;
if (is_array($var)) {
@@ -393,7 +419,7 @@ class Format {
}
}
function htmldecode($var) {
static function htmldecode($var) {
if(is_array($var))
return array_map(array('Format','htmldecode'), $var);
@@ -405,12 +431,20 @@ class Format {
return htmlspecialchars_decode($var, $flags);
}
function input($var) {
static function http_query_string(string $query, ?array $filter = null) {
$args = [];
parse_str($query, $args);
if ($filter && is_array($filter))
$args = array_diff_key($args, array_flip($filter));
return http_build_query($args);
}
static function input($var) {
return Format::htmlchars($var);
}
//Format text for display..
function display($text, $inline_images=true, $balance=true) {
static function display($text, $inline_images=true, $balance=true) {
global $cfg;
// Exclude external images?
@@ -450,22 +484,23 @@ class Format {
return $text;
}
function stripExternalImages($input, $display=false) {
static function stripExternalImages($input, $display=false) {
global $cfg;
// Allowed Inline External Image Extensions
$allowed = array('gif', 'png', 'jpg', 'jpeg');
$exclude = !$cfg->allowExternalImages();
$local = false;
$input = preg_replace_callback('/<img ([^>]*)(src="([^"]+)")([^>]*)\/?>/',
function($match) use ($local, $allowed, $exclude, $display) {
if (strpos($match[3], 'cid:') !== false)
$local = true;
$input = preg_replace_callback('/<img\b([^>]*?)\bsrc\s*=\s*(["\'])(.*?)\2([^>]*)\/?>/i',
function($match) use ($allowed, $exclude, $display) {
$src = $match[3];
// CID is only valid if it is the prefix of the src value
$local = (stripos(ltrim($src), 'cid:') === 0);
// Split the src URL and get the extension
$part = parse_url($match[3], PHP_URL_PATH);
$path = explode('.', $part);
$part = parse_url($src, PHP_URL_PATH);
$path = explode('.', (string) $part);
$ext = preg_split('/[^A-Za-z]/', end($path))[0];
if (!$local && (($exclude && $display) || !in_array($ext, $allowed)))
@@ -473,12 +508,13 @@ class Format {
else
return $match[0];
},
$input);
$input
);
return $input;
}
function striptags($var, $decode=true) {
static function striptags($var, $decode=true) {
if(is_array($var))
return array_map(array('Format','striptags'), $var, array_fill(0, count($var), $decode));
@@ -487,7 +523,7 @@ class Format {
}
// Strip all Emoticon/Emoji characters until we support them
function strip_emoticons($text) {
static function strip_emoticons($text) {
return preg_replace(array(
'/[\x{1F601}-\x{1F64F}]/u', # Emoticons
'/[\x{1F680}-\x{1F6C0}]/u', # Transport/Map
@@ -510,17 +546,18 @@ class Format {
'/[\x{23E0}-\x{23EF}]/u', # More Buttons
'/[\x{2310}-\x{231F}]/u', # Hourglass/Watch
'/[\x{1000B6}]/u', # Private Use Area (Plane 16)
'/[\x{2322}-\x{232F}]/u' # Keyboard
'/[\x{2322}-\x{232F}]/u', # Keyboard
'/\x{00B0}|\x{00BA}/u' # Degree Symbol
), '', $text);
}
// Insert </br> tag inside empty <p> tags to ensure proper editor spacing
function editor_spacing($text) {
static function editor_spacing($text) {
return preg_replace('/<p><\/p>/', '<p><br></p>', $text);
}
//make urls clickable. Mainly for display
function clickableurls($text, $target='_blank') {
static function clickableurls($text, $target='_blank') {
global $ost;
// Find all text between tags
@@ -553,23 +590,27 @@ class Format {
$text);
}
function stripEmptyLines($string) {
static function stripEmptyLines($string) {
return preg_replace("/\n{3,}/", "\n\n", trim($string));
}
function viewableImages($html, $options=array()) {
static function viewableImages($html, $options=array(), $format=false) {
$cids = $images = array();
$options +=array(
'disposition' => 'inline');
return preg_replace_callback('/"cid:([\w._-]{32})"/',
$html = preg_replace_callback('/("|&quot;)cid:([\w._-]{32})("|&quot;)/',
function($match) use ($options, $images) {
if (!($file = AttachmentFile::lookup($match[1])))
if (!($file = AttachmentFile::lookup($match[2])))
return $match[0];
if (!$file->isInlineSafeImage())
return $match[0];
return sprintf('"%s" data-cid="%s"',
$file->getDownloadUrl($options), $match[1]);
$file->getDownloadUrl($options), $match[2]);
}, $html);
return $format ? Format::htmlchars($html, true) : $html;
}
@@ -583,7 +624,7 @@ class Format {
* @param array $array The array to implode
* @return string The imploded array
*/
function array_implode( $glue, $separator, $array ) {
static function array_implode( $glue, $separator, $array ) {
if ( !is_array( $array ) ) return $array;
@@ -598,7 +639,7 @@ class Format {
return implode( $separator, $string );
}
function number($number, $locale=false) {
static function number($number, $locale=false) {
if (is_array($number))
return array_map(array('Format','number'), $number);
@@ -619,7 +660,7 @@ class Format {
* TODO: Combine this routine with Format::number and pass in type of
* formatting.
*/
function ordinalsuffix($number, $locale=false) {
static function ordinalsuffix($number, $locale=false) {
if (is_array($number))
return array_map(array('Format', 'ordinalsuffix'), $number);
@@ -646,7 +687,7 @@ class Format {
}
/* elapsed time */
function elapsedTime($sec) {
static function elapsedTime($sec) {
if(!$sec || !is_numeric($sec)) return "";
@@ -660,7 +701,7 @@ class Format {
return $tstring;
}
function __formatDate($timestamp, $format, $fromDb, $dayType, $timeType,
static function __formatDate($timestamp, $format, $fromDb, $dayType, $timeType,
$strftimeFallback, $timezone, $user=false) {
global $cfg;
static $cache;
@@ -701,7 +742,7 @@ class Format {
// IntDateFormat
// Format datetime to desired format in accorrding to desired locale
function IntDateFormat(DateTime $datetime, $format, $options=array()) {
static function IntDateFormat(DateTime $datetime, $format, $options=array()) {
global $cfg;
if (!$datetime instanceof DateTime)
@@ -730,11 +771,12 @@ class Format {
if ($cfg && $cfg->isForce24HourTime())
$format = str_replace('X', 'R', $format);
// TODO: Deprecated; replace this soon
return strftime($format, $timestamp);
}
// Normalize ambiguous timezones
function timezone($tz, $default=false) {
static function timezone($tz, $default=false) {
// Translate ambiguous 'GMT' timezone
if ($tz == 'GMT')
@@ -765,7 +807,7 @@ class Format {
return $tz;
}
function parseDateTime($date, $locale=null, $format=false) {
static function parseDateTime($date, $locale=null, $format=false) {
global $cfg;
if (!$date)
@@ -795,7 +837,7 @@ class Format {
return $datetime;
}
function time($timestamp, $fromDb=true, $format=false, $timezone=false, $user=false) {
static function time($timestamp, $fromDb=true, $format=false, $timezone=false, $user=false) {
global $cfg;
return self::__formatDate($timestamp,
@@ -804,7 +846,7 @@ class Format {
'%X', $timezone ?: $cfg->getTimezone(), $user);
}
function date($timestamp, $fromDb=true, $format=false, $timezone=false, $user=false) {
static function date($timestamp, $fromDb=true, $format=false, $timezone=false, $user=false) {
global $cfg;
return self::__formatDate($timestamp,
@@ -813,7 +855,7 @@ class Format {
'%x', $timezone ?: $cfg->getTimezone(), $user);
}
function datetime($timestamp, $fromDb=true, $format=false, $timezone=false, $user=false) {
static function datetime($timestamp, $fromDb=true, $format=false, $timezone=false, $user=false) {
global $cfg;
return self::__formatDate($timestamp,
@@ -822,7 +864,7 @@ class Format {
'%x %X', $timezone ?: $cfg->getTimezone(), $user);
}
function daydatetime($timestamp, $fromDb=true, $format=false, $timezone=false, $user=false) {
static function daydatetime($timestamp, $fromDb=true, $format=false, $timezone=false, $user=false) {
global $cfg;
return self::__formatDate($timestamp,
@@ -831,7 +873,7 @@ class Format {
'%x %X', $timezone ?: $cfg->getTimezone(), $user);
}
function getStrftimeFormat($format) {
static function getStrftimeFormat($format) {
static $codes, $ids;
if (!isset($codes)) {
@@ -894,7 +936,7 @@ class Format {
}
// Translate php date / time formats to js equivalent
function dtfmt_php2js($format) {
static function dtfmt_php2js($format) {
$codes = array(
// Date
@@ -925,13 +967,13 @@ class Format {
// Thanks, http://stackoverflow.com/a/2955878/1025836
/* static */
function slugify($text) {
static function slugify($text) {
// convert special characters to entities
$text = htmlentities($text, ENT_NOQUOTES, 'UTF-8');
// removes entity suffixes, leaving only un-accented characters
$text = preg_replace('~&([A-za-z])(?:acute|cedil|circ|grave|orn|ring|slash|th|tilde|uml);~', '$1', $text);
$text = preg_replace('~&([A-za-z]{2})(?:lig);~', '$1', $text);
$text = preg_replace('~&([A-Za-z])(?:acute|cedil|circ|grave|orn|ring|slash|th|tilde|uml);~', '$1', $text);
$text = preg_replace('~&([A-Za-z]{2})(?:lig);~', '$1', $text);
// replace non letter or digits by -
$text = preg_replace('~[^\p{L}\p{N}]+~u', '-', $text);
@@ -967,7 +1009,7 @@ class Format {
* References:
* http://www.ietf.org/rfc/rfc2397.txt
*/
function parseRfc2397($data, $output_encoding=false, $always_convert=true) {
static function parseRfc2397($data, $output_encoding=false, $always_convert=true) {
if (substr($data, 0, 5) != "data:")
return array('data'=>$data, 'type'=>'text/plain');
@@ -1004,7 +1046,7 @@ class Format {
// Performs Unicode normalization (where possible) and splits words at
// difficult word boundaries (for far eastern languages)
function searchable($text, $lang=false) {
static function searchable($text, $lang=false, $length=false) {
global $cfg;
if (function_exists('normalizer_normalize')) {
@@ -1036,10 +1078,14 @@ class Format {
// Drop leading and trailing whitespace
$text = trim($text);
}
if ($length && (str_word_count($text) > $length))
return null;
return $text;
}
function relativeTime($to, $from=false, $granularity=1) {
static function relativeTime($to, $from=false, $granularity=1) {
if (!$to)
return false;
$timestamp = $to;

View File

@@ -25,6 +25,7 @@ class Form {
var $options = array();
var $fields = array();
var $title = '';
var $notice = '';
var $instructions = '';
var $validators = array();
@@ -39,6 +40,8 @@ class Form {
$this->title = $options['title'];
if (isset($options['instructions']))
$this->instructions = $options['instructions'];
if (isset($options['notice']))
$this->notice = $options['notice'];
if (isset($options['id']))
$this->id = $options['id'];
@@ -53,6 +56,10 @@ class Form {
$this->id = $id;
}
function setNotice($notice) {
$this->notice = $notice;
}
function data($source) {
foreach ($this->fields as $name=>$f)
if (isset($source[$name]))
@@ -112,6 +119,7 @@ class Form {
}
function getTitle() { return $this->title; }
function getNotice() { return $this->notice; }
function getInstructions() { return Format::htmldecode($this->instructions); }
function getSource() { return $this->_source; }
function setSource($source) { $this->_source = $source; }
@@ -159,30 +167,54 @@ class Form {
}
return $this->_clean;
}
/*
* Transform form data to database ready clean data.
*
*/
function to_db($clean=null, $validate=true) {
if (!$clean
&& !$this->isValid()
&& !($clean=$this->getClean($validate)))
return false;
$data = [];
foreach ($clean as $name => $val) {
if (!($f = $this->getField($name)))
continue;
try {
$data[$name] = $f->to_database($val);
} catch (FieldUnchanged $e) {
// Unset field if it's unchanged...mainly
// useful for Secret/PasswordField
unset($data[$name]);
}
}
return $data;
}
/*
* Process the form input and return clean data.
*
* It's similar to getClean but forms downstream can use it to return
* database ready data.
* It's similar to to_db but forms downstream can use it to skip or add
* extra validations
*/
function process($validate=true) {
return $this->getClean($validate);
return $this->to_db($validate);
}
function errors($formOnly=false) {
return ($formOnly) ? $this->_errors['form'] : $this->_errors;
}
function addError($message, $index=false) {
if ($index)
$this->_errors[$index] = $message;
else
$this->_errors['form'][] = $message;
}
function addErrors($errors=array()) {
function addErrors($errors=[]) {
foreach ($errors as $k => $v) {
if (($f=$this->getField($k)))
$f->addError($v);
@@ -202,6 +234,9 @@ class Form {
$this->title = $options['title'];
if (isset($options['instructions']))
$this->instructions = $options['instructions'];
if (isset($options['notice']))
$this->notice = $options['notice'];
$form = $this;
$template = $options['template'] ?: 'dynamic-form.tmpl.php';
if (isset($options['staff']) && $options['staff'])
@@ -434,9 +469,14 @@ implements FormRenderer {
?>
<table class="<?php echo 'grid form' ?>">
<caption><?php echo Format::htmlchars($this->title ?: $form->getTitle()); ?>
<div><small><?php echo Format::viewableImages($form->getInstructions()); ?></small></div>
</caption>
<tbody><tr><?php for ($i=0; $i<12; $i++) echo '<td style="width:8.3333%"/>'; ?></tr></tbody>
<div><small><?php echo Format::viewableImages($form->getInstructions()); ?></small></div>
<?php
if ($form->getNotice())
echo sprintf('<div><small><p id="msg_warning">%s</p></small></div>',
Format::htmlchars($form->getNotice()));
?>
</caption>
<tbody><tr><?php for ($i=0; $i<12; $i++) echo '<td style="width:8.3333%"/>'; ?></tr></tbody>
<?php
$row_size = 12;
$cols = $row = 0;
@@ -580,7 +620,7 @@ class FormField {
static $more_types = array();
static $uid = null;
function _uid() {
static function _uid() {
return ++self::$uid;
}
@@ -604,7 +644,7 @@ class FormField {
foreach (static::$more_types as $group => $entries)
foreach ($entries as $c)
static::$types[$group] = array_merge(
static::$types[$group] ?: array(), call_user_func($c));
static::$types[$group] ?? array(), call_user_func($c));
static::$more_types = array();
}
@@ -677,6 +717,12 @@ class FormField {
function errors() {
return $this->_errors;
}
function resetErrors() {
$this->_errors = [];
return !($this->_errors);
}
function addError($message, $index=false) {
if ($index)
$this->_errors[$index] = $message;
@@ -1234,7 +1280,7 @@ class FormField {
* the default value will be reflected in the returned configuration.
*/
function getConfiguration() {
if (!$this->_config) {
if (!isset($this->_config)) {
$this->_config = $this->get('configuration');
if (is_string($this->_config))
$this->_config = JsonDataParser::parse($this->_config);
@@ -1495,7 +1541,7 @@ class TextboxField extends FormField {
function($v) use ($config) {
$regex = $config['regex'];
return @preg_match($regex, $v);
}, __('Value does not match required pattern')
}, $config['validator-error'] ?? __('Value does not match required pattern')
),
);
// Support configuration forms, as well as GUI-based form fields
@@ -1539,6 +1585,15 @@ class PasswordField extends TextboxField {
$this->set('validator', 'password');
}
protected function getMasterKey() {
return SECRET_SALT;
}
protected function getSubKey() {
$config = $this->getConfiguration();
return $config['key'] ?: 'pwfield';
}
function parse($value) {
// Don't trim the value
return $value;
@@ -1548,11 +1603,13 @@ class PasswordField extends TextboxField {
// If not set in UI, don't save the empty value
if (!$value)
throw new FieldUnchanged();
return Crypto::encrypt($value, SECRET_SALT, 'pwfield');
return Crypto::encrypt($value, $this->getMasterKey(),
$this->getSubKey());
}
function to_php($value) {
return Crypto::decrypt($value, SECRET_SALT, 'pwfield');
return Crypto::decrypt($value, $this->getMasterKey(),
$this->getSubKey());
}
}
@@ -1732,6 +1789,10 @@ class BooleanField extends FormField {
return ($value) ? __('Yes') : __('No');
}
function asVar($value, $id=false) {
return $this->toString($value);
}
function getClean($validate=true) {
if (!isset($this->_clean)) {
$this->_clean = (isset($this->value))
@@ -1806,7 +1867,10 @@ class ChoiceField extends FormField {
'id'=>1, 'label'=>__('Choices'), 'required'=>false, 'default'=>'',
'hint'=>__('List choices, one per line. To protect against spelling changes, specify key:value names to preserve entries if the list item names change.</br><b>Note:</b> If you have more than two choices, use a List instead.'),
'validator'=>'choices',
'configuration'=>array('html'=>false)
'configuration'=>array(
'html' => false,
'disabled' => false,
)
)),
'default' => new TextboxField(array(
'id'=>3, 'label'=>__('Default'), 'required'=>false, 'default'=>'',
@@ -2025,7 +2089,7 @@ class ChoiceField extends FormField {
$name = $name ?: $this->get('name');
$val = $value;
if ($value && is_array($value))
$val = '"?'.implode('("|,|$)|"?', array_keys($value)).'("|,|$)';
$val = '"?(?<![0-9])'.implode('("|,|$)|"?(?<![0-9])', array_keys($value)).'("|,|$)';
switch ($method) {
case '!includes':
return Q::not(array("{$name}__regex" => $val));
@@ -2058,18 +2122,19 @@ class ChoiceField extends FormField {
function applyQuickFilter($query, $qf_value, $name=false) {
global $thisstaff;
$field = new AssigneeChoiceField();
//special assignment quick filters
switch (true) {
case ($qf_value == 'assigned'):
case ($qf_value == '!assigned'):
$result = AssigneeChoiceField::getSearchQ($qf_value, $qf_value);
$result = $field->getSearchQ($qf_value, $qf_value);
return $query->filter($result);
case (strpos($qf_value, 's') !== false):
case (strpos($qf_value, 't') !== false):
case ($qf_value == 'M'):
case ($qf_value == 'T'):
$value = array($qf_value => $qf_value);
$result = AssigneeChoiceField::getSearchQ('includes', $value);
$result = $field->getSearchQ('includes', $value);
return $query->filter($result);
break;
}
@@ -2207,7 +2272,7 @@ class DatetimeField extends FormField {
return $this->max;
}
function getPastPresentLabels() {
static function getPastPresentLabels() {
return array(__('Create Date'), __('Reopen Date'),
__('Close Date'), __('Last Update'));
}
@@ -2504,26 +2569,26 @@ class DatetimeField extends FormField {
"{$name}__lte" => $right->format('Y-m-d H:i:s'),
));
case 'ndaysago':
$int = $intervals[$value['int'] ?: 'd'] ?: 'DAY';
$int = $intervals[(string) $value['int'] ?: 'd'] ?: 'DAY';
$interval = new SqlInterval($int, $value['until']);
return new Q(array(
"{$name}__range" => array($now->minus($interval), $now),
));
case 'ndays':
$int = $intervals[$value['int'] ?: 'd'] ?: 'DAY';
$int = $intervals[(string) $value['int'] ?: 'd'] ?: 'DAY';
$interval = new SqlInterval($int, $value['until']);
return new Q(array(
"{$name}__range" => array($now, $now->plus($interval)),
));
// Distant past and future ranges
case 'distpast':
$int = $intervals[$value['int'] ?: 'd'] ?: 'DAY';
$int = $intervals[(string) $value['int'] ?: 'd'] ?: 'DAY';
$interval = new SqlInterval($int, $value['until']);
return new Q(array(
"{$name}__lte" => $now->minus($interval),
));
case 'distfut':
$int = $intervals[$value['int'] ?: 'd'] ?: 'DAY';
$int = $intervals[(string) $value['int'] ?: 'd'] ?: 'DAY';
$interval = new SqlInterval($int, $value['until']);
return new Q(array(
"{$name}__gte" => $now->plus($interval),
@@ -2700,6 +2765,14 @@ class SectionBreakField extends FormField {
function isBlockLevel() {
return true;
}
function isEditableToStaff() {
return $this->isVisibleToStaff();
}
function isEditableToUsers() {
return $this->isVisibleToUsers();
}
}
class ThreadEntryField extends FormField {
@@ -2853,7 +2926,7 @@ class TopicField extends ChoiceField {
}
function whatChanged($before, $after) {
return FormField::whatChanged($before, $after);
return parent::whatChanged($before, $after);
}
function searchable($value) {
@@ -2961,7 +3034,7 @@ class SLAField extends ChoiceField {
}
function whatChanged($before, $after) {
return FormField::whatChanged($before, $after);
return parent::whatChanged($before, $after);
}
function searchable($value) {
@@ -3077,7 +3150,7 @@ class PriorityField extends ChoiceField {
}
function whatChanged($before, $after) {
return FormField::whatChanged($before, $after);
return parent::whatChanged($before, $after);
}
function searchable($value) {
@@ -3158,7 +3231,7 @@ class TimezoneField extends ChoiceField {
}
function whatChanged($before, $after) {
return FormField::whatChanged($before, $after);
return parent::whatChanged($before, $after);
}
function searchable($value) {
@@ -3199,7 +3272,7 @@ class DepartmentField extends ChoiceField {
$config = $this->getConfiguration();
$staff = $config['staff'] ?: $thisstaff;
$selected = self::getWidget();
$selected = $this->getWidget();
if($selected && $selected->value) {
if(is_array($selected->value)) {
foreach ($selected->value as $k => $v) {
@@ -3263,7 +3336,7 @@ class DepartmentField extends ChoiceField {
function getValue() {
if (($value = parent::getValue()) && ($id=$this->getClean()))
return $value[$id];
return is_array($value) ? $value[$id] : $value;
}
function to_php($value, $id=false) {
@@ -3526,10 +3599,10 @@ class AssigneeField extends ChoiceField {
$name = $value->getName();
}
return array(JsonDataEncoder::encode(array($key => $name)));
return JsonDataEncoder::encode(array($key => $name));
}
if (is_array($value)) {
return array(JsonDataEncoder::encode($value[0]));
return JsonDataEncoder::encode($value[0]);
}
return $value;
}
@@ -3562,7 +3635,7 @@ class AssigneeField extends ChoiceField {
// Compare old and new
return ($old == $new)
? false
: array($old[0], $new[0]);
: array($old, $new);
}
function whatChanged($before, $after) {
@@ -3807,6 +3880,10 @@ class FileUploadField extends FormField {
'hint'=>__('Optionally, enter comma-separated list of additional file types, by extension. (e.g .doc, .pdf).'),
'configuration'=>array('html'=>false, 'rows'=>2),
)),
'strictmimecheck' => new BooleanField([
'id' => 4, 'label'=>__('Strict Mime Type Check'), 'required' => false, 'default' => false,
'hint' => 'File Mime Type associations is OS dependent',
'configuration' => ['desc' => __('Enable strict Mime Type check')]]),
'max' => new TextboxField(array(
'label'=>__('Maximum Files'),
'hint'=>__('Users cannot upload more than this many files.'),
@@ -3831,8 +3908,9 @@ class FileUploadField extends FormField {
Http::response(400, 'Send one file at a time');
$file = array_shift($files);
$file['name'] = urldecode($file['name']);
$config = $this->getConfiguration();
if (!$this->isValidFile($file))
if (!self::isValidFile($file, $config['strictmimecheck']))
Http::response(413, 'Invalid File');
if (!$bypass && !$this->isValidFileType($file['name'], $file['type']))
@@ -3861,10 +3939,11 @@ class FileUploadField extends FormField {
if (!$this->isValidFileType($file['name'], $file['type']))
throw new FileUploadError(__('File type is not allowed'));
if (!$this->isValidFile($file))
$config = $this->getConfiguration();
if (!self::isValidFile($file, $config['strictmimecheck']))
throw new FileUploadError(__('Invalid File'));
$config = $this->getConfiguration();
if ($file['size'] > $config['size'])
throw new FileUploadError(__('File size is too large'));
@@ -3901,13 +3980,25 @@ class FileUploadField extends FormField {
return $F;
}
function isValidFile($file) {
/**
* Strict mode can be enabled in Admin Panel > Settings > Tickets
*
* PS: Please note that the a mismatch can happen if the mime types
* database is not up to date or a little different compared to what the
* browser reports.
**/
static function isValidFile($file, $strict = false) {
// Strict mime check
if ($strict
&& !empty($file['type'])
&& FileObject::mimecmp($file['tmp_name'], $file['type']))
return false;
// Check invalid image hacks
if ($file['tmp_name']
&& stripos($file['type'], 'image/') === 0
&& function_exists('exif_imagetype')
&& !exif_imagetype($file['tmp_name']))
&& !(exif_imagetype($file['tmp_name'])
|| mime_content_type($file['tmp_name']) === 'image/svg+xml'))
return false;
return true;
@@ -3964,7 +4055,11 @@ class FileUploadField extends FormField {
}
function getConfiguration() {
global $cfg;
$config = parent::getConfiguration();
// If no size present default to system setting
$config['size'] ??= $cfg->getMaxFileSize();
$_types = self::getFileTypes();
$mimetypes = array();
$extensions = array();
@@ -4024,7 +4119,7 @@ class FileUploadField extends FormField {
if (isset($this->attachments) && $this->attachments) {
$this->attachments->keepOnlyFileIds($value);
}
return JsonDataEncoder::encode($value);
return JsonDataEncoder::encode($value) ?? NULL;
}
function parse($value) {
@@ -4348,6 +4443,8 @@ class TextboxWidget extends Widget {
if ($v)
$attrs['data-translate-tag'] = '"'.$v.'"';
break;
case 'length':
$k = 'maxlength';
case 'size':
case 'maxlength':
if ($v && is_numeric($v))
@@ -4415,8 +4512,9 @@ class PasswordWidget extends TextboxWidget {
function render($mode=false, $extra=false) {
$extra = array();
if ($this->field->value) {
$extra['placeholder'] = '••••••••••••';
if (isset($this->field->value)) {
$extra['placeholder'] = str_repeat('•',
strlen($this->field->value));
}
return parent::render($mode, $extra);
}
@@ -4586,6 +4684,8 @@ class ChoicesWidget extends Widget {
echo ' data-'.$D.'="'.Format::htmlchars($V).'"';
?>
data-placeholder="<?php echo Format::htmlchars($prompt); ?>"
<?php if ($config['disabled'])
echo ' disabled="disabled"'; ?>
<?php if ($config['multiselect'])
echo ' multiple="multiple"'; ?>>
<?php if ($showdefault || (!$have_def && !$config['multiselect'])) { ?>
@@ -5148,6 +5248,8 @@ class FileUploadWidget extends Widget {
);
function render($options=array()) {
global $ost;
$config = $this->field->getConfiguration();
$name = $this->field->getFormName();
$id = substr(md5(spl_object_hash($this)), 10);
@@ -5211,7 +5313,7 @@ class FileUploadWidget extends Widget {
// Get Form Type
$type = $this->field->getForm()->type;
// Determine if for Ticket/Task/Custom
if ($type) {
if ($type && !is_numeric($field_id)) {
if ($type == 'T')
$field_id = 'ticket/attach';
elseif ($type == 'A')
@@ -5233,6 +5335,7 @@ class FileUploadWidget extends Widget {
url: 'ajax.php/form/upload/<?php echo $field_id; ?>',
link: $('#<?php echo $id; ?>').find('a.manual'),
paramname: 'upload[]',
data: {"__CSRFToken__": "<?php echo $ost->getCSRF()->getToken(); ?>"},
fallback_id: 'file-<?php echo $id; ?>',
allowedfileextensions: <?php echo JsonDataEncoder::encode(
$config['__extensions'] ?: array()); ?>,
@@ -5335,6 +5438,14 @@ class FreeTextField extends FormField {
return true;
}
function isEditableToStaff() {
return $this->isVisibleToStaff();
}
function isEditableToUsers() {
return $this->isVisibleToUsers();
}
/* utils */
function to_config($config) {
@@ -5619,7 +5730,7 @@ class AssignmentForm extends Form {
'id'=>2, 'label'=>'', 'required'=>false,
'default'=>false,
'configuration'=>array(
'desc' => 'Maintain referral access to current assignees')
'desc' => __('Maintain referral access to current assignees'))
)
),
'comments' => new TextareaField(array(
@@ -6033,7 +6144,7 @@ class TransferForm extends Form {
'refer' => new BooleanField(array(
'id'=>2, 'label'=>'', 'required'=>false, 'default'=>false,
'configuration'=>array(
'desc' => 'Maintain referral access to current department')
'desc' => __('Maintain referral access to current department'))
)),
'comments' => new TextareaField(array(
'id' => 3,
@@ -6075,7 +6186,7 @@ class TransferForm extends Form {
break;
default:
throw new Exception(sprintf(__('%s: Unknown template style %s'),
get_class(), $options['template']));
get_class($this), $options['template']));
}
$form = $this;

View File

@@ -15,7 +15,7 @@
**********************************************************************/
class Http {
function header_code_verbose($code) {
static function header_code_verbose($code) {
switch($code):
case 200: return '200 OK';
case 201: return '201 Created';
@@ -33,7 +33,7 @@ class Http {
endswitch;
}
function response($code,$content=false,$contentType='text/html',$charset='UTF-8') {
static function response($code,$content=false,$contentType='text/html',$charset='UTF-8') {
header('HTTP/1.1 '.Http::header_code_verbose($code));
header('Status: '.Http::header_code_verbose($code)."\r\n");
@@ -53,7 +53,7 @@ class Http {
* Flush the content to requester without exiting
*
*/
function flush($code, $content, $contentType='text/html', $charset='UTF-8') {
static function flush($code, $content, $contentType='text/html', $charset='UTF-8') {
self::response($code, null, $contentType, $charset);
header('Cache-Control: no-cache, must-revalidate');
header('Content-Length: '.strlen($content)."\r\n\r\n");
@@ -66,7 +66,7 @@ class Http {
fastcgi_finish_request();
}
function redirect($url,$delay=0,$msg='') {
static function redirect($url,$delay=0,$msg='') {
$iis = strpos($_SERVER['SERVER_SOFTWARE'], 'IIS') !== false;
@list($name, $version) = explode('/', $_SERVER['SERVER_SOFTWARE']);
@@ -84,7 +84,7 @@ class Http {
exit;
}
function cacheable($etag, $modified=false, $ttl=3600) {
static function cacheable($etag, $modified=false, $ttl=3600) {
// Thanks, http://stackoverflow.com/a/1583753/1025836
// Timezone doesn't matter here — but the time needs to be
// consistent round trip to the browser and back.
@@ -108,21 +108,17 @@ class Http {
* is browser dependent, so the user agent is inspected to determine the
* best encoding format for the filename
*/
function getDispositionFilename($filename) {
static function getDispositionFilename($filename) {
$user_agent = strtolower ($_SERVER['HTTP_USER_AGENT']);
if (false !== strpos($user_agent,'msie')
&& false !== strpos($user_agent,'win'))
return 'filename='.rawurlencode($filename);
elseif (false !== strpos($user_agent, 'safari')
&& false === strpos($user_agent, 'chrome'))
// Safari and Safari only can handle the filename as is
return 'filename='.str_replace(',', '', $filename);
else
// Use RFC5987
return "filename*=UTF-8''".rawurlencode($filename);
}
function download($filename, $type, $data=null, $disposition='attachment') {
static function download($filename, $type, $data=null, $disposition='attachment') {
if (strpos($type, 'image/') !== false && preg_match('/image\/.*\+.*/', $type))
$disposition='attachment';
@@ -151,5 +147,59 @@ class Http {
return http_build_query($vars, '', $separator);
}
static function domain() {
$domain = null;
if (isset($_SERVER['HTTP_HOST'])
&& strpos($_SERVER['HTTP_HOST'], '.') !== false
&& !Validator::is_ip($_SERVER['HTTP_HOST']))
// Remote port specification, as it will make an invalid domain
list($domain) = explode(':', $_SERVER['HTTP_HOST']);
return $domain;
}
// Get current url
static function url() {
// Scheme + Host.
$https = osTicket::is_https();
$url = sprintf('http%s://%s',
$https ? 's' : '',
$_SERVER['HTTP_HOST']);
// Add Port if not 80 and not https
$port = osTicket::get_client_port();
if ($port && $port != 80 && !$https && !str_contains($url, $port))
$url .= ":{$port}";
// Path + Query string
if (isset($_SERVER['REQUEST_URI']))
$url .= $_SERVER['REQUEST_URI'];
else
$url .= sprintf('%s?$s',
$_SERVER['PHP_SELF'], $_SERVER['QUERY_STRING']);
return $url;
}
// Parse path from given url or the current url
static function url_path(?string $url = null, bool $htmlentities = true) {
$path = parse_url($url ?: self::url(), PHP_URL_PATH);
return ($htmlentities && $path)
? htmlentities($path) : $path;
}
// Parse query string from current url
static function query_string(?string $url = null) {
return parse_url($url ?: self::url(), PHP_URL_QUERY);
}
// Refresh url
static function refresh_url(array $queryFilter = []) {
$url = self::url();
$refresh_url = self::url_path($url);
if (($qs=self::query_string($url)))
$refresh_url .= sprintf('?%s',
Format::http_query_string($qs, $queryFilter));
return $refresh_url;
}
}
?>

View File

@@ -49,14 +49,14 @@ class Internationalization {
function loadDefaultData() {
# notrans -- do not translate the contents of this array
$models = array(
'department.yaml' => 'Dept::__create',
'sla.yaml' => 'SLA::__create',
'department.yaml' => 'Dept::__create',
'form.yaml' => 'DynamicForm::create',
'list.yaml' => 'DynamicList::create',
// Note that department, sla, and forms are required for
// help_topic
'help_topic.yaml' => 'Topic::__create',
'filter.yaml' => 'Filter::create',
'filter.yaml' => 'Filter::__create',
'team.yaml' => 'Team::__create',
// Organization
'organization.yaml' => 'Organization::__create',
@@ -182,7 +182,7 @@ class Internationalization {
global $thisstaff, $thisclient;
$langs = self::availableLanguages();
$lang = strtolower($lang);
$lang = str_replace('-', '_', strtolower($lang));
if (isset($langs[$lang])) {
$info = &$langs[$lang];
if (!isset($info['desc'])) {
@@ -213,7 +213,7 @@ class Internationalization {
static function getLanguageInfo($lang) {
$langs = self::availableLanguages();
return @$langs[strtolower($lang)] ?: array();
return @$langs[str_replace('-', '_', strtolower($lang))] ?: array();
}
static function availableLanguages($base=I18N_DIR) {
@@ -240,7 +240,7 @@ class Internationalization {
'code' => $base,
);
$installed[strtolower($base)]['flag'] = strtolower(
$langs[$code]['flag'] ?: $locale ?: $code
($langs[$code]['flag'] ?? $locale) ?: $code
);
}
}
@@ -251,7 +251,7 @@ class Internationalization {
static function isLanguageInstalled($code) {
$langs = self::availableLanguages();
return isset($langs[strtolower($code)]);
return isset($langs[strtolower((string) $code)]);
}
static function isLanguageEnabled($code) {
@@ -382,7 +382,7 @@ class Internationalization {
return $lang;
// Support the flag buttons for guests
if ((!$user || $user != $thisstaff) && $_SESSION['::lang'])
if ((!$user || $user != $thisstaff) && isset($_SESSION['::lang']))
return $_SESSION['::lang'];
return self::getDefaultLanguage();
@@ -465,11 +465,11 @@ class Internationalization {
// Create formatter && cache
$cache[$k] = $formatter = new IntlDateFormatter(
$options['locale'],
$options['daytype'] ?: null,
$options['timetype'] ?: null,
$options['timezone'] ?: null,
$options['calendar'] ?: IntlDateFormatter::GREGORIAN,
$options['pattern'] ?: null
$options['daytype'] ?? null,
$options['timetype'] ?? null,
$options['timezone'] ?? null,
$options['calendar'] ?? IntlDateFormatter::GREGORIAN,
$options['pattern'] ?? null
);
return $formatter;

View File

@@ -31,8 +31,6 @@ class CsvImporter {
function __construct($stream) {
// File upload
if (is_array($stream) && !$stream['error']) {
// Properly detect Macintosh style line endings
ini_set('auto_detect_line_endings', true);
$this->stream = fopen($stream['tmp_name'], 'r');
// Skip Byte Order Mark (BOM) if present
if (!self::isBOM(fgets($this->stream, 4)))
@@ -65,7 +63,7 @@ class CsvImporter {
$named_fields[$f->get('name')] = $f;
// Read the first row and see if it is a header or not
if (!($data = fgetcsv($this->stream, 1000, ",")))
if (!($data = fgetcsv($this->stream, 1000, ",", "\"", "")))
throw new ImportError(__('Whoops. Perhaps you meant to send some CSV records'));
$headers = array();
@@ -172,7 +170,7 @@ implements Iterator {
function next() {
do {
if (($csv = fgetcsv($this->stream, 4096, ",")) === false) {
if (($csv = fgetcsv($this->stream, 4096, ",", "\"", "")) === false) {
// Read error
$this->current = false;
break;
@@ -191,7 +189,7 @@ implements Iterator {
foreach ($this->headers as $h => $label) {
$f = $this->fields[$h];
$f->_errors = array();
$T = $f->parse($csv[$i]);
$T = $f->parse(trim($csv[$i]));
if ($f->validateEntry($T) && $f->errors())
throw new ImportDataError(sprintf(__(
/* 1 will be a field label, and 2 will be error messages */

View File

@@ -21,7 +21,7 @@
include_once "JSON.php";
class JsonDataParser {
function parse($stream, $tidy=false) {
static function parse($stream, $tidy=false) {
if (is_resource($stream)) {
$contents = '';
while (!feof($stream))
@@ -54,7 +54,7 @@ class JsonDataParser {
return trim($content);
}
function lastError() {
static function lastError() {
if (function_exists("json_last_error")) {
$errors = array(
JSON_ERROR_NONE => __('No errors'),
@@ -75,7 +75,7 @@ class JsonDataParser {
}
class JsonDataEncoder {
function encode($var) {
static function encode($var) {
if (function_exists('json_encode'))
return json_encode($var);
else {

View File

@@ -181,7 +181,7 @@ class DynamicList extends VerySimpleModel implements CustomList {
return ($this->getForm() && $this->getForm()->getFields());
}
function getSortModes() {
static function sortModes() {
return array(
'Alpha' => __('Alphabetical'),
'-Alpha' => __('Alphabetical (Reversed)'),
@@ -189,6 +189,10 @@ class DynamicList extends VerySimpleModel implements CustomList {
);
}
function getSortModes() {
return self::sortModes();
}
function getSortMode() {
return $this->sort_mode;
}
@@ -592,8 +596,6 @@ class DynamicList extends VerySimpleModel implements CustomList {
function importFromPost($stuff, $extra=array()) {
if (is_array($stuff) && !$stuff['error']) {
// Properly detect Macintosh style line endings
ini_set('auto_detect_line_endings', true);
$stream = fopen($stuff['tmp_name'], 'r');
}
elseif ($stuff) {
@@ -722,7 +724,7 @@ class DynamicListItem extends VerySimpleModel implements CustomListItem {
function setConfiguration($vars, &$errors=array()) {
$config = array();
foreach ($this->getConfigurationForm($vars)->getFields() as $field) {
$config[$field->get('id')] = $field->to_php($field->getClean());
$config[$field->get('id')] = $field->to_database($field->getClean());
$errors = array_merge($errors, $field->errors());
}
@@ -797,13 +799,14 @@ class DynamicListItem extends VerySimpleModel implements CustomListItem {
}
function display() {
return $this->getValue();
//TODO: Allow for display mode (edit, preview or both)
return sprintf('%s &nbsp;<a class="preview" href="#"
data-preview="#list/%d/items/%d/preview">
<i class="icon-info-sign"></i></a>',
$this->getValue(),
return sprintf('<a class="preview" href="#"
data-preview="#list/%d/items/%d/preview">%s</a>',
$this->getListId(),
$this->getId()
$this->getId(),
$this->getValue()
);
}
@@ -926,9 +929,9 @@ class TicketStatusList extends CustomListHandler {
$items = TicketStatus::objects();
if ($filters)
$items->filter($filters);
if ($criteria['limit'])
if (isset($criteria['limit']))
$items->limit($criteria['limit']);
if ($criteria['offset'])
if (isset($criteria['offset']))
$items->offset($criteria['offset']);
$items->order_by($this->getListOrderBy());

View File

@@ -69,7 +69,7 @@ class Log {
}
/*** static function ***/
function lookup($id){
static function lookup($id){
return ($id && is_numeric($id) && ($l= new Log($id)) && $l->getId()==$id)?$l:null;
}
}

1098
include/class.mail.php Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,12 +2,12 @@
/*********************************************************************
class.mailer.php
osTicket mailer
osTicket/Mail/Mailer
It's mainly PEAR MAIL wrapper for now (more improvements planned).
Wrapper for sending emails via SMTP / SendMail
Peter Rotich <peter@osticket.com>
Copyright (c) 2006-2013 osTicket
Copyright (c) osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
@@ -15,67 +15,93 @@
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
include_once(INCLUDE_DIR.'class.email.php');
require_once(INCLUDE_DIR.'html2text.php');
namespace osTicket\Mail;
class Mailer {
var $email;
private $from = null;
var $email = null;
var $smtpAccounts = [];
var $ht = array();
var $attachments = array();
var $options = array();
var $smtp = array();
var $eol="\n";
function __construct($email=null, array $options=array()) {
function __construct(?\Email $email=null, array $options=array()) {
global $cfg;
if(is_object($email) && $email->isSMTPEnabled() && ($info=$email->getSMTPInfo())) { //is SMTP enabled for the current email?
$this->smtp = $info;
} elseif($cfg && ($e=$cfg->getDefaultSMTPEmail()) && $e->isSMTPEnabled()) { //What about global SMTP setting?
$this->smtp = $e->getSMTPInfo();
if(!$e->allowSpoofing() || !$email)
$email = $e;
} elseif(!$email && $cfg && ($e=$cfg->getDefaultEmail())) {
if($e->isSMTPEnabled() && ($info=$e->getSMTPInfo()))
$this->smtp = $info;
$email = $e;
// Get all possible outgoing emails accounts (SMTP) to try
if (($email instanceof \Email)
&& ($smtp=$email->getSmtpAccount(false))
&& $smtp->isActive()) {
$this->smtpAccounts[$smtp->getId()] = $smtp;
}
if ($cfg) {
// Get Default MTA (SMTP)
if (($smtp=$cfg->getDefaultMTA()) && $smtp->isActive()) {
$this->smtpAccounts[$smtp->getId()] = $smtp;
// If email is not set then use Default MTA
if (!$email)
$email = $smtp->getEmail();
} elseif (!$email && $cfg && ($email=$cfg->getDefaultEmail())) {
// as last resort we will send via Default Email
if (($smtp=$email->getSmtpAccount(false)) && $smtp->isActive())
$this->smtpAccounts[$smtp->getId()] = $smtp;
}
}
$this->email = $email;
$this->attachments = array();
$this->options = $options;
if (isset($this->options['eol']))
$this->eol = $this->options['eol'];
elseif (defined('MAIL_EOL') && is_string(MAIL_EOL))
$this->eol = MAIL_EOL;
}
function getEOL() {
return $this->eol;
}
function getSmtpAccounts() {
return $this->smtpAccounts;
}
function getEmail() {
return $this->email;
}
function getSMTPInfo() {
return $this->smtp;
}
/* FROM Address */
function setFromAddress($from) {
$this->ht['from'] = $from;
function setFromAddress($from=null, $name=null) {
if ($from instanceof \EmailAddress)
$this->from = $from;
elseif (\Validator::is_email($from)) {
$this->from = new \EmailAddress(
str_contains($from, '<') ? $from : sprintf('"%s" <%s>', $name ?: '', $from));
} elseif (is_string($from))
$this->from = new \EmailAddress($from);
elseif (($email=$this->getEmail())) {
// we're assuming from was null or unexpected monster
$address = sprintf('"%s" <%s>',
$name ?: $email->getName(),
$email->getEmail());
$this->from = new \EmailAddress($address);
}
}
function getFromAddress($options=array()) {
if (!isset($this->from))
$this->setFromAddress(null, $options['from_name'] ?: null);
if (!$this->ht['from'] && ($email=$this->getEmail())) {
if (($name = $options['from_name'] ?: $email->getName()))
$this->ht['from'] =sprintf('"%s" <%s>', $name, $email->getEmail());
else
$this->ht['from'] =sprintf('<%s>', $email->getEmail());
}
return $this->from;
}
return $this->ht['from'];
function getFromName() {
return $this->getFromAddress()->getName();
}
function getFromEmail() {
return $this->getFromAddress()->getEmail();
}
/* attachments */
@@ -83,33 +109,42 @@ class Mailer {
return $this->attachments;
}
function addAttachment(Attachment $attachment) {
// XXX: This looks too assuming; however, the attachment processor
// in the ::send() method seems hard coded to expect this format
$this->attachments[] = $attachment;
function addAttachment(\Attachment $attachment) {
$this->attachments[$attachment->getFile()->getUId()] = $attachment;
}
function addAttachmentFile(AttachmentFile $file) {
// XXX: This looks too assuming; however, the attachment processor
// in the ::send() method seems hard coded to expect this format
$this->attachments[] = $file;
function addAttachmentFile(\AttachmentFile $file) {
$this->attachments[$file->getUId()] = $file;
}
function addFileObject(FileObject $file) {
$this->attachments[] = $file;
function addFileObject(\FileObject $file) {
$this->attachments[$file->getUId()] = $file;
}
function addAttachments($attachments) {
foreach ($attachments as $a) {
if ($a instanceof Attachment)
if ($a instanceof \Attachment)
$this->addAttachment($a);
elseif ($a instanceof AttachmentFile)
elseif ($a instanceof \AttachmentFile)
$this->addAttachmentFile($a);
elseif ($a instanceof FileObject)
elseif ($a instanceof \FileObject)
$this->addFileObject($a);
}
}
/**
* lookup Attached File by Key
*/
function getFile(String $key) {
foreach ($this->getAttachments() as $uid => $F) {
if ($F instanceof \Attachment)
$F = $F->getFile();
if (strcasecmp($F->getKey(), $key) === 0)
return $F;
}
return \AttachmentFile::lookup($key);
}
/**
* getMessageId
*
@@ -147,7 +182,7 @@ class Mailer {
*/
function getMessageId($recipient, $options=array(), $version='B') {
$tag = '';
$rand = Misc::randCode(5,
$rand = \Misc::randCode(5,
// RFC822 specifies the LHS of the addr-spec can have any char
// except the specials — ()<>@,;:\".[], dash is reserved as the
// section separator, and + is reserved for historical reasons
@@ -155,32 +190,34 @@ class Mailer {
$sig = $this->getEmail()?$this->getEmail()->getEmail():'@osTicketMailer';
$sysid = static::getSystemMessageIdCode();
// Create a tag for the outbound email
$entry = (isset($options['thread']) && $options['thread'] instanceof ThreadEntry)
$entry = (isset($options['thread'])
&& ($options['thread'] instanceof \ThreadEntry))
? $options['thread'] : false;
$thread = $entry ? $entry->getThread()
: (isset($options['thread']) && $options['thread'] instanceof Thread
: (isset($options['thread'])
&& ($options['thread'] instanceof \Thread)
? $options['thread'] : false);
switch (true) {
case $recipient instanceof Staff:
case $recipient instanceof \Staff:
$utype = 'S';
break;
case $recipient instanceof TicketOwner:
case $recipient instanceof \TicketOwner:
$utype = 'U';
break;
case $recipient instanceof Collaborator:
case $recipient instanceof \Collaborator:
$utype = 'C';
break;
case $recipient instanceof MailingList:
case $recipient instanceof \MailingList:
$utype = 'M';
break;
default:
$utype = $options['utype'] ?: is_array($recipient) ? 'M' : '?';
$utype = ($options['utype'] ?: is_array($recipient)) ? 'M' : '?';
}
$tag = pack('VVVa',
$recipient instanceof EmailContact ? $recipient->getUserId() : 0,
$recipient instanceof \EmailContact ? $recipient->getUserId() : 0,
$entry ? $entry->getId() : 0,
$thread ? $thread->getId() : 0,
$utype ?: '?'
@@ -304,31 +341,32 @@ class Mailer {
0, 6);
}
function send($recipients, $subject, $message, $options=null) {
function send($recipients, $subject, $body, $options=null) {
global $ost, $cfg;
//Get the goodies
require_once (PEAR_DIR.'Mail.php'); // PEAR Mail package
require_once (PEAR_DIR.'Mail/mime.php'); // PEAR Mail_Mime packge
$messageId = $this->getMessageId($recipients, $options);
$subject = preg_replace("/(\r\n|\r|\n)/s",'', trim($subject));
$headers = array (
'From' => $this->getFromAddress($options),
'Subject' => $subject,
'Date'=> date('D, d M Y H:i:s O'),
'Message-ID' => "<{$messageId}>",
'X-Mailer' =>'osTicket Mailer',
);
$from = $this->getFromAddress($options);
// Create new ostTicket/Mail/Message object
$message = new Message();
// Set our custom Message-Id
$message->setMessageId($messageId);
// Set From Address
$message->setFrom($from->getEmail(), $from->getName());
// Set Subject
$message->setSubject($subject);
// Collect Generic Headers
$headers = ['X-Mailer' =>'osTicket Mailer'];
// Add in the options passed to the constructor
$options = ($options ?: array()) + $this->options;
// Message Id Token
$mid_token = '';
// Check if the email is threadable
if (isset($options['thread'])
&& $options['thread'] instanceof ThreadEntry
&& ($options['thread'] instanceof \ThreadEntry)
&& ($thread = $options['thread']->getThread())) {
// Add email in-reply-to references if not set
@@ -336,15 +374,15 @@ class Mailer {
$entry = null;
switch (true) {
case $recipients instanceof MailingList:
case $recipients instanceof \MailingList:
$entry = $thread->getLastEmailMessage();
break;
case $recipients instanceof TicketOwner:
case $recipients instanceof Collaborator:
case $recipients instanceof \TicketOwner:
case $recipients instanceof \Collaborator:
$entry = $thread->getLastEmailMessage(array(
'user_id' => $recipients->getUserId()));
break;
case $recipients instanceof Staff:
case $recipients instanceof \Staff:
//XXX: is it necessary ??
break;
}
@@ -370,9 +408,9 @@ class Mailer {
// Return-Path
if (isset($options['nobounce']) && $options['nobounce'])
$headers['Return-Path'] = '<>';
elseif ($this->getEmail() instanceof Email)
$headers['Return-Path'] = $this->getEmail()->getEmail();
$message->setReturnPath('<>');
elseif ($this->getEmail() instanceof \Email)
$message->setReturnPath($this->getEmail()->getEmail());
// Bulk.
if (isset($options['bulk']) && $options['bulk'])
@@ -394,74 +432,72 @@ class Mailer {
'Auto-Submitted' => 'auto-generated');
// In-Reply-To
if (isset($options['inreplyto']) && $options['inreplyto'])
$headers += array('In-Reply-To' => $options['inreplyto']);
$message->addInReplyTo($options['inreplyto']);
// References
if (isset($options['references']) && $options['references']) {
if (is_array($options['references']))
$headers += array('References' =>
implode(' ', $options['references']));
else
$headers += array('References' => $options['references']);
}
if (isset($options['references']) && $options['references'])
$message->addReferences($options['references']);
// Add Headers
$message->addHeaders($headers);
// Use general failsafe default initially
$eol = "\n";
// MAIL_EOL setting can be defined in `ost-config.php`
if (defined('MAIL_EOL') && is_string(MAIL_EOL))
$eol = MAIL_EOL;
$mime = new Mail_mime($eol);
// Add recipients
if (!is_array($recipients) && (!$recipients instanceof MailingList))
if (!is_array($recipients) && (!$recipients instanceof \MailingList))
$recipients = array($recipients);
foreach ($recipients as $recipient) {
if ($recipient instanceof ClientSession)
if ($recipient instanceof \ClientSession)
$recipient = $recipient->getSessionUser();
switch (true) {
case $recipient instanceof EmailRecipient:
$addr = sprintf('"%s" <%s>',
$recipient->getName(),
$recipient->getEmail());
switch ($recipient->getType()) {
case 'to':
$mime->addTo($addr);
break;
case 'cc':
$mime->addCc($addr);
break;
case 'bcc':
$mime->addBcc($addr);
break;
}
break;
case $recipient instanceof TicketOwner:
case $recipient instanceof Staff:
$mime->addTo(sprintf('"%s" <%s>',
$recipient->getName(),
$recipient->getEmail()));
break;
case $recipient instanceof Collaborator:
$mime->addCc(sprintf('"%s" <%s>',
$recipient->getName(),
$recipient->getEmail()));
break;
case $recipient instanceof EmailAddress:
$mime->addTo($recipient->getAddress());
break;
default:
// Assuming email address.
$mime->addTo($recipient);
try {
switch (true) {
case $recipient instanceof \EmailRecipient:
$email = (string) $recipient->getEmail()->getEmail();
$name = (string) $recipient->getName();
switch ($recipient->getType()) {
case 'to':
$message->addTo($email, $name);
break;
case 'cc':
$message->addCc($email, $name);
break;
case 'bcc':
$message->addBcc($email, $name);
break;
}
break;
case $recipient instanceof \TicketOwner:
case $recipient instanceof \Staff:
$message->addTo((string) $recipient->getEmail(),
(string) $recipient->getName());
break;
case $recipient instanceof \Collaborator:
$message->addCc((string) $recipient->getEmail(),
(string) $recipient->getName());
break;
case $recipient instanceof \EmailAddress:
$message->addTo((string) $recipient->getEmail(),
(string) $recipient->getName());
break;
default:
// Assuming email address.
if (is_string($recipient))
$message->addTo($recipient);
}
} catch(\Exception $ex) {
$this->logWarning(sprintf("%s1\$s: %2\$s\n\n%3\$s\n",
_S("Unable to add email recipient"),
($recipient instanceof EmailContact)
? $recipient->getEmailAddress()
: (string) $recipient,
$ex->getMessage()
));
}
}
// Add in extra attachments, if any from template variables
if ($message instanceof TextWithExtras
&& ($attachments = $message->getAttachments())
) {
if ($body instanceof \TextWithExtras
&& ($attachments = $body->getAttachments())) {
foreach ($attachments as $a) {
$file = $a->getFile();
$mime->addAttachment($file->getData(),
$file->getMimeType(), $file->getName(), false);
$message->addAttachment($a->getFile());
}
}
@@ -473,26 +509,26 @@ class Mailer {
// Embed the data-mid in such a way that it should be included
// in a response
if ($options['reply-tag'] || $mid_token) {
$message = sprintf('<div style="display:none"
$body = sprintf('<div style="display:none"
class="mid-%s">%s</div>%s',
$mid_token,
$options['reply-tag'],
$message);
$body);
}
$txtbody = rtrim(Format::html2text($message, 90, false))
$txtbody = rtrim(\Format::html2text($body, 90, false))
. ($messageId ? "\nRef-Mid: $messageId\n" : '');
$mime->setTXTBody($txtbody);
$message->setTextBody($txtbody);
}
else {
$mime->setTXTBody($message);
$message->setTextBody($body);
$isHtml = false;
}
if ($isHtml && $cfg && $cfg->isRichTextEnabled()) {
// Pick a domain compatible with pear Mail_Mime
$matches = array();
if (preg_match('#(@[0-9a-zA-Z\-\.]+)#', $this->getFromAddress(), $matches)) {
if (preg_match('#(@[0-9a-zA-Z\-\.]+)#', (string) $this->getFromAddress(), $matches)) {
$domain = $matches[1];
} else {
$domain = '@localhost';
@@ -500,136 +536,140 @@ class Mailer {
// Format content-ids with the domain, and add the inline images
// to the email attachment list
$self = $this;
$message = preg_replace_callback('/cid:([\w.-]{32})/',
function($match) use ($domain, $mime, $self) {
$file = false;
foreach ($self->attachments as $id=>$F) {
if ($F instanceof Attachment)
$F = $F->getFile();
if (strcasecmp($F->getKey(), $match[1]) === 0) {
$file = $F;
break;
}
}
if (!$file)
// Not attached yet attempt to attach it inline
$file = AttachmentFile::lookup($match[1]);
if (!$file)
$body = preg_replace_callback('/cid:([\w.-]{32})/',
function($match) use ($domain, $message, $self) {
if (!($file=$self->getFile($match[1])))
return $match[0];
$mime->addHTMLImage($file->getData(),
$file->getMimeType(), $file->getName(), false,
$match[1].$domain);
// Don't re-attach the image below
unset($self->attachments[$file->getId()]);
return $match[0].$domain;
}, $message);
try {
$message->addInlineImage($match[1].$domain, $file);
// Don't re-attach the image below
unset($self->attachments[$file->getUId()]);
return $match[0].$domain;
} catch(\Exception $ex) {
$self->logWarning(sprintf("%1\$s:%2\$s\n\n%3\$s\n",
_S("Unable to retrieve email inline image"),
$match[1].$domain,
$ex->getMessage()));
}
}, $body);
// Add an HTML body
$mime->setHTMLBody($message);
$message->setHtmlBody($body);
}
//XXX: Attachments
if(($attachments=$this->getAttachments())) {
foreach($attachments as $file) {
// Read the filename from the Attachment if possible
if ($file instanceof Attachment) {
if ($file instanceof \Attachment) {
$filename = $file->getFilename();
$file = $file->getFile();
} elseif ($file instanceof AttachmentFile) {
} else {
$filename = $file->getName();
} elseif ($file instanceof FileObject) {
$filename = $file->getFilename();
} else
continue;
}
$mime->addAttachment($file->getData(),
$file->getMimeType(), $filename, false);
try {
$message->addAttachment($file, $filename);
} catch(\Exception $ex) {
$this->logWarning(sprintf("%1\$s:%2\$s\n\n%3\$s\n",
_S("Unable to retrieve email attachment"),
$filename,
$ex->getMessage()));
}
}
}
//Desired encodings...
$encodings=array(
'head_encoding' => 'quoted-printable',
'text_encoding' => 'base64',
'html_encoding' => 'base64',
'html_charset' => 'utf-8',
'text_charset' => 'utf-8',
'head_charset' => 'utf-8'
);
//encode the body
$body = $mime->get($encodings);
//encode the headers.
$headers = $mime->headers($headers, true);
$to = implode(',', array_filter(array($headers['To'], $headers['Cc'],
$headers['Bcc'])));
// Cache smtp connections made during this request
static $smtp_connections = array();
if(($smtp=$this->getSMTPInfo())) { //Send via SMTP
$key = sprintf("%s:%s:%s", $smtp['host'], $smtp['port'],
$smtp['username']);
if (!isset($smtp_connections[$key])) {
$mail = mail::factory('smtp', array(
'host' => $smtp['host'],
'port' => $smtp['port'],
'auth' => $smtp['auth'],
'username' => $smtp['username'],
'password' => $smtp['password'],
'timeout' => 20,
'debug' => false,
'persist' => true,
// Try possible SMTP Accounts - connections are cached per request
// at the account level.
foreach ($this->getSmtpAccounts() ?: [] as $smtpAccount) {
try {
// Check if SPOOFING is allowed.
if (!$smtpAccount->allowSpoofing()
&& strcasecmp($smtpAccount->getEmail()->getEmail(),
$this->getFromEmail())) {
// If the Account DOES NOT allow spoofing then set the
// Sender as the smtp account sending out the email
// TODO: Allow Aliases to Spoof parent account by
// default.
$message->setSender(
// get Account Email
(string) $smtpAccount->getEmail()->getEmail(),
// Try to keep the name if available
$this->getFromName() ?: $smtpAccount->getName() ?: $this->getEmail());
}
// Attempt to send the Message.
if (($smtp=$smtpAccount->getSmtpConnection())
&& $smtp->sendMessage($message))
return $message->getId();
} catch (\Exception $ex) {
// Log the SMTP error
$this->logError(sprintf("%1\$s: %2\$s (%3\$s)\n\n%4\$s\n",
_S("Unable to email via SMTP"),
$smtpAccount->getEmail()->getEmail(),
$smtpAccount->getHostInfo(),
$ex->getMessage()
));
}
// Attempt Failed: Reset FROM to original email and clear Sender
$message->setOriginator($this->getFromEmail(), $this->getFromName());
}
// No SMTP or it FAILED....use Sendmail transport (PHP mail())
// Set Sender / Originator
if (isset($options['from_address'])) {
// This is often set via Mailer::sendmail()
$message->setSender($options['from_address']);
} elseif (($from=$this->getFromAddress())) {
// This should be already set but we're making doubly sure
$message->setOriginator($from->getEmail(), $from->getName());
}
try {
// ostTicket/Mail/Sendmail transport (mail())
// Set extra params as needed
// NOTE: Laminas Mail doesn't respect -f param set Sender (above)
$args = [];
$sendmail = new Sendmail($args);
if ($sendmail->sendMessage($message))
return $message->getId();
} catch (\Exception $ex) {
$this->logError(sprintf("%1\$s\n\n%2\$s\n",
_S("Unable to email via Sendmail"),
$ex->getMessage()
));
if ($mail->connect())
$smtp_connections[$key] = $mail;
}
else {
// Use persistent connection
$mail = $smtp_connections[$key];
}
$result = $mail->send($to, $headers, $body);
if(!PEAR::isError($result))
return $messageId;
// Force reconnect on next ->send()
unset($smtp_connections[$key]);
$alert=_S("Unable to email via SMTP")
.sprintf(":%1\$s:%2\$d [%3\$s]\n\n%4\$s\n",
$smtp['host'], $smtp['port'], $smtp['username'], $result->getMessage());
$this->logError($alert);
}
return false;
}
//No SMTP or it failed....use php's native mail function.
$args = array();
if (isset($options['from_address']))
$args[] = '-f '.$options['from_address'];
elseif ($this->getEmail())
$args = array('-f '.$this->getEmail()->getEmail());
$mail = mail::factory('mail', $args);
$to = $headers['To'];
$result = $mail->send($to, $headers, $body);
if(!PEAR::isError($result))
return $messageId;
function log($msg, $type='warning') {
global $ost;
$alert=_S("Unable to email via php mail function")
.sprintf(":%1\$s\n\n%2\$s\n",
$to, $result->getMessage());
$this->logError($alert);
if (!$ost || !$msg)
return;
// No email alerts on email errors
switch ($type) {
case 'error':
return $ost->logError(_S('Mailer Error'), $msg, false);
break;
case 'warning':
default:
return $ost->logWarning(_S('Mailer Warning'), $msg, false);
}
return false;
}
function logError($error) {
global $ost;
//NOTE: Admin alert override - don't email when having email trouble!
$ost->logError(_S('Mailer Error'), $error, false);
return $this->log($error, 'error');
}
/******* Static functions ************/
function logWarning($warning) {
return $this->log($warning, 'warning');
}
//Emails using native php mail function - if DB connection doesn't exist.
//Don't use this function if you can help it.
function sendmail($to, $subject, $message, $from, $options=null) {
static function sendmail($to, $subject, $message, $from=null, $options=null) {
$mailer = new Mailer(null, array('notice'=>true, 'nobounce'=>true));
$mailer->setFromAddress($from);
$mailer->setFromAddress($from, $options['from_name'] ?: null);
return $mailer->send($to, $subject, $message, $options);
}
}
?>

File diff suppressed because it is too large Load Diff

View File

@@ -27,6 +27,7 @@ class Mail_Parse {
var $decode_bodies;
var $struct;
var $headers;
var $charset ='UTF-8'; //Default charset.
@@ -35,18 +36,15 @@ class Mail_Parse {
function __construct(&$mimeMessage, $charset=null){
$this->mime_message = &$mimeMessage;
if($charset)
$this->charset = $charset;
$this->include_bodies = true;
$this->decode_headers = false;
$this->decode_bodies = true;
//Desired charset
if($charset)
if ($charset)
$this->charset = $charset;
$this->splitBodyHeader();
$this->notes = array();
}
@@ -61,14 +59,13 @@ class Mail_Parse {
$info = array('raw' => &$this->mime_message);
Signal::send('mail.received', $this, $info);
$this->splitBodyHeader();
$decoder = new Mail_mimeDecode($this->mime_message);
$this->struct = $decoder->decode($params);
if (PEAR::isError($this->struct))
return false;
$this->headers = &$this->struct->headers;
$info = array(
'raw_header' => &$this->header,
'headers' => &$this->struct->headers,
@@ -132,14 +129,35 @@ class Mail_Parse {
return (count($this->struct->headers) > 1);
}
function getMimeMessage() {
return $this->mime_message;
}
function getMimeFile() {
$file = [
// Use subject if available otherwise use mid.
'name' => sprintf('%s.eml',
$this->getSubject() ?: $this->getMessageId()),
'type' => 'message/rfc822',
'data' => $this->getMimeMessage(),
];
// See if we have transfer encoding
if (($encoding=$this->getHeaderEntry('content-transfer-encoding')))
$file['encoding'] = $encoding;
return $file;
}
function splitBodyHeader() {
$match = array();
if (preg_match("/^(.*?)\r?\n\r?\n./s",
$this->mime_message,
$match)) {
if (!$this->header
&& preg_match("/^(.*?)\r?\n\r?\n./s",
$this->mime_message,
$match)) {
$this->header=$match[1];
}
}
/**
* Takes the header section of an email message with the form of
* Header: Value
@@ -159,7 +177,7 @@ class Mail_Parse {
if (in_array($headers[$i][0], array(" ", "\t"))) {
# Continuation from previous header (runon to next line)
$j=$i-1; while (!isset($headers[$j]) && $j>0) $j--;
$headers[$j] .= " ".ltrim($headers[$i]);
$headers[strtolower($j)] .= " ".ltrim($headers[$i]);
unset($headers[$i]);
} elseif (strlen($headers[$i]) == 0) {
unset($headers[$i]);
@@ -169,6 +187,7 @@ class Mail_Parse {
foreach ($headers as $hdr) {
list($name, $val) = explode(": ", $hdr, 2);
# Create list of values if header is specified more than once
$name = strtolower($name);
if (isset($array[$name]) && $as_array) {
if (is_array($array[$name])) $array[$name][] = $val;
else $array[$name] = array($array[$name], $val);
@@ -180,13 +199,18 @@ class Mail_Parse {
}
/* static */
function findHeaderEntry($headers, $name, $allEntries=false) {
static function findHeaderEntry($headers, $name, $allEntries=false) {
if (!is_array($headers))
$headers = self::splitHeaders($headers, $allEntries);
foreach ($headers as $key=>$val)
if (strcasecmp($key, $name) === 0)
return $val;
return false;
$name = strtolower($name);
return $headers[$name] ?? false;
}
function getHeaderEntry($name) {
return $this->hasHeaders()
? self::findHeaderEntry($this->getHeaders(), $name)
: false;
}
function getStruct(){
@@ -194,13 +218,26 @@ class Mail_Parse {
}
function getHeader() {
if(!$this->header) $this->splitBodyHeader();
if (!$this->header)
$this->splitBodyHeader();
return $this->header;
}
function getHeaders() {
if (!isset($this->headers))
$this->headers = self::splitHeaders($this->getHeader());
return $this->headers;
}
function hasHeaders() {
return ($this->getHeaders());
}
function getError(){
return PEAR::isError($this->struct)?$this->struct->getMessage():'';
return PEAR::isError($this->struct)
? $this->struct->getMessage() : '';
}
@@ -208,68 +245,82 @@ class Mail_Parse {
if (!($header = $this->struct->headers['from']))
return null;
if (is_string($header) && (strpos($header, '<') !== false) && (strpos($header, '>') === false))
$header = $header.'>';
return Mail_Parse::parseAddressList($header, $this->charset);
}
function getDeliveredToAddressList() {
if (!($header = $this->struct->headers['delivered-to']))
return null;
return Mail_Parse::parseAddressList($header, $this->charset);
return self::parseAddressList($this->getHeaderEntry('delivered-to'),
$this->charset);
}
function getToAddressList(){
// Delivered-to incase it was a BBC mail.
$tolist = array();
if ($header = $this->struct->headers['to'])
$tolist = array_merge($tolist,
Mail_Parse::parseAddressList($header, $this->charset));
return $tolist ? $tolist : null;
return self::parseAddressList($this->getHeaderEntry('to'),
$this->charset);
}
function getCcAddressList(){
if (!($header = @$this->struct->headers['cc']))
return null;
return Mail_Parse::parseAddressList($header, $this->charset);
return self::parseAddressList($this->getHeaderEntry('cc'),
$this->charset);
}
function getBccAddressList(){
if (!($header = @$this->struct->headers['bcc']))
return null;
return Mail_Parse::parseAddressList($header, $this->charset);
return self::parseAddressList($this->getHeaderEntry('bcc'),
$this->charset);
}
function getMessageId(){
if (($mid = $this->struct->headers['message-id']) && is_array($mid))
$mid = array_pop(array_filter($mid));
if (!$mid)
if (!($mid = $this->getHeaderEntry('message-id')))
$mid = sprintf('<%s@local>', md5($this->getHeader()));
if ($mid && is_array($mid))
$mid = array_pop(array_filter($mid));
return $mid;
}
function getSubject(){
return Format::mimedecode($this->struct->headers['subject'], $this->charset);
return Format::mimedecode($this->getHeaderEntry('subject'), $this->charset);
}
function getReplyTo() {
if (!($header = @$this->struct->headers['reply-to']))
return null;
return self::parseAddressList($this->getHeaderEntry('reply-to'),
$this->charset);
}
return Mail_Parse::parseAddressList($header, $this->charset);
function getInReplyTo() {
return $this->getHeaderEntry('in-reply-to');
}
function getReferences() {
return $this->getHeaderEntry('references');
}
function getRecipients() {
$recipients = [];
if (($to = $this->getToAddressList()))
$recipients['to'] = $to;
if (($cc = $this->getCcAddressList()))
$recipients['cc'] = $cc;
if (($dt = $this->getDeliveredToAddressList()))
$recipients['delivered-to'] = $dt;
return $recipients;
}
function isBounceNotice() {
if (!($body = $this->getPart($this->struct, 'message/delivery-status')))
if (!$this->struct
|| !($body = $this->getPart($this->struct,
'message/delivery-status')))
return false;
$info = self::splitHeaders($body);
if (!isset($info['Action']))
if (!isset($info['action']))
return false;
return strcasecmp($info['Action'], 'failed') === 0;
return strcasecmp($info['action'], 'failed') === 0;
}
function getDeliveryStatusMessage() {
@@ -292,7 +343,7 @@ class Mail_Parse {
// Handle rfc1892 style bounces
if (strtolower($ctype) === 'text/rfc822-headers') {
$body = $p->body . "\n\nIgnored";
$T = new Mail_mimeDecode($body);
$T = new Mail_mimeDecode($body, ['attachOnParseError' => false]);
if ($struct = $T->decode())
return $struct->headers;
}
@@ -303,6 +354,11 @@ class Mail_Parse {
function getBody(){
global $cfg;
// When struct is not set then it might mean decode error - return
// empty body
if (!$this->struct)
return new TextThreadEntryBody('');
if ($cfg && $cfg->isRichTextEnabled()) {
if ($html=$this->getPart($this->struct,'text/html'))
$body = new HtmlThreadEntryBody($html);
@@ -318,8 +374,7 @@ class Mail_Parse {
if (!isset($body))
$body = new TextThreadEntryBody('');
if ($cfg && $cfg->stripQuotedReply())
elseif ($cfg && $cfg->stripQuotedReply())
$body->stripQuotedReply($cfg->getReplySeparator());
return $body;
@@ -443,6 +498,11 @@ class Mail_Parse {
&& $part->headers['content-disposition']
&& preg_match('/filename="([^"]+)"/', $part->headers['content-disposition'], $matches)) {
$filename = Format::mimedecode($matches[1], $this->charset);
} elseif (preg_match('/filename\*\d+=([^;]+);/', $part->headers['content-disposition']) === 1) {
$filename = '';
foreach ($part->d_parameters as $key=>$fname)
if (strpos($key, 'filename*') !== false)
$filename .= Format::decodeRfc5987($fname);
} else {
// Not an attachment?
return false;
@@ -490,8 +550,12 @@ class Mail_Parse {
return $files;
}
if($part==null)
if($part==null) {
$part=$this->getStruct();
if (!$part->parts)
if($result=$this->getAttachments($part))
$files=array_merge($files,$result);
}
if($part->parts){
foreach($part->parts as $k=>$p){
@@ -510,7 +574,7 @@ class Mail_Parse {
// http://msdn.microsoft.com/en-us/library/ee237166(v=exchg.80).aspx
$urgency = 4 - $this->tnef->Importance;
}
elseif ($priority = Mail_Parse::parsePriority($this->getHeader())) {
elseif ($priority = self::parsePriority($this->getHeader())) {
$urgency = $priority + 1;
}
if ($urgency) {
@@ -522,6 +586,114 @@ class Mail_Parse {
}
}
function getHeaderInfo() {
if (!$this->hasHeaders())
return null;
$info = [];
$info['emailId'] = 0;
$info['recipients'] = array();
$info['subject'] = $this->getSubject();
$info['header'] = $this->getHeader();
$info['mid'] = $this->getMessageId();
$info['priorityId'] = $this->getPriority();
$info['mailflags'] = new ArrayObject();
//FROM address: who sent the email.
if (($fromlist = $this->getFromAddressList())) {
$from=$fromlist[0]; //Default.
foreach ($fromlist as $fromobj) {
if(!Validator::is_email($fromobj->mailbox.'@'.$fromobj->host)) continue;
$from = $fromobj;
break;
}
$info['email'] = $from->mailbox.'@'.$from->host;
$info['name'] = trim($from->personal,'"');
if($from->comment && $from->comment[0])
$info['name'].= ' ('.$from->comment[0].')';
//Use email address as name when FROM address doesn't have a name.
if(!$info['name'] && $info['email'])
$info['name'] = $info['email'];
}
/* Scan through the list of addressees (via To, Cc, and Delivered-To headers), and identify
* how the mail arrived at the system. One of the mails should be in the system email list.
* The recipient list (without the Delivered-To addressees) will be made available to the
* ticket filtering system. However, addresses in the Delivered-To header should never be
* considered for the collaborator list.
*/
$recipients = $this->getRecipients();
$info['system_emails'] = $info['thread_entry_recipients'] = [];
foreach ($recipients as $source => $list) {
foreach($list as $addr) {
if (!($emailId=Email::getIdByEmail(strtolower($addr->mailbox).'@'.$addr->host))) {
//Skip virtual Delivered-To addresses
if ($source == 'delivered-to') continue;
$name = trim(@$addr->personal, '"');
$email = strtolower($addr->mailbox).'@'.$addr->host;
$info['recipients'][] = array(
'source' => sprintf(_S("Email (%s)"), $source),
'name' => $name,
'email' => $email);
$info['thread_entry_recipients'][$source][] = sprintf('%s <%s>', $name, $email);
} elseif ($emailId) {
$info['system_emails'][] = $emailId;
$system_email = Email::lookup($emailId);
$info['thread_entry_recipients']['to'][] = (string) $system_email;
if (!$info['emailId'])
$info['emailId'] = $emailId;
}
}
}
$info['thread_entry_recipients']['to'] = isset($info['thread_entry_recipients']['to'])
? array_unique($info['thread_entry_recipients']['to'])
: [];
/*
* In the event that the mail was delivered to the system although none of the system
* mail addresses are in the addressee lists, be careful not to include the addressee
* in the collaborator list. Therefore, the delivered-to addressees should be flagged so they
* are not added to the collaborator list in the ticket creation process.
*/
if (isset($recipients['delivered-to'])) {
foreach ($recipients['delivered-to'] as $addr) {
foreach ($info['recipients'] as $i=>$r) {
if (strcasecmp($r['email'], $addr->mailbox.'@'.$addr->host) === 0)
$info['recipients'][$i]['source'] = 'delivered-to';
}
}
}
//maybe we got BCC'ed??
if(isset($recipients['bcc'])) {
foreach ($recipients['bcc'] as $addr) {
if (($emailId=Email::getIdByEmail($addr->mailbox.'@'.$addr->host))) {
$info['system_emails'][] = $emailId;
if (!$info['emailId'])
$info['emailId'] = $emailId;
}
}
}
// reply to ?
if (($replyto = $this->getReplyTo())
&& ($replyto = $replyto[0])) {
$info['reply-to'] = $replyto->mailbox.'@'.$replyto->host;
if ($replyto->personal)
$info['reply-to-name'] = trim($replyto->personal, " \t\n\r\0\x0B\x22");
}
$info['system_emails'] = array_unique($info['system_emails']);
$info['to-email-id'] = $info['emailId'];
$info['in-reply-to'] = $this->getInReplyTo();
$info['references'] = $this->getReferences();
$info['mailflags']['bounce'] = TicketFilter::isBounce($info['header']);
return $info;
}
/**
* Return a normalized priority urgency from the email headers received.
*
@@ -561,7 +733,7 @@ class Mail_Parse {
return 0;
}
function parseAddressList($address, $charset='UTF-8'){
static function parseAddressList($address, $charset='UTF-8'){
if (!$address)
return array();
@@ -569,7 +741,8 @@ class Mail_Parse {
if (is_array($address))
$address = implode(', ', $address);
$parsed = Mail_RFC822::parseAddressList($address, null, null,false);
$rfc822 = new Mail_RFC822();
$parsed = $rfc822->parseAddressList($address, null, null,false);
if (PEAR::isError($parsed))
return array();
@@ -596,11 +769,16 @@ class Mail_Parse {
}
class EmailDataParser {
var $stream;
var $error;
private $options = [
'attachOnParseError' => true
];
private $stream;
private $errors = [];
function __construct($stream=null) {
function __construct($stream=null, array $options = []) {
$this->stream = $stream;
if ($options)
$this->options = array_merge($this->options, $options);
}
function parse($stream) {
@@ -610,154 +788,68 @@ class EmailDataParser {
if(is_resource($stream)) {
while(!feof($stream))
$contents .= fread($stream, 8192);
} else {
$contents = $stream;
}
$parser= new Mail_Parse($contents);
if(!$parser->decode()) //Decode...returns false on decoding errors
return $this->err('Email parse failed ['.$parser->getError().']');
if (!$parser->decode())
return $this->onParseError($parser);
$data =array();
$data['emailId'] = 0;
$data['recipients'] = array();
$data['subject'] = $parser->getSubject();
$data['header'] = $parser->getHeader();
$data['mid'] = $parser->getMessageId();
$data['priorityId'] = $parser->getPriority();
$data['mailflags'] = new ArrayObject();
//FROM address: who sent the email.
if(($fromlist = $parser->getFromAddressList())) {
$from=$fromlist[0]; //Default.
foreach($fromlist as $fromobj) {
if(!Validator::is_email($fromobj->mailbox.'@'.$fromobj->host)) continue;
$from = $fromobj;
break;
}
$data['email'] = $from->mailbox.'@'.$from->host;
$data['name'] = trim($from->personal,'"');
if($from->comment && $from->comment[0])
$data['name'].= ' ('.$from->comment[0].')';
//Use email address as name when FROM address doesn't have a name.
if(!$data['name'] && $data['email'])
$data['name'] = $data['email'];
}
/* Scan through the list of addressees (via To, Cc, and Delivered-To headers), and identify
* how the mail arrived at the system. One of the mails should be in the system email list.
* The recipient list (without the Delivered-To addressees) will be made available to the
* ticket filtering system. However, addresses in the Delivered-To header should never be
* considered for the collaborator list.
*/
$tolist = array();
if (($to = $parser->getToAddressList()))
$tolist['to'] = $to;
if (($cc = $parser->getCcAddressList()))
$tolist['cc'] = $cc;
if (($dt = $parser->getDeliveredToAddressList()))
$tolist['delivered-to'] = $dt;
$data['system_emails'] = array();
$data['thread_entry_recipients'] = array();
foreach ($tolist as $source => $list) {
foreach($list as $addr) {
if (!($emailId=Email::getIdByEmail(strtolower($addr->mailbox).'@'.$addr->host))) {
//Skip virtual Delivered-To addresses
if ($source == 'delivered-to') continue;
$name = trim(@$addr->personal, '"');
$email = strtolower($addr->mailbox).'@'.$addr->host;
$data['recipients'][] = array(
'source' => sprintf(_S("Email (%s)"), $source),
'name' => $name,
'email' => $email);
$data['thread_entry_recipients'][$source][] = sprintf('%s <%s>', $name, $email);
} elseif ($emailId) {
$data['system_emails'][] = $emailId;
$system_email = Email::lookup($emailId);
$data['thread_entry_recipients']['to'][] = (string) $system_email;
if (!$data['emailId'])
$data['emailId'] = $emailId;
}
}
}
$data['thread_entry_recipients']['to'] = array_unique($data['thread_entry_recipients']['to']);
/*
* In the event that the mail was delivered to the system although none of the system
* mail addresses are in the addressee lists, be careful not to include the addressee
* in the collaborator list. Therefore, the delivered-to addressees should be flagged so they
* are not added to the collaborator list in the ticket creation process.
*/
if ($tolist['delivered-to']) {
foreach ($tolist['delivered-to'] as $addr) {
foreach ($data['recipients'] as $i=>$r) {
if (strcasecmp($r['email'], $addr->mailbox.'@'.$addr->host) === 0)
$data['recipients'][$i]['source'] = 'delivered-to';
}
}
}
//maybe we got BCC'ed??
if($bcc = $parser->getBccAddressList()) {
foreach ($bcc as $addr) {
if (($emailId=Email::getIdByEmail($addr->mailbox.'@'.$addr->host))) {
$data['system_emails'][] = $emailId;
if (!$data['emailId'])
$data['emailId'] = $emailId;
}
}
}
$data['system_emails'] = array_unique($data['system_emails']);
if (!($data = $parser->getHeaderInfo()))
return false;
if ($parser->isBounceNotice()) {
// Fetch the original References and assign to 'references'
if ($headers = $parser->getOriginalMessageHeaders()) {
if (($headers = $parser->getOriginalMessageHeaders())) {
$data['references'] = $headers['references'];
$data['in-reply-to'] = $headers['message-id'] ?: @$headers['in-reply-to'] ?: null;
$data['in-reply-to'] = $headers['message-id']
?: ($data['in-reply-to'] ?: null);
}
// Fetch deliver status report
$data['message'] = $parser->getDeliveryStatusMessage() ?: $parser->getBody();
$data['thread-type'] = 'N';
$data['mailflags']['bounce'] = true;
}
else {
} else {
// Typical email
$data['message'] = $parser->getBody();
$data['in-reply-to'] = @$parser->struct->headers['in-reply-to'];
$data['references'] = @$parser->struct->headers['references'];
$data['mailflags']['bounce'] = TicketFilter::isBounce($data['header']);
}
$data['to-email-id'] = $data['emailId'];
if (($replyto = $parser->getReplyTo())) {
$replyto = $replyto[0];
$data['reply-to'] = $replyto->mailbox.'@'.$replyto->host;
if ($replyto->personal)
$data['reply-to-name'] = trim($replyto->personal, " \t\n\r\0\x0B\x22");
}
$data['attachments'] = $parser->getAttachments();
return $data;
}
function err($error) {
$this->error = $error;
private function attachOnParseError() {
return (bool) $this->options['attachOnParseError'];
}
return false;
private function onParseError($parser) {
$error = sprintf('%s: [%s]',
__('Email parse failed'),
$parser->getError());
if ($this->attachOnParseError()
&& $parser
// Make sure we can get header info
&& ($data=$parser->getHeaderInfo())) {
// Text thread entry body with the warning / error
$data['message'] = new TextThreadEntryBody($error);
// Add Mime message as an Attachment
$data['attachments'][] = $parser->getMimeFile();
return $data;
}
return $this->err($error);
}
private function err($error) {
// add error and return false
return ($this->addError($error) && false);
}
function getErrors() {
return $this->errors;
}
function addError($error) {
$this->errors [] = $error;
}
function getError() {
@@ -765,7 +857,7 @@ class EmailDataParser {
}
function lastError() {
return $this->error;
return end($this->getErrors());
}
}
?>

View File

@@ -183,7 +183,7 @@ abstract class BaseMessageStorage implements MessageStorageBackend {
return $messages;
}
function getIterator() {
function getIterator(): Traversable {
$this->used = true;
$messages = $this->load();
if ($this->queued) {

View File

@@ -78,9 +78,8 @@ class DatabaseMigrater {
* If the hash file doesn't match the schema_signature on record, then an
* update is triggered and the patches in the stream folder are used to
* upgrade the database.
*/
/* static */
function getUpgradeStreams($basedir) {
*/
static function getUpgradeStreams($basedir) {
static $streams = array();
if ($streams) return $streams;

48
include/class.mime.php Normal file
View File

@@ -0,0 +1,48 @@
<?php
/**
* class.mime.php
*
* osTicket MIME utilities — extensions for Laminas\Mime components.
*
* @author JediKev <kevin@enhancesoft.com>
* @copyright Copyright (c) osTicket <gpl@osticket.com>
*
*/
namespace osTicket\Mime {
use Laminas\Mime\Mime;
use Laminas\Mime\Part;
class Rfc2231Part extends Part {
private ?string $rawfilename = null;
/**
* Store the raw (UTF-8) filename for RFC-2231 emission.
*/
public function setRawFilename(string $name): void {
$this->rawfilename = $name;
}
/**
* Override to append ; filename*=UTF-8''<percent-encoded> to Content-Disposition.
* Keep everything else from the parent as-is.
*/
public function getHeadersArray($eol = Mime::LINEEND): array {
$headers = parent::getHeadersArray($eol);
if ($this->rawfilename === null || $this->rawfilename === '')
return $headers;
// RFC 2231: UTF-8 then percent-encode (spaces as %20)
$encoded = rawurlencode($this->rawfilename);
foreach ($headers as &$h) {
if (strcasecmp($h[0], 'Content-Disposition') === 0) {
$h[1] .= "; filename*=UTF-8''{$encoded}";
break;
}
}
unset($h);
return $headers;
}
}
}

View File

@@ -16,7 +16,7 @@
class Misc {
function randCode($len=8, $chars=false) {
static function randCode($len=8, $chars=false) {
$chars = $chars ?: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_=';
// Determine the number of bits we need
@@ -42,7 +42,7 @@ class Misc {
return substr($output, 0, $len);
}
function __rand_seed($value=0) {
static function __rand_seed($value=0) {
// Form a 32-bit figure for the random seed with the lower 16-bits
// the microseconds of the current time, and the upper 16-bits from
// received value
@@ -52,7 +52,7 @@ class Misc {
}
/* Helper used to generate ticket IDs */
function randNumber($len=6) {
static function randNumber($len=6) {
$number = '';
for ($i=0; $i<$len; $i++) {
$min = ($i == 0) ? 1 : 0;
@@ -63,7 +63,7 @@ class Misc {
}
/* misc date helpers...this will go away once we move to php 5 */
function db2gmtime($var){
static function db2gmtime($var){
static $dbtz;
global $cfg;
@@ -83,7 +83,7 @@ class Misc {
}
// Take user's time and return GMT time.
function user2gmtime($timestamp=null, $user=null) {
static function user2gmtime($timestamp=null, $user=null) {
global $cfg;
$tz = new DateTimeZone($cfg->getTimezone($user));
@@ -103,7 +103,7 @@ class Misc {
}
//Take user time or gmtime and return db (mysql) time.
function dbtime($var=null){
static function dbtime($var=null){
static $dbtz;
global $cfg;
@@ -124,7 +124,7 @@ class Misc {
}
/*Helper get GM time based on timezone offset*/
function gmtime($time=false, $user=false) {
static function gmtime($time=false, $user=false) {
global $cfg;
$tz = new DateTimeZone($user ? $cfg->getDbTimezone($user) : 'UTC');
@@ -140,14 +140,14 @@ class Misc {
}
/* Needed because of PHP 4 support */
function micro_time() {
static function micro_time() {
list($usec, $sec) = explode(" ", microtime());
return ((float)$usec + (float)$sec);
}
// Date range for the period in a given time
function date_range($period, $time=false, $tz=null) {
static function date_range($period, $time=false, $tz=null) {
$time = $time ?: self::gmtime();
if (!($dt = Format::parseDateTime($time)))
return null;
@@ -226,30 +226,7 @@ class Misc {
return (object) array('start' => $start, 'end' => $end);
}
//Current page
function currentURL() {
$str = 'http';
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
$str .='s';
}
$str .= '://';
if (!isset($_SERVER['REQUEST_URI'])) { //IIS???
$_SERVER['REQUEST_URI'] = substr($_SERVER['PHP_SELF'],1 );
if (isset($_SERVER['QUERY_STRING'])) {
$_SERVER['REQUEST_URI'].='?'.$_SERVER['QUERY_STRING'];
}
}
if ($_SERVER['SERVER_PORT']!=80) {
$str .= $_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].$_SERVER['REQUEST_URI'];
} else {
$str .= $_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'];
}
return $str;
}
function realpath($path) {
static function realpath($path) {
$rp = realpath($path);
return $rp ? $rp : $path;
}

View File

@@ -27,7 +27,7 @@ class ObjectModel {
const OBJECT_TYPE_STAFF = 'S';
const OBJECT_TYPE_CHILD_TICKET = 'C';
private function objects() {
private static function objects() {
static $objects = false;
if ($objects == false) {
$objects = array(

View File

@@ -125,7 +125,7 @@ class StaffNav {
function getTabs(){
global $thisstaff;
if(!$this->tabs) {
if(!isset($this->tabs)) {
$this->tabs = array();
$this->tabs['dashboard'] = array(
'desc'=>__('Dashboard'),'href'=>'dashboard.php','title'=>__('Agent Dashboard'), "class"=>"no-pjax"
@@ -210,7 +210,7 @@ class AdminNav extends StaffNav{
function getTabs(){
if(!$this->tabs){
if(!isset($this->tabs)){
$tabs=array();
$tabs['dashboard']=array('desc'=>__('Dashboard'),'href'=>'logs.php','title'=>__('Admin Dashboard'));

134
include/class.oauth2.php Normal file
View File

@@ -0,0 +1,134 @@
<?php
/**
* class.oauth2.php
*
* osTicket OAuth2 Utils & Helpers
*
* @author Peter Rotich <peter@osticket.com>
* @copyright Copyright (c) osTicket <gpl@osticket.com>
*
*/
namespace osTicket\OAuth2 {
// Define exceptions as RunTimeException
class Exception extends \RuntimeException { }
/**
* osTicket OAuth2 Access Token
*
* Based on AcessToken class from league/oauth2-client library (MIT)
* @link http://tools.ietf.org/html/rfc6749#section-1.4 Access Token (RFC 6749, §1.4)
*
*/
class AccessToken {
protected $accessToken;
protected $expires;
protected $refreshToken;
protected $resourceOwnerId;
protected $scope;
// osTicket specific
protected $resourceOwnerEmail;
protected $configSignature;
public function __construct(array $options = []) {
if (empty($options['access_token'])) {
throw new \InvalidArgumentException(sprintf(
__('Required option not passed: "%s"'),
'access_token'));
}
$this->accessToken = $options['access_token'];
if (!empty($options['refresh_token']))
$this->refreshToken = $options['refresh_token'];
if (!empty($options['expires']))
$this->expires = $options['expires'];
if (!empty($options['config_signature']))
$this->configSignature = $options['config_signature'];
if (!empty($options['resource_owner_id']))
$this->resourceOwnerId = $options['resource_owner_id'];
if (!empty($options['resource_owner_email']))
$this->resourceOwnerEmail = $options['resource_owner_email'];
if (!empty($options['scope']))
$this->scope = $options['scope'];
}
public function getToken() {
return $this->accessToken;
}
public function getAccessToken() {
return $this->getToken();
}
public function getRefreshToken() {
return $this->refreshToken;
}
public function getExpires() {
return $this->expires;
}
public function getResourceOwnerId() {
return $this->resourceOwnerId;
}
public function getResourceOwnerEmail() {
return $this->resourceOwnerEmail;
}
public function getResourceOwner() {
return $this->getResourceOwnerEmail();
}
public function getScope() {
return $this->scope;
}
public function getConfigSignature() {
return $this->configSignature;
}
public function hasExpired() {
$expires = $this->getExpires();
if (empty($expires))
throw new \RuntimeException('"expires" is not set on the token');
return $expires < time();
}
public function isExpired() {
return $this->hasExpired();
}
public function isMatch($email, $strict=false) {
return (!$strict || strcasecmp($this->getResourceOwnerEmail(), $email) === 0);
}
public function getAuthRequest($user=null) {
if ($this->hasExpired())
throw new Exception('Access Token is Expired');
return base64_encode(sprintf("user=%s\1auth=Bearer %s\1\1",
$user ?? $this->getResourceOwner(),
$this->getAccessToken()));
}
public function __toString() {
return (string) $this->getToken();
}
public function toArray() {
return [
'access_token' => $this->getToken(),
'refresh_token' => $this->getRefreshToken(),
'expires' => $this->getExpires(),
'config_signature' => $this->getConfigSignature(),
'resource_owner_id' => $this->getResourceOwnerId(),
'resource_owner_email' => $this->getResourceOwnerEmail(),
];
}
}
}

Some files were not shown because too many files have changed in this diff Show More