PHP CKEditor 5 Build with Custom Uploader: Images Not Saving or Missing Attributes

  Kiến thức lập trình

Thank you in advance for your help. I’ll try to explain my issue as simply as possible:

Context:
I’m attempting to create a page system where users can add blocks, each block having a CKEditor text editor. I’m working with native PHP and using an HTACCESS configuration to manage this project.

Problem 1:
My initial problem is that when I’m in the editor and I insert an image, I receive a browser error stating, “Couldn’t upload file: Image26342.jpeg.” However, the image is indeed in my “Uploader” folder after being sent. I managed to remove this error message by modifying my uploader like this:

From:
xhr.responseType = “json”;

To:
xhr.responseType = “JSON”;

But I suspect this isn’t the correct solution…

Problem 2 (Possibly Related to the First):
If I change the response type to JSON in uppercase and then save my data with the images in my editor, instead of saving “,” it only saves “.”

As mentioned before, these issues may be connected.

Here’s my custom uploader in JS:

class MyUploadAdapter {
  constructor(loader) {
    this.loader = loader;
    console.log("MyUploadAdapter initialized");
  }

  upload() {
    console.log("Upload started");
    return this.loader.file.then(
      (file) =>
        new Promise((resolve, reject) => {
          this._initRequest();
          this._initListeners(resolve, reject, file);
          this._sendRequest(file);
        })
    );
  }

  abort() {
    if (this.xhr) {
      console.log("Upload aborted");
      this.xhr.abort();
    }
  }

  _initRequest() {
    const xhr = (this.xhr = new XMLHttpRequest());
    xhr.open("POST", "../admin/uploader", true);
    xhr.responseType = "json";
    console.log("Request initialized");
  }

  _initListeners(resolve, reject, file) {
    const xhr = this.xhr;
    const loader = this.loader;
    const genericErrorText = `Couldn't upload file: ${file.name}.`;

    xhr.addEventListener("error", () => {
      console.log("Error event occurred");
      reject(genericErrorText);
    });
    xhr.addEventListener("abort", () => {
      console.log("Upload aborted");
      reject();
    });
    xhr.addEventListener("load", () => {
      console.log("Upload completed");
      const response = xhr.response;
      console.log("Response received", response);

      if (!response || response.error) {
        console.log(
          "Error in response",
          response ? response.error : "No response"
        );
        return reject(
          response && response.error ? response.error.message : genericErrorText
        );
      }

      console.log("Upload successful, URL:", response.url);
      resolve({
        default: response.url,
      });
    });

    if (xhr.upload) {
      xhr.upload.addEventListener("progress", (evt) => {
        if (evt.lengthComputable) {
          loader.uploadTotal = evt.total;
          loader.uploaded = evt.loaded;
          console.log(`Progress: ${evt.loaded}/${evt.total}`);
        }
      });
    }
  }

  _sendRequest(file) {
    const data = new FormData();
    data.append("upload", file);
    console.log("Sending request with file:", file.name);
    this.xhr.send(data);
  }
}

function MyCustomUploadAdapterPlugin(editor) {
  editor.plugins.get("FileRepository").createUploadAdapter = (loader) => {
    return new MyUploadAdapter(loader);
  };
}

function initializeEditors() {
  document
    .querySelectorAll('[data-ckeditor="true"]')
    .forEach(function (editor) {
      if (!editor.classList.contains("initialized")) {
        ClassicEditor.create(editor, {
          removePlugins: ["MediaEmbedToolbar"],
          extraPlugins: [MyCustomUploadAdapterPlugin],
          mediaEmbed: {
            previewsInData: true,
          },
        })
          .then(() => {
            console.log("Editor initialized");
          })
          .catch((error) => {
            console.error(error);
          });
        editor.classList.add("initialized");
      }
    });
}

document.addEventListener("DOMContentLoaded", initializeEditors);

And here’s my uploader in PHP:

<?php
header('Content-Type: application/json');

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (isset($_FILES['upload']) && $_FILES['upload']['error'] === UPLOAD_ERR_OK) {
        $targetDir = "../assets/img/uploader/";
        
        if (!file_exists($targetDir)) {
            mkdir($targetDir, 0777, true);
        }
        
        $fileName = uniqid() . '_' . basename($_FILES['upload']['name']);
        $uploadPath = $targetDir . $fileName;
        
        if (move_uploaded_file($_FILES['upload']['tmp_name'], $uploadPath)) {
            echo json_encode(array('url' => $uploadPath));
        } else {
            http_response_code(500);
            echo json_encode(array('error' => 'Erreur lors du téléchargement du fichier.'));
        }
    } else {
        http_response_code(400);
        echo json_encode(array('error' => 'Aucun fichier téléchargé ou une erreur s'est produite.'));
    }
} else {
    http_response_code(405);
    echo json_encode(array('error' => 'Méthode de requête non autorisée.'));
}
?>

Here my console :

Editor initialized
admin.js:4 MyUploadAdapter initialized
admin.js:8 Upload started
admin.js:30 Request initialized
admin.js:81 Sending request with file: WhatsApp Image 2024-06-11 at 15.21.16.jpeg
admin.js:72 Progress: 55221/55221
admin.js:47 Upload completed
admin.js:49 Response received null
admin.js:52 Error in response No response

If you have any ideas, I’m all ears. I believe there’s something I’ve done incorrectly regarding formats or communication between JS and PHP.

If anyone wants to call me for a screen share to further troubleshoot, I’d be happy to do so.

Thanks again for your time.

I tried everything…

New contributor

Alexis FREDRIKSEN is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

LEAVE A COMMENT