Skip to content

Commit 1048605

Browse files
author
Derek Crannaford
committed
Merge branch 'feat/weedremeed' into 'develop'
RC - new features from major project requirements See merge request 2pisoftware/cosine/core!420
2 parents 69da42d + f8f36b6 commit 1048605

22 files changed

Lines changed: 1107 additions & 276 deletions

system/classes/HtmlBootstrap5.php

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,205 @@ public static function dropdownButton($title, $contents, $class)
4848
</div>';
4949
}
5050

51+
/**
52+
* creates a simple one column form from the following array:
53+
* array(
54+
* array("title","type","fieldname","value",{size | array(select options) | cols, rows}),
55+
* ...
56+
* )
57+
*
58+
* valid field types are:
59+
* text, password, autocomplete, static, date, textarea, section,
60+
* select, multiselect, checkbox, hidden
61+
*
62+
* Field type auto uses ui hints from a DbObject.
63+
*
64+
* when prefixing a fieldname with a minus sign '-' this field will be read-only
65+
*/
66+
public static function form($data, $action = null, $method = "POST", $submitTitle = "Save", $id = null, $class = null, $target = "_self", $enctype = null)
67+
{
68+
if (empty($data)) {
69+
return;
70+
}
71+
72+
$buffer = "";
73+
74+
if (null !== $action) {
75+
$form = new \Html\FormBootstrap5();
76+
77+
// If form tag is needed print it
78+
$class .= " col";
79+
$form->id($id)->setClass($class)->method($method)->action($action)->target($target);
80+
81+
if (in_modified_multiarray("file", $data, 1)) {
82+
$form->enctype("multipart/form-data");
83+
}
84+
85+
$buffer .= $form->open();
86+
}
87+
88+
foreach ($data as $row) {
89+
$buffer .= "<div class='row'><div class='col-12'>";
90+
91+
// Backwards compatibility - provide option to pass additional data
92+
$field = null;
93+
$tooltip = null;
94+
if (is_object($row)) {
95+
$field = property_exists($row, 'field') ? $row->field : $row;
96+
$tooltip = property_exists($row, 'tooltip') ? $row->tooltip : null;
97+
} else {
98+
$field = array_key_exists('field', $row) ? $row['field'] : $row;
99+
$tooltip = array_key_exists('tooltip', $row) ? $row['tooltip'] : null;
100+
}
101+
102+
// Check if the row is an object like an InputField
103+
if (!is_array($field) && is_object($field)) {
104+
$label_class = 'form-label';
105+
$field->setClass(str_replace(['small-12', 'columns', 'column'], '', $field->class ?? ''));
106+
switch (get_class($field)) {
107+
case 'Html\Form\Select':
108+
case 'Html\Cmfive\SelectWithOther':
109+
$field->setClass($field->class . ' form-select');
110+
break;
111+
case 'Html\Form\InputField\Checkbox':
112+
case 'Html\Form\InputField\Radio':
113+
$field->setClass($field->class . ' form-check-control');
114+
// $label_class = 'form-check-label';
115+
break;
116+
case 'Html\Form\InputField\Text':
117+
case 'Html\Form\InputField\Date':
118+
case 'Html\Form\InputField\File':
119+
case 'Html\Form\InputField\Number':
120+
default:
121+
$field->setClass($field->class . ' form-control');
122+
break;
123+
}
124+
if ((property_exists($field, "type") && $field->type !== "hidden") || !property_exists($field, "type")) {
125+
$buffer .= '<div class="col"><label class="' . $label_class . '"'
126+
. (property_exists($field, 'id') && !empty($field->id) ? ' for="' . $field->id . '"' : '')
127+
. '>'
128+
. $field->label
129+
. (property_exists($field, "required") && $field->required ? " <small>Required</small>" : "")
130+
. "</label>"
131+
. $field->__toString() . '</div>';
132+
} else {
133+
$buffer .= $field->__toString();
134+
}
135+
continue;
136+
}
137+
138+
$title = !empty($field[0]) ? $field[0] : '';
139+
$type = !empty($field[1]) ? $field[1] : '';
140+
$name = !empty($field[2]) ? $field[2] : '';
141+
$value = !empty($field[3]) ? $field[3] : '';
142+
$readonly = "";
143+
144+
// handle disabled fields
145+
if (substr(($name ?? ""), 0, 1) == '-') {
146+
$name = substr(($name ?? ""), 1);
147+
$readonly = " readonly='true' ";
148+
}
149+
// Add title field
150+
if ("section" === $type) {
151+
$buffer .= "<h4>{$title}</h4></div></div>";
152+
continue;
153+
}
154+
155+
if (!empty($title) && "static" !== $type && "hidden" !== $type) {
156+
$buffer .= "<label class='col-12'>$title";
157+
}
158+
159+
switch ($type) {
160+
case "text":
161+
case "password":
162+
$size = !empty($field[4]) ? $field[4] : '';
163+
$required = !empty($field[5]) ? $field[5] : '';
164+
$buffer .= '<input' . $readonly . ' style="width:100%;" type="' . $type . '" name="' . $name . '" value="' . htmlspecialchars($value) . '" size="' . $size . '" id="' . $name . '" ' . $required . '/>';
165+
break;
166+
case "autocomplete":
167+
$options = !empty($field[4]) ? $field[4] : '';
168+
$minValue = !empty($field[5]) ? $field[5] : 1;
169+
$required = !empty($field[6]) ? $field[6] : '';
170+
$buffer .= HtmlBootstrap5::autocomplete($name, $options, $value, null, "width: 100%;", $minValue, $required);
171+
break;
172+
case "date":
173+
$size = !empty($field[4]) ? $field[4] : '';
174+
$buffer .= HtmlBootstrap5::datePicker($name, $value, $size);
175+
break;
176+
case "datetime":
177+
$size = !empty($field[4]) ? $field[4] : '';
178+
$buffer .= HtmlBootstrap5::datetimePicker($name, $value, $size);
179+
break;
180+
case "time":
181+
$size = !empty($field[4]) ? $field[4] : '';
182+
$buffer .= HtmlBootstrap5::timePicker($name, $value, $size);
183+
break;
184+
case "static":
185+
$size = !empty($field[4]) ? $field[4] : '';
186+
$buffer .= "<div class='col-6 col-md-3'>{$title}</div><div class='col-6 col-md-9'>{$value}</div>";
187+
break;
188+
case "textarea":
189+
$c = !empty($field[4]) ? $field[4] : '';
190+
$r = !empty($field[5]) ? $field[5] : '';
191+
$custom_class = true;
192+
if (isset($field[6])) {
193+
$custom_class = $field[6];
194+
}
195+
$buffer .= '<textarea' . $readonly . ' style="width:100%; height:auto; " name="' . $name . '" rows="' . $r . '" cols="' . $c . '" ' .
196+
(!empty($custom_class) ? ($custom_class === true ? "class='ckeditor'" : "class='$custom_class' ") : '') . ' id="' . $name . '">' . $value . '</textarea>';
197+
break;
198+
case "select":
199+
$items = !empty($field[4]) ? $field[4] : '';
200+
$default = !empty($field[5]) ? ($field[5] == "null" ? '' : $field[5]) : "-- Select --";
201+
$class = !empty($field[6]) ? $field[6] : '';
202+
if ($readonly == "") {
203+
$buffer .= HtmlBootstrap5::select($name, $items, $value, $class, "width: 100%;", $default, $readonly != "");
204+
} else {
205+
$buffer .= $value;
206+
}
207+
break;
208+
case "multiSelect":
209+
$items = !empty($field[4]) ? $field[4] : '';
210+
if ($readonly == "") {
211+
$buffer .= HtmlBootstrap5::multiSelect($name, $items, $value, null, "width: 100%;");
212+
} else {
213+
$buffer .= $value;
214+
}
215+
break;
216+
case "checkbox":
217+
$defaultValue = !empty($field[4]) ? $field[4] : '';
218+
$class = !empty($field[5]) ? $field[5] : '';
219+
$buffer .= HtmlBootstrap5::checkbox($name, $value, $defaultValue, $class);
220+
break;
221+
case "radio":
222+
$group = !empty($field[4]) ? $field[4] : '';
223+
$defaultValue = !empty($field[5]) ? $field[5] : '';
224+
$class = !empty($field[6]) ? $field[6] : '';
225+
$buffer .= HtmlBootstrap5::radio($name, $group, $value, $defaultValue, $class) . "&nbsp;" . htmlentities($title);
226+
break;
227+
case "hidden":
228+
$buffer .= '<input type="hidden" name="' . $name . '" value="' . htmlspecialchars($value) . '" id="' . $name . '"/>';
229+
break;
230+
case "file":
231+
$size = !empty($field[4]) ? $field[4] : '';
232+
$buffer .= '<input style="width:100%;" type="' . $type . '" name="' . $name . '" size="' . $size . '" id="' . $name . '"/>';
233+
break;
234+
case "multifile":
235+
$buffer .= HtmlBootstrap5::multiFileUpload($name);
236+
break;
237+
}
238+
if (!empty($title) && "static" !== $type && "hidden" !== $type) {
239+
$buffer .= "</label>";
240+
}
241+
$buffer .= "</div></div>";
242+
}
243+
244+
if (null !== $action) {
245+
$buffer .= $form->close($submitTitle);
246+
}
247+
return $buffer;
248+
}
249+
51250
/**
52251
* Creates a complex form where each section can have
53252
* a different number of columns.

system/classes/html/GlobalAttributes.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ trait GlobalAttributes
4949
* used by Angular JS.
5050
*
5151
* @param string $name
52-
* @param Mixed $value
52+
* @param mixed $value
5353
* @return this
5454
*/
5555
public function setAttribute($name, $value)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
/**
4+
* Start a multipart upload for bucket defined in file.adapters.s3.bucket
5+
* If you need to change parameters such as s3 bucket/key/etc,
6+
* you should make a new endpoint in your module that accepts your own values
7+
*/
8+
function ajax_multipart_POST(Web $w)
9+
{
10+
$w->setLayout(null);
11+
12+
$body = json_decode(file_get_contents('php://input'), true);
13+
14+
$filename = $body["filename"];
15+
$prefix = Config::get("file.adapters.s3.options.directory");
16+
$key = $prefix . "/" . hash("md5", $filename);
17+
18+
$mime = $body["mime"];
19+
20+
$upload = FileMultipartUploadService::getInstance($w)
21+
->startMultipart(
22+
$key,
23+
$mime,
24+
Config::get("file.adapters.s3.bucket")
25+
);
26+
27+
$w->out(json_encode(["id" => $upload->id]));
28+
}
29+
30+
/**
31+
* Abort a multipart upload
32+
*/
33+
function ajax_multipart_DELETE(Web $w)
34+
{
35+
$w->setLayout(null);
36+
37+
[$upload_id] = $w->pathMatch("id");
38+
39+
$obj = FileMultipartUploadService::getInstance($w)
40+
->getObject("FileS3Object", $upload_id);
41+
42+
FileMultipartUploadService::getInstance($w)
43+
->abortMultipart($obj);
44+
45+
$obj->delete();
46+
47+
$w->out((new JsonResponse())->setStatus(200));
48+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/**
4+
* Mark a multipart upload as done
5+
*/
6+
function ajax_done_POST(Web $w)
7+
{
8+
$w->setLayout(null);
9+
10+
[$upload_id] = $w->pathMatch("id");
11+
12+
// TODO: check if user has permission to upload to this
13+
// they already must have file_upload to get to this route
14+
// but do they have permission to upload to this upload_id?
15+
16+
$obj = FileMultipartUploadService::getInstance($w)
17+
->getObject("FileS3Object", $upload_id);
18+
19+
$attachment = FileMultipartUploadService::getInstance($w)
20+
->finishMultipart($obj);
21+
22+
$obj->delete();
23+
24+
$w->out(
25+
(new JsonResponse())
26+
->setSuccessfulResponse(
27+
"Success",
28+
!empty($attachment->id) ? ["id" => $attachment->id] : []
29+
)
30+
);
31+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
/**
4+
* Receive a presigned UploadPart url for multipart uploads
5+
*/
6+
function ajax_part_POST(Web $w)
7+
{
8+
$w->setLayout(null);
9+
10+
$body = json_decode(file_get_contents('php://input'), true);
11+
12+
$upload_id = $body["id"];
13+
$partNumber = $body["part"];
14+
$length = $body["length"];
15+
$md5 = $body["md5"];
16+
17+
// TODO: check if user has permission to upload to this
18+
19+
$obj = FileMultipartUploadService::getInstance($w)
20+
->getObject("FileS3Object", $upload_id);
21+
22+
$presigned = FileMultipartUploadService::getInstance($w)
23+
->getPresignedUploadPart($obj, $partNumber, $length, $md5);
24+
25+
$w->out(json_encode(["endpoint" => $presigned]));
26+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script setup lang="ts">
2+
import { computed } from "vue";
3+
4+
const props = defineProps<{
5+
file: File
6+
}>();
7+
8+
const src = computed(() => URL.createObjectURL(props.file));
9+
</script>
10+
11+
<template>
12+
<img v-if="props.file.type.startsWith('image')" :src="src" :alt="props.file.name" height="200" class="img-thumbnail h-100"/>
13+
<audio v-else-if="props.file.type.startsWith('audio')" :src="src" :title="props.file.name" controls></audio>
14+
<video v-else-if="props.file.type.startsWith('video')" :src="src" :title="props.file.name" height="200" controls></video>
15+
<a v-else rel="noreferer" target="_blank" :href="src">{{ props.file.name }}</a>
16+
</template>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { createApp } from 'vue';
2+
3+
import MultipartUploaderComponent from "./MultipartUploaderComponent.vue";
4+
5+
window.addEventListener("load", () => {
6+
createApp({
7+
components: {
8+
MultipartUploaderComponent,
9+
}
10+
}).mount("#multipart-uploader");
11+
})

0 commit comments

Comments
 (0)