diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css index e2120f71909cc711027c791bd0573437bdb04adb..9e8480010a95f50380a6751361e99bb47420151d 100644 --- a/assets/default/css/theme.css +++ b/assets/default/css/theme.css @@ -1263,3 +1263,22 @@ img.avatar { border-bottom: 2px solid #ddd; border-bottom-color: rgba(0,0,0,0.1); } + +.freetext-files { + padding: 10px; + margin-top: 10px; + border: 1px dotted #ddd; + border-radius: 4px; + background-color: #f5f5f5; +} +.freetext-files .file { + margin-right: 10px; + display: inline-block; + width: 48%; + padding-top: 0.2em; +} +.freetext-files .title { + font-weight: bold; + margin-bottom: 0.3em; + font-size: 1.1em; +} diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php index 8fedabe4c8158ae3917826e6e5956f93e764c302..39e73162b9c675f154a554ca3f11abb9bc7dd9e2 100644 --- a/include/class.dynamic_forms.php +++ b/include/class.dynamic_forms.php @@ -681,15 +681,19 @@ class DynamicFormField extends VerySimpleModel { function setConfiguration($vars, &$errors=array()) { $config = array(); foreach ($this->getConfigurationForm($vars)->getFields() as $name=>$field) { - $config[$name] = $field->to_php($field->getClean()); + $config[$name] = $field->to_config($field->getClean()); $errors = array_merge($errors, $field->errors()); } - if (count($errors) === 0) - $this->set('configuration', JsonDataEncoder::encode($config)); + if (count($errors)) + return false; + + // See if field impl. need to save or override anything + $config = $this->getImpl()->to_config($config); + $this->set('configuration', JsonDataEncoder::encode($config)); $this->set('hint', Format::sanitize($vars['hint'])); - return count($errors) === 0; + return true; } function isDeletable() { @@ -902,14 +906,26 @@ class DynamicFormField extends VerySimpleModel { } function delete() { - // Don't really delete form fields as that will screw up the data + // Don't really delete form fields with data as that will screw up the data // model. Instead, just drop the association with the form which // will give the appearance of deletion. Not deleting means that // the field will continue to exist on form entries it may already // have answers on, but since it isn't associated with the form, it // won't be available for new form submittals. $this->set('form_id', 0); - $this->save(); + + $impl = $this->getImpl(); + + // Trigger db_clean so the field can do house cleaning + $impl->db_cleanup(true); + + // Short-circuit deletion if the field has data. + if ($impl->hasData()) + return $this->save(); + + // Delete the field for realz + parent::delete(); + } function save($refetch=false) { diff --git a/include/class.forms.php b/include/class.forms.php index 59ea16f7b47d6a08f080ab2c1b61abc3bd24f3fa..f5f3ce86f0df34a6f6e3a0609d6ca64c81bb1e0a 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -554,6 +554,18 @@ class FormField { return $value; } + /** + * + * to_config + * + * Transform the data from the value to config form (as determined by + * field). By default to_php is used at the base level + * + */ + function to_config($value) { + return $this->to_php($value); + } + /** * to_database * @@ -596,7 +608,7 @@ class FormField { * cleaned as well. This hook allows fields to participate when the data * for a field is cleaned up. */ - function db_cleanup() { + function db_cleanup($field=false) { } /** @@ -2392,6 +2404,14 @@ class FileUploadField extends FormField { return JsonDataParser::decode($value); } + function to_config($value) { + + if ($value && is_array($value)) + $value = array_values($value); + + return $value; + } + function display($value) { $links = array(); foreach ($this->getFiles() as $f) { @@ -2410,7 +2430,7 @@ class FileUploadField extends FormField { return implode(', ', $files); } - function db_cleanup() { + function db_cleanup($field=false) { // Delete associated attachments from the database, if any $this->getFiles(); if (isset($this->attachments)) { @@ -3100,6 +3120,7 @@ class FileUploadWidget extends Widget { $mimetypes = array_filter($config['__mimetypes'], function($t) { return strpos($t, '/') !== false; } ); + $maxfilesize = ($config['size'] ?: 1048576) / 1048576; $files = $F = array(); $new = array_fill_keys($this->field->getClean(), 1); foreach ($attachments as $f) { @@ -3142,7 +3163,7 @@ class FileUploadWidget extends Widget { allowedfiletypes: <?php echo JsonDataEncoder::encode( $mimetypes); ?>, maxfiles: <?php echo $config['max'] ?: 20; ?>, - maxfilesize: <?php echo ($config['size'] ?: 1048576) / 1048576; ?>, + maxfilesize: <?php echo $maxfilesize; ?>, name: '<?php echo $name; ?>[]', files: <?php echo JsonDataEncoder::encode($files); ?> });}); @@ -3187,6 +3208,7 @@ class FileUploadError extends Exception {} class FreeTextField extends FormField { static $widget = 'FreeTextWidget'; + protected $attachments; function getConfigurationOptions() { return array( @@ -3195,6 +3217,12 @@ class FreeTextField extends FormField { 'label'=>__('Content'), 'required'=>true, 'default'=>'', 'hint'=>__('Free text shown in the form, such as a disclaimer'), )), + 'attachments' => new FileUploadField(array( + 'id'=>'attach', + 'label' => __('Attachments'), + 'name'=>'files', + 'configuration' => array('extensions'=>'') + )), ); } @@ -3205,12 +3233,49 @@ class FreeTextField extends FormField { function isBlockLevel() { return true; } + + /* utils */ + + function to_config($config) { + + $keepers = array(); + if ($config && isset($config['attachments'])) + foreach ($config['attachments'] as $fid) + $keepers[] = $fid; + + $this->getAttachments()->keepOnlyFileIds($keepers); + + return $config; + } + + function db_cleanup($field=false) { + + if ($field && $this->getFiles()) + $this->getAttachments()->deleteAll(); + } + + function getAttachments() { + + if (!isset($this->attachments)) + $this->attachments = GenericAttachments::forIdAndType($this->get('id'), 'I'); + + return $this->attachments; + } + + function getFiles() { + + if (!($attachments = $this->getAttachments())) + return array(); + + return $attachments->all(); + } + } class FreeTextWidget extends Widget { function render($options=array()) { $config = $this->field->getConfiguration(); - ?><div class=""><?php + ?><div class="thread-body" style="padding:0"><?php if ($label = $this->field->getLocal('label')) { ?> <h3><?php echo Format::htmlchars($label); @@ -3225,6 +3290,21 @@ class FreeTextWidget extends Widget { echo Format::viewableImages($config['content']); ?></div> </div> <?php + if (($attachments=$this->field->getFiles())) { ?> + <section class="freetext-files"> + <div class="title"><?php echo __('Related Resources'); ?></div> + <?php foreach ($attachments as $attach) { ?> + <div class="file"> + <a href="<?php echo $attach->file->getDownloadUrl(); ?>" + target="_blank" download="<?php echo $attach->file->getDownloadUrl(); + ?>" class="truncate no-pjax"> + <i class="icon-file"></i> + <?php echo Format::htmlchars($attach->getFilename()); ?> + </a> + </div> + <?php } ?> + </section> + <?php } } } diff --git a/include/client/templates/dynamic-form.tmpl.php b/include/client/templates/dynamic-form.tmpl.php index bcfc9ada252471feb406527b69b274ff945e183f..7a23a4546e734458d2ec14c491da0db26788e105 100644 --- a/include/client/templates/dynamic-form.tmpl.php +++ b/include/client/templates/dynamic-form.tmpl.php @@ -18,7 +18,7 @@ continue; ?> <tr> - <td colspan="2" style="padding-top:8px;"> + <td colspan="2" style="padding-top:10px;"> <?php if (!$field->isBlockLevel()) { ?> <label for="<?php echo $field->getFormName(); ?>"><span class="<?php if ($field->isRequiredForUsers()) echo 'required'; ?>"> diff --git a/scp/css/scp.css b/scp/css/scp.css index ad7c976ade5c91a161e2b1e345801a253f5fa85d..2015ba20d0cf0cdacdfb0c14d5eb18aeaebf80ff 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -2404,3 +2404,22 @@ td.indented { width: calc(100% - 95px); line-height: 1.4em; } + +.freetext-files { + padding: 10px; + margin-top: 10px; + border: 1px dotted #ddd; + border-radius: 4px; + background-color: #f5f5f5; +} +.freetext-files .file { + margin-right: 10px; + display: inline-block; + width: 48%; + padding-top: 0.2em; +} +.freetext-files .title { + font-weight: bold; + margin-bottom: 0.3em; + font-size: 1.1em; +}