Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcus Nyeholt committed Jul 6, 2015
0 parents commit d1a5a0f
Show file tree
Hide file tree
Showing 23 changed files with 1,983 additions and 0 deletions.
27 changes: 27 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Copyright (c) 2011, Marcus Nyeholt
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.

Neither the name of the organisation nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# [Editable User Forms](https://packagist.org/packages/nyeholt/silverstripe-editableuserforms)

Allows users to re-edit submissions made from a userdefined form.

## Requirement

* SilverStripe 3.1.X
* [Userforms](https://github.com/silverstripe/silverstripe-userforms)
* [Dropzone](https://github.com/unclecheese/silverstripe-dropzone) for file uploads

## Getting Started

* Place the module under your root project directory.
* dev/build
* Create and configure an Editable User Defined Form page

## Overview

Aside from the normal User Defined Form options, the module provides several additional configuration options

### Config options

* ShowSubmittedList - display the list of complete submissions the user has put together
* ShowDraftList - display the list of in-complete submissions
* AllowEditingComplete - Whether users should be able to edit 'completed' submissions
* ShowSubmitButton - Whether the form should display the 'submit' button. If this button is _not_ displayed,
the form will automatically be 'submitted' (ie, marked as 'complete') when a user fills out all required fields
* ShowPreviewButton - show the 'preview' button - a read-only version of the form for printing out
* ShowDeleteButton - show the 'delete' button in form submission listings
* ShowButtonsOnTop - show buttons across the top of the form as well as the base
* LoadLastSubmission - loads the most recent 'draft' submission the user made when they land on the form page
* SubmitWarning - text to display to the user when they click the 'submit' button
* Workflow definition - if specified, a form submission will be sent to a workflow for approval

### Fields

* `EditableTextFieldWithDefault` - Provides a textbox field that can be pre-populated with a field value. For example,
`$Member.Email`
* `EditableEmailFieldWithDefault` - An email specific field extended from the TextFieldWithDefault
* `EditableMultiFileField` - Allows for the upload of one or more files from the frontend using Dropzone. Note: the
standard EditableFileField does NOT work for re-editable forms
* `FormSaveField` - inserts a 'save' button into the form for users to incrementally save as they go

### Workflow actions

* `SetPropertyAction` - A workflow action that sets a value on a data object field when triggered. Used for marking
form submissions as 'Complete'



7 changes: 7 additions & 0 deletions _config/editable_userforms.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
Name: submitted_forms
---
SubmittedForm:
better_buttons_enabled: 0
extensions:
- EditableSubmissionExtension
32 changes: 32 additions & 0 deletions code/editableformfields/EditableEmailFieldWithDefault.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/**
* An email field that allows a user to specify a default value
*
* @author Marcus Nyeholt <marcus@silverstripe.com.au>
*/
class EditableEmailFieldWithDefault extends EditableTextFieldWithDefault
{
static $singular_name = 'Email field with default';

static $plural_name = 'Email fields with default';

public function Icon() {
return 'userforms/images/editableemailfield.png';
}

function getFormField($fieldType='TextField') {
return parent::getFormField('EmailField');
}

public function getValidation() {
$options = array(
'email' => true
);

if($this->getSetting('MinLength')) $options['minlength'] = $this->getSetting('MinLength');
if($this->getSetting('MaxLength')) $options['maxlength'] = $this->getSetting('MaxLength');

return $options;
}
}
41 changes: 41 additions & 0 deletions code/editableformfields/EditableMaskedTextField.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

/**
* A text field that allows a user to specify a default value
*
* @author Marcus Nyeholt <marcus@silverstripe.com.au>
*/
class EditableMaskedTextField extends EditableTextFieldWithDefault
{
static $singular_name = 'Masked Text field';

static $plural_name = 'Masked Text fields';

public function Icon() {
return 'editableuserforms/images/maskedtextfield.png';
}

function getFieldConfiguration() {
$fields = parent::getFieldConfiguration();
// eventually replace hard-coded "Fields"?
$baseName = "Fields[$this->ID]";

$mask = $this->getSetting('TextMask');

$extraFields = new FieldSet(
new TextField($baseName . "[CustomSettings][TextMask]", _t('EditableMaskedTextFieldWithDefault.MASK', 'Mask'), $mask),
new LiteralField('MaskInstructions', _t('EditableMaskedTextFieldWithDefault.MASK_INSTRUCTIONS', 'Example: 99/99/9999 <ul><li>a - Represents an alpha character (A-Z,a-z)</li><li>9 - Represents a numeric character (0-9)</li><li>* - Represents an alphanumeric character (A-Z,a-z,0-9)</li></ul>'))
);

$fields->merge($extraFields);
return $fields;
}

function getFormField($properties = array()) {
$field = parent::getFormField('MaskedTextField');

$field->setInputMask($this->getSetting('TextMask'));

return $field;
}
}
81 changes: 81 additions & 0 deletions code/editableformfields/EditableMultiFileField.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

class EditableMultiFileField extends EditableFormField {
private static $singular_name = 'Multi File Upload Field';

private static $plural_name = 'Multi File Upload Fields';

public function Icon() {
return 'userforms/images/editablefilefield.png';
}

public function getFieldConfiguration() {
$fields = parent::getFieldConfiguration();

$folder = ($this->getSetting('Folder')) ? $this->getSetting('Folder') : null;

$tree = UserformsTreeDropdownField::create(
$this->getSettingName("Folder"),
_t('EditableUploadField.SELECTUPLOADFOLDER', 'Select upload folder'),
"Folder"
);

$tree->setValue($folder);

$fields->push($tree);

$multiple = $this->getSetting('MultipleUploads');

$cb = CheckboxField::create($this->getSettingName("MultipleUploads"), 'Allow multiple uploads');
$cb->setValue($multiple ? true : false);

$fields->push($cb);

return $fields;
}

public function getFormField() {
$field = FileAttachmentField::create($this->Name, $this->Title);

// $field = FileField::create($this->Name, $this->Title);

if($this->getSetting('Folder')) {
$folder = Folder::get()->byId($this->getSetting('Folder'));

if($folder) {
$field->setFolderName(
preg_replace("/^assets\//","", $folder->Filename)
);
}
}

if ($this->getSetting('MultipleUploads')) {
$field->setMultiple(true);
}

if ($this->Required) {
// Required validation can conflict so add the Required validation messages
// as input attributes
$errorMessage = $this->getErrorMessage()->HTML();
$field->setAttribute('data-rule-required', 'true');
$field->setAttribute('data-msg-required', $errorMessage);
}

return $field;
}

/**
* Return the value for the database, link to the file is stored as a
* relation so value for the field can be null.
*
* @return string
*/
public function getValueFromData($data) {
$val = isset($data[$this->Name]) ? $data[$this->Name] : null;
return is_array($val) ? implode(',', $val) : $val;
}

public function getSubmittedFormField() {
return new SubmittedMultiFileField();
}
}
145 changes: 145 additions & 0 deletions code/editableformfields/EditableSubmittedFormReportField.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?php

return;

/**
* Overridden class to format the export in the way we want
*
* @author Marcus Nyeholt <marcus@silverstripe.com.au>
*/
class EditableSubmittedFormReportField extends SubmittedFormReportField {

/**
* Overidden to exclude certain fields (IE Save) from the export. Additionally,
* we need to trim the result set of fields that don't exist in the field anymore, even if there
* are form submissions that have values for those non-existent fields.
*/
public function export($fileName = null) {

$separator = ",";

// Get the UserDefinedForm to export data from the URL
$SQL_ID = (isset($_REQUEST['id'])) ? Convert::raw2sql($_REQUEST['id']) : false;

if ($SQL_ID) {
$udf = DataObject::get_by_id("UserDefinedForm", $SQL_ID);
if ($udf) {
$fileName = str_replace(' ', '_', $udf->MenuTitle . '-' . date('Y-m-d_H.i.s') . '.csv');
// we'll limit submissions to only those that are completed.
$submissions = $udf->Submissions(); //'"SubmissionStatus" = \'Complete\'');
if ($submissions && $submissions->Count() > 0) {

// Get all the submission IDs (so we know what names/titles to get - helps for sites with many UDF's)
$inClause = array();
foreach ($submissions as $submission) {
$inClause[] = $submission->ID;
}

// Get the CSV header rows from the database

$tmp = DB::query("SELECT DISTINCT \"SubmittedFormField\".\"ID\", \"Name\", \"Title\"
FROM \"SubmittedFormField\"
LEFT JOIN \"SubmittedForm\" ON \"SubmittedForm\".\"ID\" = \"SubmittedFormField\".\"ParentID\"
WHERE \"SubmittedFormField\".\"ParentID\" IN (" . implode(',', $inClause) . ")
ORDER BY \"SubmittedFormField\".\"ID\"");

// Sort the Names and Titles from the database query into separate keyed arrays
$stored = array();
foreach ($tmp as $array) {
// only store if we haven't got this field already
// TODO Specific hack here to handle save fields in editable user forms
if (!isset($stored[$array['Name']]) && $array['Title'] != 'Save') {
$csvHeaderNames[] = $array['Name'];
$csvHeaderTitle[] = $array['Title'];
$stored[$array['Name']] = true;
}
}

// For every submission...
$i = 0;
foreach ($submissions as $submission) {

// Get the rows for this submission (One row = one form field)
$dataRow = $submission->Values();
$rows[$i] = array();

// For every row/field, get all the columns
foreach ($dataRow as $column) {

// If the Name of this field is in the $csvHeaderNames array, get an array of all the places it exists
if ($index = array_keys($csvHeaderNames, $column->Name)) {
if (is_array($index)) {

// Set the final output array for each index that we want to insert this value into
foreach ($index as $idx) {
$rows[$i][$idx] = $column->Value;
}
$rows[$i]['SubmissionStatus'] = $submission->SubmissionStatus;
$rows[$i]['Submitted'] = $submission->LastEdited;
}
}
}

$i++;
}

$csvHeaderTitle[] = "Status";
$csvHeaderTitle[] = "Submitted";

// CSV header row
$csvData = '"' . implode('","', $csvHeaderTitle) . '"' . "\n";

// For every row of data (one form submission = one row)
foreach ($rows as $row) {
// Loop over all the names we can use
for ($i = 0; $i < count($csvHeaderNames); $i++) {
if (!isset($row[$i]) || !$row[$i])
$csvData .= '"",'; // If there is no data for this column, output it as blank instead
else
$csvData .= '"' . str_replace('"', '\"', $row[$i]) . '",';
}
// Start a new row for each submission
$csvData .= '"' . $row['SubmissionStatus'] . '",' . '"' . $row['Submitted'] . '"' . "\n";
}
} else {
user_error("No submissions to export.", E_USER_ERROR);
}

if (class_exists('SS_HTTPRequest')) {
SS_HTTPRequest::send_file($csvData, $fileName)->output();
} else {
HTTPRequest::send_file($csvData, $fileName)->output();
}
} else {
user_error("'$SQL_ID' is a valid type, but we can't find a UserDefinedForm in the database that matches the ID.", E_USER_ERROR);
}
} else {
user_error("'$SQL_ID' is not a valid UserDefinedForm ID.", E_USER_ERROR);
}
}

function getSubmissions() {
return $this->customise(array(
'Submissions' => $this->Submissions(),
'CanDelete' => Permission::checkMember(null, 'Edit'),
))->renderWith(array('EditableSubmittedFormReportField'));
}

function Field() {
Requirements::css(SAPPHIRE_DIR . "/css/SubmittedFormReportField.css");
Requirements::javascript(THIRDPARTY_DIR . '/jquery-ui/jquery-ui-1.8rc3.custom.js');
Requirements::javascript("userforms/javascript/UserForm.js");
$val = Permission::checkMember(null, 'Edit');
return $this->customise(array(
'CanDelete' => Permission::checkMember(null, 'Edit'),
))->renderWith("EditableSubmittedFormReportField");
}

public function performReadonlyTransformation() {
$clone = clone $this;
return $clone;
}

}

?>
Loading

0 comments on commit d1a5a0f

Please sign in to comment.