Login Upload Photo

I’m trying to let users update their profile photo from the front end with the tutorial listed in the documentation here: Login.Using Pre and Post Hooks - Tutorials | MODX Documentation

The file name is getting saved to the profile, but nothing is uploading and the file path isn’t included. Also it sort of looks like the snippet is supposed to add a hash string to the end of the file name, but the file name is whatever is originally picked in the file input.

I have a set of folder that matches the base_path in the snippet. The Default Admin (super user) account displays the same symptoms.

What might I be missing?

  • Do you have a submit button with the name “login-updprof-btn” in your form?
    <input type="submit" name="login-updprof-btn" value="[[!%login.update_profile]]" />
  • Does the image you upload have one of these extensions? → 'jpg','png','jpeg'
  • Does anything get logged to the MODX error log? → 'Error loading file. ...

Maybe add some more $modx->log(...) lines to the code, so that you can ‘debug’ if the hook actually runs and where it fails.

The submit button is correct, the file is a .jpg and the log shows:

/core/cache/includes/elements/modx/revolution/modsnippet/27.include.cache.php : 42) Error loading file. Error code:

With no error code at the end.

How does one add more $modx->log(…) lines to the code? Can you just put any existing line inside the parentheses? Does it have to be inside an if/else like near the bottom of the snippet?

I think I got it to upload by changing the folder permissions from 705 to 755. Is 755 what it should have been all along?

Regardless, the database field is still not getting the full path name. It is getting something like “users/3f87f4b.jpg”

Looking at the example snippet it has:

    //set path for file
    $pathToFile = $modx->config['base_path'].'site_content/content/users/';
    // set path for cmp because of media resource
    $pathToFileProfile = 'users/';

Is this assuming there is some type of media source that makes the user’s photo field look in the “site_content/content/” parent folder automatically? I know how to set TVs to use a media source which can have it’s base directory set, but how would I set a base directory for user accounts?

Even if a media source is needed, how would that work with someone like higher level users that have permission for other foldres. How would we set the base directory just for the user photo field … if it is uploaded from the front end.

A secondary problem seems to be that if no photo is added to the form, like say if a phone number is being changed, then “Array” gets inserted into the photo field.

So with the help of Microsoft CoPilot, I (or should I say we?) added a lot of error logging to look for problems, fixed the problem with Array being inserted if no file was chosen and also added a button to let the user delete or reset the photo to a default photo. Here is the code if anyone is interested.

<?php
// get user details
$profile = $modx->user->getOne('Profile');

// if delete button is clicked
if (isset($_POST['delete-photo-btn'])) {
    // get current photo path
    $currentPhoto = $profile->get('photo');

    // Check if the original photo exists
    if ($currentPhoto && file_exists($modx->config['base_path'] . $currentPhoto)) {
        // delete old pic in profile
        $hook->setValue('photo', '');
        $modx->log(modX::LOG_LEVEL_DEBUG, 'Old photo deleted: ' . $currentPhoto);
    } else {
        $modx->log(modX::LOG_LEVEL_DEBUG, 'No existing photo found or photo path is empty.');
    }

    // Set to default photo path
    $defaultPhotoPath = 'path/to/default/photo.jpg';
    $hook->setValue('photo', $defaultPhotoPath);
    $modx->log(modX::LOG_LEVEL_DEBUG, 'Photo field set to default photo: ' . $defaultPhotoPath);

    return true;
}

// if post
if (isset($_POST['login-updprof-btn'])) {
    // set extensions
    $validExt = array('jpg', 'png', 'jpeg');
    
    // set path for file
    $pathToFile = $modx->config['base_path'] . 'site_content/content/users/';
    
    // set path for cmp because of media resource
    $pathToFileProfile = 'users/';
    
    // Check if a file is uploaded
    if (!empty($_FILES['photo']['name'])) {
        // get file name
        $nameFile = $_FILES['photo']['name'];
        
        // lowercase and extension
        $extFile = mb_strtolower(pathinfo($nameFile, PATHINFO_EXTENSION));
        
        // the tmp file
        $tmpFile = $_FILES['photo']['tmp_name'];
        
        // upload is ok then
        if ((is_uploaded_file($tmpFile)) && !($_FILES['photo']['error'])) {
            // check extension types
            if (in_array($extFile, $validExt)) {
                // make a file name
                $tmpzname = 'user' . $modx->user->get('id');
                
                // add a hash and extension
                $nameFile = hash('adler32', $tmpzname) . '.' . $extFile;
                
                // full name with path
                $fullNameFile = $pathToFile . $nameFile;
                
                // copy the tmp to new one move_uploaded_file1 and rename1 did not work this will overwrite the old pic as they all have same name
                if (copy($tmpFile, $fullNameFile)) {
                    // name and path for profile as it's different because of media resource
                    $fullNameFileProfile = $pathToFileProfile . $nameFile;
                    
                    // Check if the original photo exists
                    $currentPhoto = $profile->get('photo');
                    if ($currentPhoto && file_exists($modx->config['base_path'] . $currentPhoto)) {
                        // delete old pic in profile
                        $hook->setValue('photo', '');
                        $modx->log(modX::LOG_LEVEL_DEBUG, 'Old photo deleted: ' . $currentPhoto);
                    } else {
                        $modx->log(modX::LOG_LEVEL_DEBUG, 'No existing photo found or photo path is empty.');
                    }

                    // set new pic path
                    $hook->setValue('photo', $fullNameFileProfile);
                    $modx->log(modX::LOG_LEVEL_DEBUG, 'New photo path set: ' . $fullNameFileProfile);
                } else {
                    $modx->log(modX::LOG_LEVEL_ERROR, 'Failed to copy file. Source: ' . $tmpFile . ' Destination: ' . $fullNameFile);
                }
            } else {
                $modx->log(modX::LOG_LEVEL_ERROR, 'The image has an invalid extension');
            }
        } else {
            $modx->log(modX::LOG_LEVEL_ERROR, 'Error loading file. Error code: ' . $_FILES['photo']['error']);
        }
    } else {
        $modx->log(modX::LOG_LEVEL_DEBUG, 'No file uploaded. Keeping existing photo.');
        // Retain existing photo path
        $currentPhoto = $profile->get('photo');
        if ($currentPhoto) {
            $hook->setValue('photo', $currentPhoto);
        } else {
            $hook->setValue('photo', '');
        }
    }
}

return true;
?>

Plus, add a delete button to the UpdateProfile form.

<form enctype="multipart/form-data" method="post">
    <!-- Existing form fields -->
    <input type="file" name="photo">
    <button type="submit" name="login-updprof-btn">Upload</button>
    <button type="submit" name="delete-photo-btn">Delete Photo</button>
</form>

The delete photo button can be moved up near the photo if you have it up higher in your form.

You could (create and) set the system setting photo_profile_source, to use a specific media source for the user profile pictures.


That said, this hook code is just some example code that you can adapt to your specific requirements. You don’t have to use a media source. Just change the line $pathToFileProfile = 'users/'; so that the stored path matches what it is supposed to be.

1 Like