file_hash = md5($uid . wp_rand(0, 1000)); }); } /** * Delete the stored files for these attachments (local, or via the remote * driver hook). DB rows are left to the caller. * * @param \FluentSupport\Framework\Database\Orm\Collection|array $attachments * @param int|null $ticketId When provided, removes the ticket_{id} upload directory. */ public static function purgeAttachments($attachments, $ticketId = null) { foreach ($attachments as $attachment) { if ($attachment->driver && $attachment->driver !== 'local') { do_action('fluent_support/delete_remote_attachment_' . $attachment->driver, $attachment); } elseif ($attachment->file_path && file_exists($attachment->file_path)) { wp_delete_file($attachment->file_path); } } if ($ticketId) { $uploadDir = wp_upload_dir(); $ticketDir = $uploadDir['basedir'] . '/' . FLUENT_SUPPORT_UPLOAD_DIR . '/ticket_' . $ticketId; if (is_dir($ticketDir)) { if (!class_exists('\WP_Filesystem_Direct')) { require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php'; } (new \WP_Filesystem_Direct(false))->rmdir($ticketDir, true); } } } public function setSettingsAttribute($settings) { $this->attributes['settings'] = \maybe_serialize($settings); } public function getSettingsAttribute($settings) { return Helper::safeUnserialize($settings); } /** * Signed download URL. * * @return string */ public function getSecureUrlAttribute() { return $this->signedUrl(self::signedUrlExpiry()); } /** * Download URL valid until $expires. * * @param int $expires Unix timestamp. * @return string */ public function signedUrl($expires) { return add_query_arg([ 'fst_file' => $this->file_hash, 'expires' => (int) $expires, 'secure_sign' => self::signature($this->id, (int) $expires) ], site_url('/index.php')); } /** * Expiry for a newly signed URL. Rounded to a TTL boundary so the URL stays * the same for a while (browser caching), and always at least one TTL ahead. * A URL signed just before a boundary used to die within seconds. * * @return int Unix timestamp. */ public static function signedUrlExpiry() { $ttl = max(MINUTE_IN_SECONDS * 5, (int) apply_filters('fluent_support/attachment_signed_url_ttl', HOUR_IN_SECONDS)); return (intdiv(time(), $ttl) + 2) * $ttl; } /** * Signature for an expiring URL. The 'v2|' prefix keeps it apart from legacy * signatures: those signed "id|YmdH", and without the prefix an old hourly * signature would pass as one that expires in 2034 (2026100614 read as a timestamp). * * @param int $attachmentId * @param int $expires Unix timestamp. * @return string */ public static function signature($attachmentId, $expires) { return hash_hmac('sha256', 'v2|' . $attachmentId . '|' . (int) $expires, wp_salt('secure_auth')); } /** * Signature of a pre-2.4.1 URL, valid for one UTC hour. * * @param int $attachmentId * @param string $hourWindow gmdate('YmdH') * @return string */ public static function legacySignature($attachmentId, $hourWindow) { return hash_hmac('sha256', $attachmentId . '|' . $hourWindow, wp_salt('secure_auth')); } }