1

I am trying to (1) upload a image file (2) resize it and center it and (3) change the file name and (4) move it into /public dir.

Here is a part of my code;

$path = $this->request->getFile('dh_site_logo')->store();
            
$temp_file_path = WRITEPATH.'uploads/' . $path;

service('image')
    ->withFile($temp_file_path)  // must include full path
    ->fit(250,150,'center')
    ->save($temp_file_path);
            
$public_file_path = ROOTPATH.'public';
$file_to_upload = 'site_logo.'.$update_post->guessExtension();;
$overwrite = true;      
        
$update_post->move($public_file_path, $file_to_upload, $overwrite);

I assume I would have to do the image manipulation in the writable/uploads directory prior to moving to /public

I cant seem to lock on to the file that has been uploaded with a new random name, manipulate and then move.

Tried this, too

            // example of return $path = "20220130/1643544458_5a528551d1fe83c88e02.gif"
            $path = $this->request->getFile('dh_site_logo')->store('site_logo', 'site_logo.gif');
            
            $temp_file_path = WRITEPATH.'uploads/' . $path;

             service('image')
                ->withFile($temp_file_path)  // must include full path
                ->fit(250,150,'center')
                ->save($temp_file_path);
            
            $public_file_path = ROOTPATH.'public';
            $new_file_name = 'site_logo.gif';
            $overwrite = true;      
        
            $update_post->move($public_file_path, $new_file_name, $overwrite);

The above give me an error, The uploaded file has already been moved

spreaderman
  • 918
  • 2
  • 10
  • 39

1 Answers1

2

Ci4 / Image Upload and Manipulate Error / finfo_file(/tmp/phpSrVWUZ): failed to open stream: No such file or directory

Problem 1:

You're receiving the above error because of the line of code below:

$file_to_upload = 'site_logo.'.$update_post->guessExtension();;

Explanation 1:

On your first line of code below:

$path = $this->request->getFile('dh_site_logo')->store();

You're calling the CodeIgniter\HTTP\Files\UploadedFile::store(?string $folderName = null, ?string $fileName = null) method which saves the uploaded file (/tmp/phpSrVWUZ) to a new location.

I.e: The store(...) method moves the uploaded file from the server's default temporary directory (/tmp) to the project's upload directory (WRITEPATH . 'uploads/').

You then try calling $update_post->guessExtension() forgetting that the UploadedFile instance $update_post still refers to the old non-existent path (/tmp/phpSrVWUZ), hence the error.

To be more specific, the method CodeIgniter\HTTP\Files\UploadedFile::guessExtension() calls another method CodeIgniter\Files\File::getMimeType(). The method getMimeType(...) tries to retrieve the mime type using the code snippet below on a non-existent file resulting in the error:

finfo_file(finfo_open(FILEINFO_MIME_TYPE), "/tmp/phpSrVWUZ");

// PHP Warning:  finfo_file(/tmp/phpSrVWUZ): Failed to open stream: No such file or directory in ...

=========

Problem 2:

Tried this, too

...

The above give me an error, The uploaded file has already been moved

Explanation 2:

You normally receive this error because you're trying to move the uploaded file to a new location more than once.

This happens when you call the CodeIgniter\HTTP\Files\UploadedFile::store(...) or CodeIgniter\HTTP\Files\UploadedFile::move(...) method more than once.

Excerpt from CI 4.x source code.

/**
 * Move the uploaded file to a new location.
 * ...
 * If this method is called more than once, any subsequent calls MUST raise
 * an exception.
 * ...
 */
public function move(...): bool {
// ...
        if ($this->hasMoved) {
            throw HTTPException::forAlreadyMoved();
        }
// ...

}

To be more specific, every UploadedFile instance has a property called protected $hasMoved = false; which gets updated to true once the uploaded file has been moved successfully from the server's default temporary directory:

Excerpt from CI 4.x source code.

    /**
     * Returns whether the file has been moved or not. If it has,
     * the move() method will not work and certain properties, like
     * the tempName, will no longer be available.
     */
    public function hasMoved(): bool;

Solution A:

This applies if you don't care about the original uploaded file and you're only interested in the final transformed/resized file residing in the 'public' path.


public function createThumbnail(\CodeIgniter\HTTP\Files\UploadedFile $uploadedFile, ?string $newThumbnailFileName = null, int $width = 250, int $height = 150, string $position = "center"): ?\CodeIgniter\Files\File
{
    if (!$uploadedFile->isValid()) {
        return null;
    }

    $newThumbnailFileName = $newThumbnailFileName
        ? ((($point = strrpos($newThumbnailFileName, ".")) === false) ? $newThumbnailFileName : substr($newThumbnailFileName, 0, $point)) . $uploadedFile->guessExtension()
        : $uploadedFile->getRandomName();

    $targetPath = ROOTPATH . 'public' . DIRECTORY_SEPARATOR . $newThumbnailFileName;

    \Config\Services::image()
        ->withFile($uploadedFile->getRealPath() ?: $uploadedFile->__toString())
        ->fit($width, $height, $position)
        ->save($targetPath);

    return new \CodeIgniter\Files\File($targetPath, true);
}

The function above basically leaves the original uploaded file in the server's default temporary directory untouched and processes a new image which gets saved in the project's 'public' path.

Usage For Solution A:

The function above returns a CodeIgniter\Files\File instance representing the newly transformed image. I.e:

$requestFileName = "dh_site_logo";
$uploadedFile = $this->request->getFile($requestFileName);

// Generates a thumbnail in the 'public' path with a uniquely generated filename.
$thumbnail = $this->createThumbnail(
    uploadedFile: $uploadedFile
);

// OR

