Hi everybody!

I have a *great* problem in my module.

I created a form and I want to upload a picture.
During the validation I check its properties and the upload using file_save_upload.

then I add to the object node the field "foto" with the correct filepath.

BUT when the hook_insert is called, the $node has the field "foto" set to NULL?!?

can anyone explain me how i can retrieve this value (i want to insert it in my DB).

i tried to call "node_save($node)", but then the hook_insert is called twice!

thanks in advance to everybody!!

I added the full code below:

here is the full code:



<?php
// $Id: $

/**
 * @file
 * Gestire l'anagrafica degli atleti
 *
 */

/**
 * Implementation of hook_node_info().
 * This is a required node hook. This function describes the nodes provided by
 * this module.
 *
 * The required attributes are:
 * - "name" provides a human readable name for the node,
 * - "module" tells Drupal how the module's functions map to hooks (i.e. if the
 *   module is node_example_foo then node_example_foo_insert will be called
 *   when inserting the node).
 * - "description" provides a brief description of the node type, which is
 *   shown when a user accesses the "Create content" page for that node type.
 *
 * The other optional, attributes:
 * - "has_title" boolean that indicates whether or not this node type has a
 *   title field.
 * - "title_label": the label for the title field of this content type.
 * - "has_body": boolean that indicates whether or not this node type has a
 *   body field.
 * - "body_label": the label for the body field of this content type.
 * - "min_word_count": the minimum number of words for the body field to be
 *   considered valid for this content type.
 */
function atleti_node_info() {
    return array(
    'atleti' => array(
    'name' => t('Nuovo atleta'),
    'module' => 'atleti',
    'description' => t("Modulo per l'inserimento dell'anagrafica atleti."),
    'has_title' => false,
    'title_label' => t('Atleti'),
    'has_body' => false,
    'body_label' => t('Corpo atleti'),
    )
    );
}

/**
 * Implementation of hook_access().
 *
 * Node modules may implement node_access() to determine the operations
 * users may perform on nodes. This example uses a very common access pattern.
 */
function atleti_access($op="view_list", $node=null, $account=null) {
    // drupal_set_message("vengo chiamato.<br />OP: ". print_r($op, true)." <br />NODE: ". print_r($node, true). " <br />ACCOUNT: ". print_r($account, true));
    if ($op == 'create') {
        return user_access('crea nuovi', $account);
    }

    if ($op == 'update') {
        if (user_access('modifica tutti', $account) || (user_access('modifica propri', $account) && ($account->uid == $node->uid))) {
            return TRUE;
        }
    }

    if ($op == 'delete') {
        if (user_access('elimina tutti', $account) || (user_access('elimina propri', $account) && ($account->uid == $node->uid))) {
            return TRUE;
        }
    }

    if ($op == 'view_list') {
        if (user_access('vedi tutti', $account) || (user_access('vedi propri', $account) && ($account->uid == $node->uid))) {
            return TRUE;
        }
    }
    return false;
}

/**
 * Implementation of hook_perm().
 *
 * Since we are limiting the ability to create new nodes to certain users,
 * we need to define what those permissions are here. We also define a permission
 * to allow users to edit the nodes they created.
 */
function atleti_perm() {
    return array(
    'crea nuovi',
    'elimina propri',
    'elimina tutti',
    'modifica propri',
    'modifica tutti',
    'vedi propri',
    'vedi tutti',
    );
}


function atleti_menu() {
    $items = array();

    $items['atleti/elenco'] = array(
    'title' => 'Atleti',
    'description' => 'Descrizione di pagina menu',
    'page callback' => 'atleti_elenco',
    'access callback' => 'atleti_access',
    'access arguments' => array('view_list'),
    'type' => MENU_NORMAL_ITEM,
    );

    return $items;
}

/**
 * Funzione per la creazione del listato di tutti gli atleti.
 * Possibilità di 
 *
 */
function atleti_elenco(){
    drupal_add_js('sites/default/scripts/sorttable.js','file');
    $content = "";

    $content .= "<div class='elenco'>
    <table class='sortable'>
    <thead>
    <tr>
        <th>ID </th>
        <th>Cognome </th>
        <th>Nome </th>
        <th>Data di nascita </th>
         <th>Categoria </th>
    </tr>
    </thead><tbody>";

    $query = "SELECT * FROM atleti, categorie WHERE atleti.categoria_id = categorie.id ORDER BY cognome ASC ";
    $res = db_query($query);

    while ($row = db_fetch_object($res)) {
        //     print_r($row);
        $content .= "<tr>
         <td>$row->nid</td>
         <td>$row->cognome</td>
         <td>$row->nome</td>
        <td>"._trasforma_data($row->data_nascita)."</td>
        <td>$row->categoria</td>
    </tr>";
    }
    $content .="</tbody></table></div>";

    return $content;
}



