Skip Navigation

[Résolu] Confusion Over Generic Image Field Functionality

This support ticket is created Il y a 4 mois et 4 semaines. There's a good chance that you are reading advice that it now obsolete.

This is the technical support forum for Toolset - a suite of plugins for developing WordPress sites without writing PHP.

Everyone can read this forum, but only Toolset clients can post in it. Toolset support works 6 days per week, 19 hours per day.

Sun Mon Tue Wed Thu Fri Sat
- 9:00 – 13:00 9:00 – 13:00 9:00 – 13:00 9:00 – 13:00 9:00 – 13:00 -
- 14:00 – 18:00 14:00 – 18:00 14:00 – 18:00 14:00 – 18:00 14:00 – 18:00 -

Supporter timezone: Asia/Karachi (GMT+05:00)

This topic contains 10 réponses, has 2 voix.

Last updated by Dave Il y a 4 mois et 2 semaines.

Assigned support staff: Waqar.

Auteur
Publications
#2450975

Tell us what you are trying to do?

Upload an image to replace one stored in a custom field. The complication is that this is being used as a form of "global setting", so the generic field needs to be used as the form is being called from a static page due to the design requirements of the site, so it's using generic fields to edit existing data from a single post that holds the settings.

Anyway, the need is to allow users to upload a single image (with preview in an ideal world, but I get this is outside the scope of what Toolset offers) and then for me to be able to take this file using the cred_save_data hook and store it in the custom field in place of the previous image. I would like it added to the media library to make this easier, as that way all I need to do is replace the URL in the custom field.

However, the generic image field only seems to capture the file name and doesn't appear to do anything else with it. I've seen this asked before, but there's never been a clear answer as to how this works. Dumping the $form_data and $_POST show the field name and simply the file name, nothing else, and a search of the database shows it's not being stored.

The form is very simple, it just shows the existing image and offers the ability to upload a replacement:

[credform]
[cred_field field='form_messages' class='alert alert-warning']
<div class="form-group">
  <div class="row">
    <div class="col-md-4">
      <label>Current Logo:</label>
    </div>
    <div class="col-md-8">
      <div class="logo-container">
        <img title="Logo" alt="Logo" src=[global_settings setting='logo']>
      </div>
    </div>
  </div>
</div>
<br />
<div class="form-group">
  <div class="row">
    <div class="col-md-4">
      <label for="%%FORM_ID%%_global-logo">[cred_i18n name='global-logo']Upload New Logo:[/cred_i18n]</label>
    </div>
    <div class="col-md-8">
      [cred_generic_field type='image' field='global-logo']
      {
      "required":0
      }
      [/cred_generic_field]
    </div>
  </div>  
</div>
<div class="row">
  <div class="col-md-6">
    [cred_field field='form_submit' output='bootstrap' value='Save' class='btn btn-primary btn-lg btn-block']
  </div>
  <div class="col-md-6">
    <button type="button" class="btn btn-secondary btn-lg btn-block" data-dismiss="modal">Cancel</button>
  </div>
</div>
[/credform]

I'm convinced I'm missing something, but the way this field works doesn't appear to be very well documented. Can someone please advise?

#2451427

Waqar
Supporter

Languages: Anglais (English )

Timezone: Asia/Karachi (GMT+05:00)

Hi,

Thank you for contacting us and I'd be happy to assist.

In my tests, I was able to achieve this, by enabling the option "Use the WordPress Media Library manager for image, video, audio, or file fields", in the form's settings.

When this option is checked, it shows an "Upload or select image" button for the generic field, which can be used to either upload a new image (which is saved to the media library) or select an existing media library image.

And on the form's submission, you'll have the full image URL available in the function attached to the "cred_save_data" hook, through $_POST['global-logo'].

Note: This solution won't work if the form is submitted by a guest (i.e. a non-logged-in user), but I don't think you need this particular form to work for the guests.

regards,
Waqar

#2452167

Hi Waqar,

Thanks for getting back to me. Enabling the "Use the WordPress Media Library manager for image, video, audio, or file fields" option does indeed allow for uploading and saving of the image, but it also grants access to the media library, which we don't want I'm afraid.

We wanted a simple file picker that puts the image in the media library behind the scenes and everything else is handled via hook code. It's intended to be as simple as possible, so the media library should not be visible, which sounded like the way the generic image field worked.

Additionally, could you clarify how the generic image field is supposed to work? Because without this pretty much completely separate option enabled, it doesn't seem to do very much at all. If this is how it's intended to work, then why is the media library manager a separate option? And why is it not documented with the generic image field documentation? I'm sure I'm still missing something here.

Can you clarify please?

(Oh, and you're right, only logged in users will ever be able to see this, so that's not an issue)

#2452991

Waqar
Supporter

Languages: Anglais (English )

