File size: 3,905 Bytes
43203b4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
(in-package :forms)

(defclass file-form-field (form-field)
  ((multiple :initarg :multiple-p
             :accessor multiple-p
             :initform nil
             :documentation "If this fields handles multiple file uploads")
   (path :initarg :path
         :accessor file-path
         :initform nil)
   (file-name :initarg :file-name
              :accessor file-name
              :initform nil)
   (content-type :initarg :content-type
                 :accessor file-content-type
                 :initform nil)
   (upload-handler :initarg :upload-handler
                   :accessor upload-handler
                   :initform nil
                   :documentation "Function that handles the file upload")
   (accept :initarg :accept
           :accessor file-accept
           :type (or null string)
           :initform nil
           :documentation "Files accepted. See https://www.w3schools.com/tags/att_input_accept.asp"))
  (:documentation "A file input field"))

(defmethod field-read-from-request ((field file-form-field) form parameters)
  (let ((fvalue
          (cdr (assoc (field-request-name field form) parameters :test #'string=))))
    (if (and fvalue (listp fvalue))
        (destructuring-bind (path file-name content-type) fvalue
          (setf (file-path field) path)
          (setf (file-name field) file-name)
          (setf (file-content-type field) content-type)
          (when (upload-handler field)
            (funcall (upload-handler field) field)))
        ;; else
        (progn
          (warn "CL-FORMS: Could not read file field file: ~a." field)
          (when (not (equalp (form-enctype form) "multipart/form-data"))
            (warn "CL-FORMS: Encoding type in forms should be set to 'multipart/form-data' for file uploads."))))
    ;; Always set the field value
    (setf (field-value field) fvalue)))

(defmethod make-form-field ((field-type (eql :file)) &rest args)
  (apply #'make-instance 'file-form-field args))

;; Stateful file field

(defclass stateful-file-field (forms::file-form-field)
  ((download-link :initarg :download-link
                  :type (or null string symbol function)
                  :initform nil
                  :documentation "Function to generate the download link to the file"))
  (:documentation "An stateful file field is a file field that mantains its value (the uploaded file data) and renders the the uploaded file download link and a file upload widget"))

(defmethod download-link ((field stateful-file-field))
  (with-slots (download-link) field
    (if (stringp download-link)
        download-link
        (funcall download-link field))))

(defmethod forms::field-read-from-request ((field stateful-file-field) form parameters)
  (let ((fvalue
          (cdr (assoc (forms::field-request-name field form) parameters :test #'string=))))
    (if (and fvalue (listp fvalue))
        ;; A file was uploaded
        (destructuring-bind (path file-name content-type) fvalue
          (setf (forms::file-path field) path)
          (setf (forms::file-name field) file-name)
          (setf (forms::file-content-type field) content-type)
          (when (forms::upload-handler field)
            (funcall (forms::upload-handler field) field)))
        ;; else, recover file info, if it is there
        (let ((file-info
                (cdr (assoc (format nil "~A.info" (forms::field-request-name field form)) parameters :test #'string=))))
          (when file-info
            (let ((finfo (read-from-string (base64:base64-string-to-string file-info))))
              (setf (forms::file-path field) (getf finfo :path)
                    (forms::file-name field) (getf finfo :file-name)
                    (forms::file-content-type field) (getf finfo :content-type))))))))

(defmethod forms::make-form-field ((field-type (eql :stateful-file)) &rest args)
  (apply #'make-instance 'stateful-file-field args))