/**
*
* Implementazione di hook_form_alter() usato per modificare la form base
*
*/
function atleti_form_alter(&$form, $form_state, $form_id) {
    //drupal_set_message('<pre>' . print_r($form, TRUE) . '</pre>');
    //drupal_set_message('nome_form: ' . $form_id);
    if($form_id == 'atleti_node_form') {
        $form['menu']['#access'] = false;
        $form['attachments']['#access'] = false;
        $form['options']['#access'] = false;
        $form['author']['#access'] = false;
        $form['comment_settings']['#access'] = false;
        $form['revision_information']['#access'] = false;
        $form['path']['#access'] = false;
    }
}

/**
 * Implementation of hook_form().
 *
 * Now it's time to describe the form for collecting the information
 * specific to this node type. This hook requires us to return an array with
 * a sub array containing information for each element in the form.
 */
function atleti_form(&$node, $form_state) {
    //abilito js e datepicker per la data di nascita
    jquery_ui_add(array('ui.datepicker'));
    drupal_add_js(
    '   $(document).ready(function(){
            $("input#edit-data-nascita").datepicker();
        }
    );
','inline');
    drupal_add_css("sites/default/modules/jquery_ui/jquery.ui/themes/default/ui.datepicker.css");

    //devo impostare questo perche' carico un file
    $form['#attributes'] = array('enctype' => 'multipart/form-data');

    // The site admin can decide if this node type has a title and body, and how
    // the fields should be labeled. We need to load these settings so we can
    // build the node form correctly.
    $type = node_get_types('type', $node);

    if ($type->has_title) {
        $form['title'] = array(
        '#type' => 'textfield',
        '#title' => check_plain($type->title_label),
        '#required' => TRUE,
        '#default_value' => $node->title,
        '#weight' => -5
        );
    }

    if ($type->has_body) {
        // In Drupal 6, we can use node_body_field() to get the body and filter
        // elements. This replaces the old textarea + filter_form() method of
        // setting this up. It will also ensure the teaser splitter gets set up
        // properly.
        $form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count);
    }

    $form['main'] = array(
    '#type' => 'fieldset',
    '#title' => check_plain($type->title_label),
    '#collapsible' => TRUE,
    '#collapsed' => false,
    '#tree' => false,
    );

    // Now we define the form elements specific to our node type.

    //COGNOME
    $form['main']['cognome'] = array(
    '#type' => 'textfield',
    '#title' => t('Cognome'),
    '#size' => 50,
    '#maxlength' => 64,
    '#required' => TRUE,
    '#default_value' => isset($node->cognome) ? $node->cognome : '',
    '#description' => t('Cognome atleta')
    );

    //NOME
    $form['main']['nome'] = array(
    '#type' => 'textfield',
    '#title' => t('Nome'),
    '#size' => 50,
    '#maxlength' => 64,
    '#required' => TRUE,
    '#default_value' => isset($node->nome) ? $node->nome : '',
    '#description' => t('Nome atleta')
    );

    //SESSO
    $options = array('','M'=>'M','F'=>'F');
    $form['main']['sesso'] = array(
    '#type' => 'select',
    '#required' => false,
    '#title' => t('Sesso'),
    '#default_value' => isset($node->sesso) ? $node->sesso : '',
    '#options' =>  $options,
    '#description' => t('Selezionare sesso')
    );
    $options = null;

    //CATEGORIA
    $sql = "SELECT `id`, `categoria` FROM `categorie` ORDER BY `id`";
    $result = db_query($sql);

    $options = array('');
    while ($row = db_fetch_array($result)) {
        $options[$row['id']] = $row['categoria'];
    }

    $form['main']['categoria_id'] = array(
    '#type' => 'select',
    '#title' => t('Categoria'),
    '#required' => TRUE,
    '#default_value' => isset($node->categoria_id) ? $node->categoria_id : '',
    '#options' =>  $options,
    '#description' => t('Specificare la categoria di appartenenza')
    );
    $options = null;

    //ALTEZZA
    $form['main']['altezza'] = array(
    '#type' => 'textfield',
    '#title' => t('Altezza'),
    '#required' => false,
    '#size' => '15',
    '#maxlength' => 3,
    '#default_value' => isset($node->altezza) ? $node->altezza : '',
    '#description' => t('Specificare altezza in cm (es. "172")')
    );

    //PESO
    $form['main']['peso'] = array(
    '#type' => 'textfield',
    '#title' => t('Peso'),
    '#required' => false,
    '#size' => '5',
    '#maxlength' => 3,
    '#default_value' => isset($node->peso) ? $node->peso : '',
    '#description' => t('Specificare altezza in Kg')
    );

    if (isset($node->data_nascita))
    if (preg_match(
    "^(19|20)\d\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])$^",
    $node->data_nascita))
    $node->data_nascita = _trasforma_data($node->data_nascita);
    //DATA DI NASCITA
    $form['main']['data_nascita'] = array(
    '#type' => 'textfield',
    '#title' => t('Data di nascita'),
    '#required' => false,
    '#size' => 12,
    '#maxlength' => 10,
    /*    '#ahah_wrapper' => array(
    'path' => 'sites/default/modules/jquery_ui/jquery.ui/ui/ui.datepicker.js' ,
    'wrapper' => 'Datepicker'
    ),*/
    '#default_value' => isset($node->data_nascita) ? $node->data_nascita: '',
    '#description' => t('Inserire data di nascita'),
    //    'day' => format_date(time(), 'custom', 'j'),
    //   'month' => format_date(time(), 'custom', 'n',0),
    //    'year' => format_date(time(), 'custom', 'Y')
    );

    //LUOGO DI NASCITA
    $form['main']['luogo_nascita'] = array(
    '#type' => 'textfield',
    '#title' => t('Luogo di nascita'),
    '#required' => false,
    '#size' => 40,
    '#maxlength' => 60,
    '#default_value' => isset($node->luogo_nascita) ? $node->luogo_nascita : '',
    '#description' => t('Inserire la citt&agrave; di nascita (es. "Alfonsine (RA))"')
    );

    //NAZIONALITA'
    $form['main']['nazionalita'] = array(
    '#type' => 'textfield',
    '#title' => t('Nazionalit&agrave;'),
    '#required' => false,
    '#size' => 20,
    '#maxlength' => 15,
    '#default_value' => isset($node->nazionalita) ? $node->nazionalita : '',
    '#description' => t('Inserire la nazionalit&agrave; (es. "ITALIANA")')
    );

    //CODICE FISCALE
    $form['main']['cf'] = array(
    '#type' => 'textfield',
    '#title' => t('Codice Fiscale'),
    '#required' => false,
    '#size' => '20',
    '#maxlength' => 16,
    '#default_value' => isset($node->cf) ? $node->cf : '',
    '#description' => t('Inserire il codice fiscale')
    );

    //TIPO ISCRIZIONE
    $options = array('','Provvisoria'=>'Provvisoria','Definitiva'=>'Definitiva');
    $form['main']['tipo_iscrizione'] = array(
    '#type' => 'select',
    '#required' => false,
    '#title' => t('Tipo di iscrizione'),
    '#default_value' => isset($node->tipo_iscrizione) ? $node->tipo_iscrizione : '',
    '#options' =>  $options,
    '#description' => t('Specificare il tipo di iscrizione')
    );
    $options = null;

    //FOTO
    //se ho fatto "Anteprima" la foto è già stata inviata. quindi la mostro
    //metto doppio controllo per sicurezza (ma è uguale in teoria)
    if ($node->op == "Anteprima"){
        drupal_set_message("foto: ".$node->foto);
        ;//if ($node->foto)
    }
    $form['main']['foto'] = array(
    '#type' => 'file',
    '#title' => t('Foto'),
    '#size' => 80,
    //'#default_value' => isset($node->foto) ? $node->foto : '',
    '#description' => t('Seleziona la foto per l\'upload')
    );

    //NOTE
    $form['main']['note'] = array(
    '#type' => 'textarea',
    '#required' => false,
    '#title' => t('Note aggiuntive'),
    '#default_value' =>  isset($node->note) ? $node->note : '',
    '#cols' => 60,
    '#rows' => 5,
    '#description' => t('Campo per eventuali informazioni addizionali'),
    );

    /* //PULSANTE SUBMIT
    $form['submit'] = array (
    '#type' => 'submit',
    '#value' => t('Invia'),
    );

    //PULSANTE RESET
    $form['reset'] = array (
    '#type' => 'button',
    '#value' => t('Cancella'),
    );

    $form['#validate'][] = 'atleti_validate';
    $form['#submit'][] = 'atleti_submit';

    */

    return $form;
}

