]> git.agnieray.net Git - galette.git/commitdiff
Add file-type support for dynamic fields; fixes #727
authorGuillaume Rousse <guillomovitch@gmail.com>
Sat, 27 Jul 2013 17:02:50 +0000 (19:02 +0200)
committerJohan Cwiklinski <johan@x-tnd.be>
Sun, 19 Jan 2014 08:18:07 +0000 (09:18 +0100)
Three form elements are actually used, for more control:
- a standard file upload element
- a read-only text field displays current field value
- a checkbox allows to delete current value

Additional checking of  error occuring while proceeding dynamic fields
allow to provide feedback when a file is too large, for instance.

12 files changed:
galette/ajouter_adherent.php
galette/ajouter_contribution.php
galette/ajouter_transaction.php
galette/config/paths.inc.php
galette/get_file.php [new file with mode: 0644]
galette/install/steps/check.php
galette/lib/Galette/DynamicFieldsTypes/File.php [new file with mode: 0644]
galette/lib/Galette/Entity/DynamicFields.php
galette/self_adherent.php
galette/templates/default/display_dynamic_fields.tpl
galette/templates/default/edit_dynamic_fields.tpl
galette/templates/default/editer_champ.tpl

index dae652c591f10a837eb925d6756c0e0bc8eb3fd5..00a09563c6f5e414eb42c877b5f1701051b40699 100644 (file)
@@ -128,9 +128,18 @@ $real_requireds = array_diff(array_keys($required), array_keys($disabled));
 
 // Validation
 if ( isset($_POST[array_shift($real_requireds)]) ) {
-    $adherent['dyn'] = $dyn_fields->extractPosted($_POST, $disabled);
+    $adherent['dyn'] = $dyn_fields->extractPosted($_POST, $_FILES, $disabled, $member->id);
+    $dyn_fields_errors = $dyn_fields->getErrors();
+    if ( count($dyn_fields_errors) > 0 ) {
+        $error_detected = array_merge($error_detected, $dyn_fields_errors);
+    }
+    // regular fields
     $valid = $member->check($_POST, $required, $disabled);
-    if ( $valid === true ) {
+    if ( $valid !== true ) {
+        $error_detected = array_merge($error_detected, $valid);
+    }
+
+    if ( count($error_detected ) == 0) {
         //all goes well, we can proceed
 
         $new = false;
@@ -295,9 +304,6 @@ if ( isset($_POST[array_shift($real_requireds)]) ) {
             //something went wrong :'(
             $error_detected[] = _T("An error occured while storing the member.");
         }
-    } else {
-        //hum... there are errors :'(
-        $error_detected = $valid;
     }
 
     if ( count($error_detected) == 0 ) {
index b770d74cf813740ed6d4300c47a1824945455f54..1f96c660d4c7bc0fdd48c8e253a46fecb2e3c7ba 100644 (file)
@@ -126,10 +126,19 @@ if ( $type_selected && !($id_adh || $id_cotis) ) {
 // Validation
 $contribution['dyn'] = array();
 if ( isset($_POST['valid']) ) {
-    $contribution['dyn'] = $dyn_fields->extractPosted($_POST, array());
-
+    // dynamic fields
+    $contribution['dyn'] = $dyn_fields->extractPosted($_POST, $_FILES, array(), $id_adh);
+    $dyn_fields_errors = $dyn_fields->get_errors();
+    if ( $count($dyn_fields_errors) > 0 ) {
+        $error_detected = array_merge($error_detected, $dyn_fields_errors);
+    }
+    // regular fields
     $valid = $contrib->check($_POST, $required, $disabled);
-    if ( $valid === true ) {
+    if ( $valid !== true ) {
+        $error_detected = array_merge($error_detected, $valid);
+    }
+
+    if ( count($error_detected ) == 0) {
         //all goes well, we can proceed
         if ( $contrib->isCotis() ) {
             // Check that membership fees does not overlap
@@ -220,9 +229,6 @@ if ( isset($_POST['valid']) ) {
                 $error_detected[] = _T("An error occured while storing the contribution.");
             }
         }
-    } else {
-        //hum... there are errors :'(
-        $error_detected = $valid;
     }
 
     if ( count($error_detected) == 0 ) {
index d6c01b9faf8c380e9e439d2b772d0841dca34ac0..05aff1ae930c65ed13db40635738fc0964fc4ad4 100644 (file)
@@ -106,10 +106,19 @@ if ( $trans_id != '' ) {
 $transaction['dyn'] = array();
 
 if ( isset($_POST['valid']) ) {
-    $transaction['dyn'] = $dyn_fields->extractPosted($_POST, array());
-
+    // dynamic fields
+    $transaction['dyn'] = $dyn_fields->extractPosted($_POST, $_FILES, array(), $id_adh);
+    $dyn_fields_errors = $dyn_fields->get_errors();
+    if ( $count($dyn_fields_errors) > 0 ) {
+        $error_detected = array_merge($error_detected, $dyn_fields_errors);
+    }
+    // regular fields
     $valid = $trans->check($_POST, $required, $disabled);
-    if ( $valid === true ) {
+    if ( $valid !== true ) {
+        $error_detected = array_merge($error_detected, $valid);
+    }
+
+    if ( count($error_detected ) == 0) {
         //all goes well, we can proceed
         $new = false;
         if ( $trans->id == '' ) {
index e96157bb4860b8bafbf39d961055c770dc50e495..4a84c98af0c88cdcc5d865da7f3c2bbb33d73838 100644 (file)
@@ -115,6 +115,9 @@ if ( !defined('GALETTE_PHOTOS_PATH') ) {
 if ( !defined('GALETTE_ATTACHMENTS_PATH') ) {
     define('GALETTE_ATTACHMENTS_PATH', GALETTE_ROOT . 'attachments/');
 }
+if ( !defined('GALETTE_FILES_PATH') ) {
+    define('GALETTE_FILES_PATH', GALETTE_ROOT . 'files/');
+}
 if ( !defined('GALETTE_TEMPIMAGES_PATH') ) {
     define('GALETTE_TEMPIMAGES_PATH', GALETTE_ROOT . 'tempimages/');
 }
diff --git a/galette/get_file.php b/galette/get_file.php
new file mode 100644 (file)
index 0000000..2ffb3c3
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Get an exported file
+ *
+ * PHP version 5
+ *
+ * Copyright © 2013-2014 The Galette Team
+ *
+ * This file is part of Galette (http://galette.tuxfamily.org).
+ *
+ * Galette is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Galette is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Galette. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Main
+ * @package   Galette
+ *
+ * @author    Guillaume Rousse <guillomovitch@gmail.com>
+ * @copyright 2013-2014 The Galette Team
+ * @license   http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
+ * @version   SVN: $Id$
+ * @link      http://galette.tuxfamily.org
+ * @since     Available since 0.7dev - 2013-07-27
+ */
+
+use Analog\Analog as Analog;
+
+/** @ignore */
+require_once 'includes/galette.inc.php';
+
+if ( !isset($_GET['file']) ) {
+    Analog::log(
+        'No requested file',
+        Analog::INFO
+    );
+    header("HTTP/1.1 500 Internal Server Error");
+    die();
+}
+
+$file = $_GET['file'];
+$name = $_GET['name'];
+
+//Exports main contain user confidential data, they're accessible only for
+//admins or staff members
+if ( $login->isAdmin() || $login->isStaff() ) {
+
+    if (file_exists(GALETTE_FILES_PATH . $file) ) {
+        // try to identify MIME type
+        if (function_exists("finfo_open")) {
+            // require PECL fileinfo
+            $finfo = finfo_open(FILEINFO_MIME_TYPE);
+            $type = finfo_file($finfo, $file);
+        } else {
+            // deprecated
+            $type = mime_content_type($file);
+        }
+        header('Content-Type: ' . $type);
+        header('Content-Disposition: attachment; filename="' . $name . '";');
+        header('Pragma: no-cache');
+        readfile(GALETTE_FILES_PATH . $file);
+    } else {
+        Analog::log(
+            'A request has been made to get an exported file named `' .
+            $file .'` that does not exists.',
+            Analog::WARNING
+        );
+        header('HTTP/1.0 404 Not Found');        
+    }
+} else {
+    Analog::log(
+        'A non authorized person asked to retrieve exported file named `' .
+        $file . '`. Access has not been granted.',
+        Analog::WARNING
+    );
+    header('HTTP/1.0 403 Forbidden');
+}
index 89435fff113cf343d46b8076aa50785e9bcdde39..acbc22230d5892730346fc08e5b1f120e1fe6978 100644 (file)
@@ -73,7 +73,8 @@ $files_need_rw = array (
     _T("Exports")           => GALETTE_EXPORTS_PATH,
     _T("Imports")           => GALETTE_IMPORTS_PATH,
     _T("Logs")              => GALETTE_LOGS_PATH,
-    _T("Attachments")       => GALETTE_ATTACHMENTS_PATH
+    _T("Attachments")       => GALETTE_ATTACHMENTS_PATH,
+    _T("Files")             => GALETTE_FILES_PATH
 );
 
 $files_perms_class = $class . 'ok';
diff --git a/galette/lib/Galette/DynamicFieldsTypes/File.php b/galette/lib/Galette/DynamicFieldsTypes/File.php
new file mode 100644 (file)
index 0000000..e6a3241
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * File field type
+ *
+ * PHP version 5
+ *
+ * Copyright © 2013-2014 The Galette Team
+ *
+ * This file is part of Galette (http://galette.tuxfamily.org).
+ *
+ * Galette is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Galette is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Galette. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  DynamicFields
+ * @package   Galette
+ *
+ * @author    Guillaume Rousse <guillomovitch@gmail.com>
+ * @copyright 2013-2014 The Galette Team
+ * @license   http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
+ * @version   SVN: $Id$
+ * @link      http://galette.tuxfamily.org
+ * @since     Available since 0.8 - 2013-07-27
+ */
+
+namespace Galette\DynamicFieldsTypes;
+
+use Analog\Analog as Analog;
+
+/**
+ * File field type
+ *
+ * @name      File
+ * @category  DynamicFields
+ * @package   Galette
+ *
+ * @author    Guillaume Rousse <guillomovitch@gmail.com>
+ * @copyright 2013-2014 The Galette Team
+ * @license   http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
+ * @link      http://galette.tuxfamily.org
+ */
+
+class File extends DynamicFieldType
+{
+    /**
+     * Default constructor
+     *
+     * @param int $id Optionnal field id to load data
+     */
+    public function __construct($id = null)
+    {
+        parent::__construct($id);
+        $this->has_data = true;
+        $this->has_size = true;
+    }
+
+    /**
+     * Get field type name
+     *
+     * @return String
+     */
+    public function getTypeName()
+    {
+        return _T("file");
+    }
+
+    /**
+     * Load field
+     *
+     * @return void
+     */
+    public function load()
+    {
+        parent::load();
+        $this->repeat = 1;
+    }
+}
index 60b3ecd68a515b6645a20a78b8ce44b38a1f532c..816299874bad5679021e762ed97263b813068b28 100644 (file)
@@ -46,6 +46,7 @@ use Galette\DynamicFieldsTypes\Line as Line;
 use Galette\DynamicFieldsTypes\Choice as Choice;
 use Galette\DynamicFieldsTypes\Date as Date;
 use Galette\DynamicFieldsTypes\Boolean as Boolean;
+use Galette\DynamicFieldsTypes\File as File;
 use Galette\DynamicFieldsTypes\DynamicFieldType as DynamicFieldType;
 
 /**
@@ -77,11 +78,15 @@ class DynamicFields
     const DATE = 4;
     /** Boolean field (checkbox) */
     const BOOLEAN = 5;
+    /** File field (upload) */
+    const FILE = 6;
 
     const PERM_ALL = 0;
     const PERM_STAFF = 2;
     const PERM_ADM = 1;
 
+    const DEFAULT_MAX_FILE_SIZE = 1024;
+
     private $_id;
     private $_index;
     private $_name;
@@ -94,6 +99,8 @@ class DynamicFields
     private $_perms_names;
     private $_forms_names;
 
+    private $_errors = array();
+
     /**
      * Default constructor
      *
@@ -111,7 +118,8 @@ class DynamicFields
             self::LINE      => _T("single line"),
             self::CHOICE    => _T("choice"),
             self::DATE      => _T("date"),
-            self::BOOLEAN   => _T("boolean")
+            self::BOOLEAN   => _T("boolean"),
+            self::FILE      => _T("file")
         );
 
         //Permissions names
@@ -321,6 +329,7 @@ class DynamicFields
                         || (int)$r['field_type'] === self::TEXT
                         || (int)$r['field_type'] === self::DATE
                         || (int)$r['field_type'] === self::BOOLEAN
+                        || (int)$r['field_type'] === self::FILE
                     ) {
                         $r['field_repeat'] = 1;
                     }
@@ -414,11 +423,12 @@ class DynamicFields
      * Extract posted values for dynamic fields
      *
      * @param array $post     Array containing the posted values
+     * @param array $files    Array containing the posted files
      * @param array $disabled Array with fields that are discarded as key
      *
      * @return array
      */
-    public function extractPosted($post, $disabled)
+    public function extractPosted($post, $files, $disabled, $member_id)
     {
         if ( $post != null ) {
             $dfields = array();
@@ -439,11 +449,88 @@ class DynamicFields
                         if ( is_numeric($field_id)
                             && is_numeric($val_index)
                         ) {
-                            $dfields[$field_id][$val_index] = $value;
+                            if ((int) $descriptions[$field_id]['field_type'] == self::FILE) {
+                                # delete checkbox
+                                $filename = sprintf(
+                                    'member_%d_field_%d_value_%d',
+                                    $member_id,
+                                    $field_id,
+                                    $val_index
+                                );
+                                unlink(GALETTE_FILES_PATH . $filename);
+                                $dfields[$field_id][$val_index] = '';
+                            } else {
+                                # actual field value
+                                $dfields[$field_id][$val_index] = $value;
+                            }
                         }
                     }
                 }
             }
+
+            while ( list($key, $value) = each($files) ) {
+                // if the field is disabled, skip it
+                if (isset($disabled[$key]) ) {
+                    continue;
+                }
+
+                if (substr($key, 0, 11) != 'info_field_') {
+                    continue;
+                }
+
+                list($field_id, $val_index) = explode('_', substr($key, 11));
+                if (! is_numeric($field_id) || ! is_numeric($val_index)) {
+                    continue;
+                }
+
+                if ($files[$key]['error'] !== UPLOAD_ERR_OK ) {
+                    Analog::log("file upload error", Analog::ERROR);
+                    continue;
+                }
+
+                $tmp_filename = $files[$key]['tmp_name'];
+                if ( $tmp_filename == '' ) {
+                    Analog::log("empty temporary filename", Analog::ERROR);
+                    continue;
+                }
+
+                if (! is_uploaded_file($tmp_filename) ) {
+                    Analog::log("not an uploaded file", Analog::ERROR);
+                    continue;
+                }
+
+                $max_size =
+                    $descriptions[$field_id]['field_size'] === 'NULL' ?
+                    self::DEFAULT_MAX_FILE_SIZE            * 1024:
+                    $descriptions[$field_id]['field_size'] * 1024;
+                if ($files[$key]['size'] > $max_size ) {
+                    Analog::log(
+                        "file too large: " . $files[$key]['size'] . " Ko, vs $max_size Ko allowed",
+                        Analog::ERROR
+                    );
+                    $this->_errors[] = preg_replace(
+                        '|%d|',
+                        $max_size,
+                        _T("File is too big. Maximum allowed size is %dKo")
+                    );
+                    continue;
+                }
+
+                $new_filename = sprintf(
+                    'member_%d_field_%d_value_%d',
+                    $member_id,
+                    $field_id,
+                    $val_index
+                );
+                Analog::log("new file: $new_filename", Analog::DEBUG);
+
+                move_uploaded_file(
+                    $tmp_filename,
+                    GALETTE_FILES_PATH . $new_filename
+                );
+                $dfields[$field_id][$val_index] = $files[$key]['name'];
+            }
+
             return $dfields;
         }
     }
@@ -620,6 +707,9 @@ class DynamicFields
         case self::BOOLEAN:
             $df = new \Galette\DynamicFieldsTypes\Boolean($id);
             break;
+        case self::FILE:
+            $df = new \Galette\DynamicFieldsTypes\File($id);
+            break;
         default:
             throw new \Exception('Unknow field type ' . $t . '!');
             break;
@@ -712,4 +802,14 @@ class DynamicFields
         }
         return $duplicated;
     }
+
+    /**
+     * Get errors
+     *
+     * @return array
+     */
+    public function getErrors()
+    {
+        return $this->_errors;
+    }
 }
index d2a0106135dd743bc435fc4a5af0de1a572f9b5a..877df415734e0a4ef76a0a57e206449a094e7c4f 100644 (file)
@@ -78,11 +78,20 @@ $has_register = false;
 
 $fields = Adherent::getDbFields();
 
-// checking posted values for 'regular' fields
 if ( isset($_POST["nom_adh"]) ) {
-    $adherent['dyn'] = $dyn_fields->extractPosted($_POST, $disabled);
+    // dynamic fields
+    $adherent['dyn'] = $dyn_fields->extractPosted($_POST, $_FILES, $disabled, $member->id);
+    $dyn_fields_errors = $dyn_fields->get_errors();
+    if ( $count($dyn_fields_errors) > 0 ) {
+        $error_detected = array_merge($error_detected, $dyn_fields_errors);
+    }
+    // regular fields
     $valid = $member->check($_POST, $required, $disabled);
-    if ( $valid === true ) {
+    if ( $valid !== true ) {
+        $error_detected = array_merge($error_detected, $valid);
+    }
+
+    if ( count($error_detected ) == 0) {
         //all goes well, we can proceed
         $store = $member->store();
         if ( $store === true ) {
@@ -202,9 +211,6 @@ if ( isset($_POST["nom_adh"]) ) {
             //something went wrong :'(
             $error_detected[] = _T("An error occured while storing the member.");
         }
-    } else {
-        //hum... there are errors :'(
-        $error_detected = $valid;
     }
 } elseif ( isset($_POST["update_lang"]) && $_POST["update_lang"] == 1 ) {
     while ( list($key, $properties) = each($fields) ) {
index bfd21650f6b19a2bed7c902554eb64a1a293c9a0..ec50ddc66641026a2dfdacc77fd420a6efa34dcd 100644 (file)
                 {if $smarty.section.fieldLoop.index_prev > 0}<br />{/if}
                 <a href="{$data.dyn[$field.field_id][$smarty.section.fieldLoop.index]}" target="_blank" title="{_T string="Open '%s' in a new window" replace=$data.dyn[$field.field_id][$smarty.section.fieldLoop.index] pattern="/%s/"}">{$data.dyn[$field.field_id][$smarty.section.fieldLoop.index]}</a>
             {else if isset($data.dyn[$field.field_id][$smarty.section.fieldLoop.index]) and $field.field_type eq 5}
-        {if $data.dyn[$field.field_id][$smarty.section.fieldLoop.index]}
-            {_T string="Yes"}
-        {else}
-            {_T string="No"}
-        {/if}
-        <br/>
+               {if $data.dyn[$field.field_id][$smarty.section.fieldLoop.index]}
+                   {_T string="Yes"}
+               {else}
+                   {_T string="No"}
+               {/if}
+               <br/>
+            {else if isset($data.dyn[$field.field_id][$smarty.section.fieldLoop.index]) and $field.field_type eq 6}
+            <a href="get_file.php?file=member_{$member->id}_field_{$field.field_id}_value_{$smarty.section.fieldLoop.index}&name={$data.dyn[$field.field_id][$smarty.section.fieldLoop.index]}">{$data.dyn[$field.field_id][$smarty.section.fieldLoop.index]}</a><br/>
             {else}
             {$data.dyn[$field.field_id][$smarty.section.fieldLoop.index]|nl2br|default:"&nbsp;"}<br/>
             {/if}
index ce35caba49e222abe42dbd11133ec7e72f2faa0d..c350ab4359dc55d9e4366019864ea9a4ed9a2622 100644 (file)
                 {if isset($disabled.dyn[$field.field_id])} {$disabled.dyn[$field.field_id]}{/if}
                 {if $field.field_required eq 1} required{/if}
             />
+        {elseif $field.field_type eq 6}
+            {_T string="new"}: <input type="file" name="info_field_{$field.field_id}_{$smarty.section.fieldLoop.index}" id="info_field_{$field.field_id}_{$smarty.section.fieldLoop.index}_{$count}_new"
+                {if isset($disabled.dyn[$field.field_id])} {$disabled.dyn[$field.field_id]}{/if}
+                {if $field.field_required eq 1} required{/if}
+            />
+            {_T string="current"}: <input type="text" name="info_field_{$field.field_id}_{$smarty.section.fieldLoop.index}" id="info_field_{$field.field_id}_{$smarty.section.fieldLoop.index}_{$count}_current" disabled
+                value="{if isset($data.dyn[$field.field_id][$smarty.section.fieldLoop.index])}{$data.dyn[$field.field_id][$smarty.section.fieldLoop.index]|escape}{/if}"
+            />
+            {_T string="delete"}: <input type="checkbox" name="info_field_{$field.field_id}_{$smarty.section.fieldLoop.index}" id="info_field_{$field.field_id}_{$smarty.section.fieldLoop.index}_{$count}_delete"
+                {if !$data.dyn[$field.field_id][$smarty.section.fieldLoop.index]}disabled{/if}
+                onclick="this.form.info_field_{$field.field_id}_{$smarty.section.fieldLoop.index}_{$count}_new.disabled = this.checked;"
+            />
         {/if}
     {/section}
         </p>
index b149b9dc6282d6055791fb30a3ee191edb66d2c8..2afa1fb4ff0c559f8f60d84f81a95a17535ea49b 100644 (file)
             <p>
                 <label for="field_size" class="bline">{_T string="Size:"}</label>
                 <input type="text" name="field_size" id="field_size" value="{$df->getSize()}" size="3"/>
+               {if $df instanceof 'Galette\DynamicFieldsTypes\File'}
+                <span class="exemple">{_T string="Maximum file size, in Ko."}</span>
+               {else}
                 <span class="exemple">{_T string="Maximum number of characters."}</span>
+               {/if}
             </p>
 {/if}
 {if $df->isMultiValued()}