// Generates a thumbnail in the 'public' path with a custom filename ('site_logo').
$thumbnail = $this->createThumbnail(
    uploadedFile: $uploadedFile,
    newThumbnailFileName: "site_logo"
);

// OR

// You can modify the default parameters as well.
$thumbnail = $this->createThumbnail(
    uploadedFile: $uploadedFile,
    width: 100,
    height: 150,
    position: "left"
);

Solution B:

This applies if for some reason you want to transform/resize and save the modified image in the 'public' path similar to Solution A and still persist/keep or move the original uploaded file from the server's temporary directory to your project's writable/uploads folder for future reference or purposes.

TIP: The file will be deleted from the temporary directory at the end of the request if it has not been moved away or renamed. - Excerpt From PHP Doc: Example #2 Validating file uploads

Steps:

  1. Generate as many thumbnails (transformed files) as you need.
  2. Lastly, move the uploaded file from the server's default temporary directory to the project's writable/uploads folder.

$requestFileName = "dh_site_logo";
$uploadedFile = $this->request->getFile($requestFileName);

// 1. Generate as many thumbnails (transformed files) as you need.

// Generates a thumbnail in the 'public' path with
// a uniquely generated filename.
$thumbnail = $this->createThumbnail(
    uploadedFile: $uploadedFile
);

// 2. Lastly, move the uploaded file from the server's default temporary
// directory to the project's 'writable/uploads' folder (I.e: $uploadedFile->store()).

if (!$uploadedFile->hasMoved()) {
    // The moved uploaded file.
    $file = new \CodeIgniter\Files\File(WRITEPATH . 'uploads/' . $uploadedFile->store(), true);
}

steven7mwesigwa
  • 5,701
  • 3
  • 20
  • 34
  • Many thanks for the explanation but it is quite complicate for me. May I ask, what is the difference/ use of store() and move()? Also, what is the high level overview here. Do I move the image into writable? I cannot see where /tmp is in my file structure. – spreaderman Feb 01 '22 at 04:03
  • 1
    @spreaderman I understand if you find the explanation intimidating but at least take the effort to skim through it since most of your questions have either been answered in this post or the links for further reading. – steven7mwesigwa Feb 01 '22 at 10:39
  • 1
    @spreaderman *Do I move the image into writable?*: Yes and No. Yes, if you wish to have the ability to access it even after the current request is complete/done. No, if you're only interested in the final transformed/manipulated image. – steven7mwesigwa Feb 01 '22 at 10:47
  • 1
    @spreaderman *I cannot see where `/tmp` is in my file structure.*: This should answer that question. [How do I get the PHP Uploads directory?](https://stackoverflow.com/questions/7511610/how-do-i-get-the-php-uploads-directory/7511767#7511767) – steven7mwesigwa Feb 01 '22 at 10:50
  • Yes, I have been reading your post all afternoon and learnt a lot. I can up with this solution. What do you thinnk? $x_file = $this->request->getFile('dh_site_logo'); $x_file_mime__type = $x_file->getMimeType(); $config = new \Config\Mimes(); $x_file_ext = $config->guessExtensionFromType($x_file_mime__type); $image = \Config\Services::image() ->withFile($x_file) ->resize(10, 10, true, 'height') ->save(FCPATH . 'site_logo.'.$x_file_ext); – spreaderman Feb 01 '22 at 11:06
  • @spreaderman *what is the difference/ use of `store()` and `move()`?*: There isn't much of a difference since `store(...)` calls `move(...)` behind the scenes. **Diff 1:** You can pass `$overwrite` parameter in `move(...)` which isn't possible in `store(...)` **Diff 2:** You can choose your own `$targetPath` with `move(...)` but the `$targetPath` for `store(...)` is always `WRITEPATH . 'uploads/' . $folderName`. **Diff 3:** The default `$fileName` for `store(...)` is a randomly generated name yet `move(...)` uses the client's file name as the default `$fileName`. – steven7mwesigwa Feb 01 '22 at 11:15
  • @spreaderman Your solution in the comment section is okay and should work fine. It basically does the *same* thing as the `createThumbnail(...)` method in this post. The *only* difference is that yours uses `->resize(...)` and mine uses `fit(...)` of the `\Config\Services::image()` instance. Feel free to post your solution as the accepted answer if your find it as an easier option to your problem. – steven7mwesigwa Feb 01 '22 at 11:47
  • 1
    Thank your. How about the idea of bringing in mime confit. No, your reply is the answer. My solution is a subset of yours. I will mark yours as the answer overnight. Many thanks for the verbose explanations which really help me learn a lot. – spreaderman Feb 01 '22 at 11:50
  • @spreaderman You're most welcome. I'm glad you learnt something and even managed to come up with your own simplified solution . Could you rephrase your question? I didn't understand what you meant. *How about the idea of bringing in mime confit.* – steven7mwesigwa Feb 01 '22 at 12:06
  • Sorry, should be "How about the idea of bringing in mime config. config = new \Config\Mimes(); Seemed like a safe way to detect actual mime type – spreaderman Feb 02 '22 at 01:41
  • @spreaderman Yes, it's safe. Though you're repeating what [guessExtension()](https://github.com/codeigniter4/CodeIgniter4/blob/feea6c2688aa883cbe93060ebe2f58103cb4547d/system/HTTP/Files/UploadedFile.php#L300) does already. Meaning that your solution could be shortened to: `$x_file = $this->request->getFile('dh_site_logo'); $image = \Config\Services::image()->withFile($x_file)->resize(10, 10, true, 'height')->save(FCPATH . 'site_logo.' . $x_file->guessExtension());` – steven7mwesigwa Feb 02 '22 at 04:19