/**
 * Implementation of hook_validate().
 *
 * Our "quantity" field requires a number to be entered. This hook lets
 * us ensure that the user entered an appropriate value before we try
 * inserting anything into the database.
 *
 * Errors should be signaled with form_set_error().
 */
function atleti_validate(&$node, &$form_state) {
    //drupal_set_message( "questo e' il validate <pre>" . print_r($form_state, true)."</pre>");
    
    //ALTEZZA
    //controllo che l'altezza sia un numero e >0
    if ($node->altezza != '' && !(is_numeric($node->altezza) && $node->altezza >0)){
        form_set_error('altezza', t('Errore ALTEZZA deve essere numerico e maggiore di zero.'));
    }

    //PESO
    //controllo che peso sia un numero e >0
    if ($node->peso != '' && !(is_numeric($node->peso) && $node->peso >0)){
        form_set_error('peso', t('Errore PESO deve essere numerico e maggiore di zero.'));
    }

    //DATA DI NASCITA
    //controllo che sia antecedente ad oggi //TODO

    //CONTROLLO SUL CODICE FISCALE
    if ($node->cf != '' && strlen($node->cf) != 16){
        form_set_error('cf', t('Errore CODICE FISCALE deve essere lungo 16 caratteri.'));
    }
    
    $errors_in_form = is_array(form_get_errors());
    
    //CONTROLLO FOTO
    //se il file ha nome = '' allora non ho caricato la foto.evito i controlli
    if (!$errors_in_form && $_FILES['files']['name']['foto'] != ''){
        // mi ha dato errori nell'array $_FILES?
        if ($_FILES['files']['error']['foto'] == '0'){
            //esiste il file temp?
            // if (!file_exists($_FILES['files']['tmp_name']['foto']))
            //     form_set_error('foto', t('File non caricato. Errore.'));

            //salvo il file uploadato dentro la cartella immagini, in un direttorio che si chiami come
            //il nome del modulo. così mantengo un po' di ordine! (altrimenti mi sparo...)

            //TODO: rimpiazzare il nome "atleti" con una variabile che contenga il nome del modulo
            $validators = array('file_validate_is_image' => array(),
            'file_validate_image_resolution' => array('1024x768'),
            'file_validate_size' => array(2000000),
            );

            if ($file = file_save_upload('foto',$validators)){
                $info = image_get_info($file->filepath);
                //nome: immagini/atleti/atl_TUFANO_02.jpg
                $dir = 'immagini/atleti';
                $destination = $dir."/atl_".$node->cognome."_".$node->categoria_id.".".$info['extension'];
                if (file_copy($file, $destination, FILE_EXISTS_REPLACE)) {
                   
                    $node->foto = $file;
                    $form_state['values']['foto'] = $file;
                    drupal_set_message("L'upload &egrave; andato a buon fine.");
                }
                else {
                    form_set_error('foto', t("Failed to upload the picture image; the %directory directory doesn't exist or is not writable.",
                    array('%directory' => $destination)));
                }
            }
            else form_set_error("Errore nell'upload");
        }
    }
echo "<br>validate<br><br>";print_r($form_state);echo "<br><br>";
   // node_save($node);
}