Timezone: Asia/Karachi (GMT+05:00)

Thanks for writing back.

If you don't want to allow access to the existing media library images, you can get the uploaded file's information from the 'tmp' directory using the PHP global variable '$_FILES'. That is where a generic field can come in handy, for a case where a user wants to do custom processing on the uploaded file.

For example, I was able to use the instructions and code from this online article, to upload the generic field's image to the WordPress media library, through a function attached to the "cred_save_data" hook:
hidden link

Example:


add_action('cred_save_data', 'my_save_data_action',10,2);
function my_save_data_action($post_id, $form_data)
{
	// if a specific form
	if ($form_data['id']==12345)
	{
		// WordPress environmet
		require( dirname(__FILE__) . '/../../../wp-load.php' );

		// it allows us to use wp_handle_upload() function
		require_once( ABSPATH . 'wp-admin/includes/file.php' );

		// you can add some kind of validation here
		if( empty( $_FILES[ 'global-logo' ] ) ) {
			wp_die( 'No files selected.' );
		}

		$upload = wp_handle_upload( 
			$_FILES[ 'global-logo' ], 
			array( 'test_form' => false ) 
		);

		if( ! empty( $upload[ 'error' ] ) ) {
			wp_die( $upload[ 'error' ] );
		}

		// it is time to add our uploaded image into WordPress media library
		$attachment_id = wp_insert_attachment(
			array(
				'guid'           => $upload[ 'url' ],
				'post_mime_type' => $upload[ 'type' ],
				'post_title'     => basename( $upload[ 'file' ] ),
				'post_content'   => '',
				'post_status'    => 'inherit',
			),
			$upload[ 'file' ]
		);

		if( is_wp_error( $attachment_id ) || ! $attachment_id ) {
			wp_die( 'Upload error.' );
		}

		// update medatata, regenerate image sizes
		require_once( ABSPATH . 'wp-admin/includes/image.php' );

		wp_update_attachment_metadata(
			$attachment_id,
			wp_generate_attachment_metadata( $attachment_id, $upload[ 'file' ] )
		);

		$new_image_id = $attachment_id;
		$new_image_url = wp_get_attachment_url($attachment_id);

	}
}

Note: Please replace "12345" with your actual form's ID and "global-logo" with your generic field's slug.

At the bottom of the function, you have the newly uploaded media library image's ID and the URL, to save it in the desired option or custom field.

I hope this helps.

#2453715

Hi Waqar,

I've had a quick try with your code, and after updating the form ID and making no other changes (the generic field is called "global-logo"), it causes a critical WordPress error with nothing showing in the console if I try uploading a file. I haven't had a chance to try debugging it yet, but I just wanted to post a quick reply to keep the thread updated.

#2453809

Waqar
Supporter

Languages: Anglais (English )

Timezone: Asia/Karachi (GMT+05:00)

Thanks for the update and that is strange.