/**
 * Implementation of hook_insert().
 *
 * As a new node is being inserted into the database, we need to do our own
 * database inserts.
 */
function atleti_insert($node) {
   echo "<br><br><br>insert<br><br>"; print_r($node);
    db_query("INSERT INTO atleti
  (vid, nid, cognome, nome, sesso, categoria_id, altezza, peso, data_nascita, luogo_nascita,
  nazionalita,cf,tipo_iscrizione,foto,note) 
  VALUES 
  (%d, %d, '%s', '%s','%s',%d,%d,%d,'%s','%s','%s','%s','%s','%s','%s')", 
    $node->vid, $node->nid, strtoupper($node->cognome), strtoupper($node->nome),$node->sesso,$node->categoria_id,$node->altezza,
    $node->peso,_trasforma_data($node->data_nascita,"TO_DB"),$node->luogo_nascita,
    strtoupper($node->nazionalita),strtoupper($node->cf),$node->tipo_iscrizione,$node->foto,$node->note);
    drupal_set_message("insert done (per davvero!).");
}

/**
 * Implementation of hook_update().
 *
 * As an existing node is being updated in the database, we need to do our own
 * database updates.
 */
function atleti_update($node) {
    // if this is a new node or we're adding a new revision,
    if ($node->revision) {
        drupal_set_message("update della revision.");
        atleti_insert($node);
    }
    else {
        db_query("UPDATE atleti
SET 
cognome = '%s', nome = '%s', sesso = '%s', categoria_id = %d, altezza = %d, peso = %d, 
data_nascita = '%s', luogo_nascita = '%s',nazionalita = '%s',cf = '%s',tipo_iscrizione = '%s',
foto = '%s',note = '%s'
WHERE 
vid = %d", strtoupper($node->cognome), strtoupper($node->nome),$node->sesso,$node->categoria_id,$node->altezza,
        $node->peso,_trasforma_data($node->data_nascita,"FROM_DB"),$node->luogo_nascita,strtoupper($node->nazionalita),strtoupper($node->cf),
        $node->tipo_iscrizione,$node->foto,$node->note, $node->vid);
        drupal_set_message("update done (per davvero!).");
    }
}