You can turn the WordPress debugging on and see if you get any details of the errors in the server's error log.
( ref: https://wordpress.org/support/article/debugging-in-wordpress/ )

#2456313

Apologies for the delay, I've not been well.

I'm just digging into this now, so to keep the thread going, here are the errors reported:

[13-Sep-2022 11:43:21 UTC] PHP Fatal error:  require(): Failed opening required '/home/customer/www/manage.gymbubbas.co.uk/public_html/wp-content/toolset-customizations/../../../wp-load.php' (include_path='.:/usr/local/php74/pear') in /home/customer/www/manage.gymbubbas.co.uk/public_html/wp-content/toolset-customizations/global_settings_functions.php on line 303

[13-Sep-2022 11:43:21 UTC] PHP Fatal error:  Uncaught Error: Object of class WP_Error could not be converted to string in /home/customer/www/manage.gymbubbas.co.uk/public_html/wp-content/plugins/sg-security/templates/error.php:111

To clarify, line 303 refers to this part of your code:

// WordPress environmet
require( dirname(__FILE__) . '/../../../wp-load.php' );

As it suggests in the error. And I believe the second error is being caused by the first one as the sg-security template is something that is shown instead of the generic WP white screen.

#2456949

Waqar
Supporter

Languages: Anglais (English )

Timezone: Asia/Karachi (GMT+05:00)

Hope you're feeling better now.

Looks like you're including that custom code, through the Toolset's custom code section.

Can you test it by including it through the active theme's "functions.php" file instead?

#2457041

I'm getting there thanks.

Yes, by default I try to keep all custom code in the Toolset Custom Code modules for transferability. But I've tried this in the functions file and it's still failed, just in a slightly different way. Now it reports "Specified file failed upload test." and the console shows errors. I've picked the ones from the point of the custom code onwards, which are as follows:

 E_NOTICE  Trying to access array offset on value of type null - /home/customer/www/ siteurl /public_html/wp-admin/includes/file.php:898 
#24 /home/customer/www/ siteurl /public_html/wp-admin/includes/file.php:1074 - _wp_handle_upload(NULL, Array[1], NULL, 'wp_handle_uplo...')
#23 /home/customer/www/ siteurl /public_html/wp-content/themes/hello-elementor-child/functions.php:301 - wp_handle_upload(NULL, Array[1])

Line 301 relates to this line from your code:

array( 'test_form' => false )

I'm not sure off hand what this is doing, but I'm wondering if 'test_form' should be the upload form name. I'm going to do some testing with that and examine the $_FILES and $upload variables, see if I can figure this out while I wait to hear back.

#2457047

Ah, never mind I've got it. There was a typo on the field name on the line above 301.

But even with that change, it will not run from the Toolset Custom Code module, so it's going to have to stay in the functions file unfortunately.

I'm just going to try and sort out the rest of what I need it to do, and if there are no further issues I'll close the thread off in an hour or so.

#2457125

I've adapted the code you provided and turned it into a simple upload function. Now I've seen it laid out, it's pretty obvious this was the way to go, but as I said in the initial post, I was confused about the functionality of the image generic field.

So what I've done is leave the hook call in with the rest of my global settings cred_submit_complete calls and then simply called the image upload function, which I have expanded with some simple validation and made it so that the field passing the image to the $_FILES function is a variable that is passed to the function, so that it can be reused elsewhere on the site if need be.

Here's what I've come up with (please note, part of the first section has been edited to show only the relevant bits):

In the Toolset Custom Code module:

function edit_global_settings( $post_id, $form_data ){
  //If is the Edit Global Logo form
  if( $form_data['id'] == 2779 ) {

    //Get uploaded image
    $new_image = image_upload( 'global-logo' );
    
    //Update stored global logo
    update_post_meta( global_settings_id(), 'wpcf-global-logo', $new_image['new_image_url'] );
  }
add_action('cred_submit_complete', 'edit_global_settings', 10, 2);

In functions.php:

/*** Function to upload images to the media library without front end access ***/
/* Requires the name of the upload field to be passed to it */
function image_upload( $upload_field ){

	//Load WordPress environmet
	require( dirname(__FILE__) . '/../../../wp-load.php' );

	//Load the wp_handle_upload() function
	require_once( ABSPATH . 'wp-admin/includes/file.php' );

	//Array of all excepted file types in lower case
	$accept = [ "jpeg", "jpg", "gif", "png", "heic" ];
	//Get uploaded file extension
	$up_ext = strtolower( pathinfo( $_FILES[$upload_field]['type'], PATHINFO_EXTENSION ) );

	//Check a file has been selected
	if( empty( $_FILES[ $upload_field ] ) ) {
		wp_die( 'No files selected.' );
		//Check if the file format is an acceptable image type
	} elseif( !in_array( $up_ext, $accept ) ) {
		wp_die( 'Incorrect file format.  Please only use jpg, gif or png files.');
	} elseif( $_FILES[$uploaded_field]['size'] >= 2097152 ) {
		wp_die( 'File is too large.  Please select a file less than 2MB.' );
	}

	//Upload the file
	$upload = wp_handle_upload( 
		$_FILES[ $upload_field ], 
		array( 'test_form' => false ) 
	);

	//Report any upload errors if they occur
	if( !empty( $upload[ 'error' ] ) ) {
		wp_die( $upload[ 'error' ] );
	}

	//Add image to media library
	$attachment_id = wp_insert_attachment(
		array(
			'guid'           => $upload[ 'url' ],
			'post_mime_type' => $upload[ 'type' ],
			'post_title'     => basename( $upload[ 'file' ] ),
			'post_content'   => '',
			'post_status'    => 'inherit',
		),
		$upload[ 'file' ]
	);

	//Report any attachment errors if they occur
	if( is_wp_error( $attachment_id ) || ! $attachment_id ) {
		wp_die( 'Upload error.' );
	}

	//Load the wp_update_attachment_metadata() function
	require_once( ABSPATH . 'wp-admin/includes/image.php' );

	//Update metadata and regenerate image sizes
	wp_update_attachment_metadata(
		$attachment_id,
		wp_generate_attachment_metadata( $attachment_id, $upload[ 'file' ] )
	);

	//Create a return array for the image ID and URL
	$new_image = array(
		'new_image_id' => $attachment_id,
		'new_image_url' => wp_get_attachment_url( $attachment_id )
	);

	return $new_image;
}

I know it's not perfect, and I'll probably redo the validation at some point to avoid using wp_die(), and depending on how usage goes, I might add the automatic deletion of the previous image, or provide some separate functionality for doing this. Either way, we've resolved the confusion here, so many thanks!