/**
 * Implementation of hook_nodeapi().
 *
 * When a node revision is deleted, we need to remove the corresponding record
 * from our table. The only way to handle revision deletion is by implementing
 * hook_nodeapi().
 */
/*function atleti_nodeapi(&$node, $op, $teaser, $page) {
    echo "<br><br><br>dentro nodeapi <br>$op<br><br><br>";
            print_r($node);
    switch ($op) {
        case "Salva": 
            echo "<br><br><br>dentro nodeapi <br><br>";
            print_r($node);
            break;
        case 'delete revision':
            // Notice that we're matching a single revision based on the node's vid.
            // db_query('DELETE FROM {node_example} WHERE vid = %d', $node->vid);
            drupal_set_message("delete revision done (per finta).");
            break;
    }
}*/

/**
 * Implementation of hook_delete().
 *
 * When a node is deleted, we need to remove all related records from our table.
 */
function atleti_delete($node) {
    // Notice that we're matching all revision, by using the node's nid.
    //db_query('DELETE FROM {node_example} WHERE nid = %d', $node->nid);

    //devo cancellare anche dalla tabella NODE (o forse lo fa in automatico?)
    drupal_set_message("delete done (per finta).");
}

/**
 * Implementation of hook_load().
 *
 * Now that we've defined how to manage the node data in the database, we
 * need to tell Drupal how to get the node back out. This hook is called
 * every time a node is loaded, and allows us to do some loading of our own.
 */
function atleti_load($node) {
    $additions = db_fetch_object(db_query('SELECT * FROM atleti WHERE vid = %d', $node->vid));

    drupal_set_message("load done (per davvero!). Il node contiene ". print_r($node, true));
    return $additions;
}

/**
 * Implementation of hook_view().
 *
 * This is a typical implementation that simply runs the node text through
 * the output filters.
 */
function atleti_view($node, $teaser = FALSE, $page = FALSE) {
    $node = node_prepare($node, $teaser);
    $node->content['myfield'] = array(
    '#value' => theme('atleti_elenco', $node),
    '#weight' => 1,
    );

    return $node;
}

/**
 * Implementation of hook_theme().
 *
 * This lets us tell Drupal about our theme functions and their arguments.
 */
function atleti_theme() {
    return array(
    'atleti_vista_singola' => array('arguments' => array('node'),),
    'atleti_elenco'        => array('arguments' => array(),),
    );

}

function theme_atleti_elenco(){
    return "elenco";
}

/**
 * A custom theme function.
 *
 * By using this function to format our node-specific information, themes
 * can override this presentation if they wish. We also wrap the default
 * presentation in a CSS class that is prefixed by the module name. This
 * way, style sheets can modify the output without requiring theme code.
 */
function theme_atleti_vista_singola($node) {

    $cat = db_fetch_object(db_query('SELECT * from categorie where categorie.id = %d',  $node->categoria_id));
    drupal_set_message("list: Il node contiene ". print_r($node, true));
    $output = '<div class="main_frame_atleti">';
    $output = '<div class="foto_atleti">
  Categoria: '.$cat->categoria.'
  <br />Societ&agrave;: '.$cat->societa.' </div>';
    $output .= t('Atleta: '. $node->cognome.", ". $node->nome);
    $output .= '</div>';
    return $output;
}

/**
 * Funzione che si occupa di gestire la conversione del campo data dal DB alla scheda e viceversa. 
 *
 * @param $data la data da convertire
 * @param $direction può essere "FROM_DB" (default", o "TO_DB". indica se stiamo prendendo il dato dal DB (FROM) o se abbiamo intenzione di scriverlo nel db (TO)
 * @return la stringa convertita
 */
function _trasforma_data($data, $direction = "FROM_DB"){
    // drupal_set_message("tras_da: ". $data);
    switch ($direction){
        case 'FROM_DB':
            $d = explode("-",$data);
            return "{$d[2]}/{$d[1]}/{$d[0]}";

        case 'TO_DB':
            $d = explode("/", $data);
            return "{$d[2]}-{$d[1]}-{$d[0]}";

        default: return '9999-99-99';
    }
}