Paste a cropped screenshot (image) from clipboard

Setting up a kind of social ‘wall’ or timeline, for a news site, I want to be able to save time by pasting a cropped screenshot from my clipboard.

To be clear, my process is to copy a cropped screenshot, there is my image. Then - rather than having to save the image, then to navigate to it and to upload it - I just want, quickly, to paste that cropped screenshot image (into a MIGX tv).

…This process works, for instance, to paste screenshots to various social networks, and saves considerable time when posting many images on a daily basis.

I note that by using a rich text tv I can paste a copied image but not a cropped screenshot which is what I need.

Can you specify more precisely what you mean by that?
Is that a functionality of your browser or a browser plugin or some OS functionality?

In what format is this “cropped screenshot”? Usually an image is a binary file, a richtext input expects text. There are ways to store an image as text like a base64 encoded image.

What do you mean by “copied image”? Using Ctrl+C on an image file in a window of the OS?
What richtext editor are you using?

Thank you Harry and apologies for this tardy response. I thought I should try harder before coming back to you, but became distracted. Here is what I have working now, and what is not working…

So from my desktop PC I can copy an image to the clipboard, that’s local functionality.

Then, in a MIGX CMP’s image input, I can paste that image and, submitting the form, the image is saved, its path saved to the image field and it renders in the grid.

There are 2 steps to this: 1. saving the pasted image and 2. renaming it.

Step 1. Saving the pasted image.

I have a plugin that listens for the OnManagerPageBeforeRender event, calling a js file that, in turn, listens for an item to be pasted and then hands it to a php file that actually saves the file. Here’s the js:-

////
// dc_imagePaste.js: Add a listener for pasted images and pass the details to php to save the item.
////

document.addEventListener('paste', function (event) {
  const items = (event.clipboardData || event.originalEvent.clipboardData).item>

  for (const item of items) {
    if (item.type.startsWith('image')) {
      const file = item.getAsFile();
      const formData = new FormData();
      formData.append('image', file);

      fetch('http://mxdc.com/assets/components/dccontent/dc_imagePaste.php', {
        method: 'POST',
        body: formData
      })
      .then(response => response.json()) // Ensure we parse JSON
      .then(data => {
        if (data.success) {
          // Dynamically find the hidden input field
          const inputElement = document.querySelector('input[type="hidden"][id^>

          if (inputElement) {
            inputElement.value = data.image_paste;
            console.log("Image path set successfully:", data.image_paste);
          } else {
            console.error('Error: MIGX-generated input field not found.');
          }
        } else {
          alert('Image upload failed: ' + data.message);
        }
      })
      .catch(err => console.error('Error uploading image:', err));
    }
  }
});

… as you can see that fetches dc_imagePaste.php:-

////
// dc_imagePaste.php: Process pasted images.
////

// Initialize MODX
require_once '../../../manager/config.core.php';
require_once MODX_CORE_PATH . 'model/modx/modx.class.php';
$modx = new modX();
$modx->initialize('mgr');
$modx->getService('error', 'error.modError', '', '');
$modx->addPackage('dccontent', MODX_CORE_PATH . 'components/dccontent/model');

$modx->log(modX::LOG_LEVEL_ERROR, 'imagePaste.php has loaded!');

// Check if an image was uploaded
if (!isset($_FILES['image']) || $_FILES['image']['error'] !== UPLOAD_ERR_OK) {
  echo json_encode(['success' => false, 'message' => 'No image uploaded or an e>
  exit;
}

// Define temporary upload folder
$targetDir = DC_ROOT_PATH . 'assets/images/content/';

// Ensure the directory exists
if (!is_dir($targetDir) && !mkdir($targetDir, 0755, true)) {
  echo json_encode(['success' => false, 'message' => 'Failed to create target d>
  exit;
}

// Generate a temporary filename
$fileExt = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION);
$tempFileName = 'temp_' . uniqid() . '.' . $fileExt;
$targetFile = $targetDir . $tempFileName;

// Save uploaded file
if (!move_uploaded_file($_FILES['image']['tmp_name'], $targetFile)) {
  echo json_encode(['success' => false, 'message' => 'Failed to save the upload>
  exit;
}

// Return temporary file path
$targetPath = 'assets/images/content/' . $tempFileName;
echo json_encode([
  'success' => true,
  'image_paste' => $targetPath, // Store this in the form for renaming later
]);
exit;

So all the above happens when I paste an image, all that works now and, while I daresay it can be improved upon, I hope it’s useful for others too.

Step 2. Renaming the saved image.

I want the image name to match the sanitised input to an accompanying text field in the MIGX form but that cannot be achieved until the form is saved. Therefore I have another plugin, this time triggered by the OnDocFormSave event, which calls some js which again works with php to rename the image.

Actually the problem I have here is that I cannot seem to trigger the plugin at all. It is enabled and it listens for OnDocFormSave but hears nothing. To debug I have a simple logging function in the plugin:-

$modx->log(modX::LOG_LEVEL_ERROR, 'This plugin is being called!');

But no log. Other debugging events do show up so it’s not a permissions problem.

I am doing something wrong with OnDocFormSave, I guess, but am scratching my head because the first plugin listening for OnManagerPageBeforeRender works fine. I wondered if there was a MIGX event I might use instead but cannot find any reference. Certainly it is a MIGX form submission or post-submission hook that I need, if anyone might have any suggestions please?

(I’ll add the js and php for Step 2 to this post when I’ve got that tested-working.)

I’m a bit confused!
Are you using a Template Variable of “Input Type” = migx or is this a CMP (your own “Custom Manager Page”) where MIGXdb is used (to save the data in a custom database table)?


Also, can you maybe edit your post and put all the code in a proper code block?
It’s much easier to read (or copy) code from a code block (and back-ticks will be preserved).

Here is how to create a code block. Put lines with three back-ticks at the start and at the end of your code:

```// line with 3 back-ticks at the beginning
// your code here
```// line with 3 back-ticks at the end

Thank you Harry, most kind of you.

From the MIGX Management page I’m using a MIGX config to create a form in the Manager - manager/?a=index&namespace=migx&configs=dccontent - that includes the image input, populating a custom table, the corresponding package and schema also having been created using the MIGX Package Manager. The image input is one of various form inputs and, from Step 1, its corresponding field records the {path}/temporary_filename to wherever dc_imagepaste.php (Step 1) saved the pasted image.

I thought to use an event-hooked plugin to change the temporary filename to the input of a text field ie that of ‘title’ on the same form, sanitised, when it is saved, but the plugin listening for OnDocFormSave isn’t being triggered at all, even with a simple logging request. I am guessing a processor is another option, again hooked to a form submission, but I thought an event listener might be more straightforward?

Otherwise, here’s that code again, properly escaped, that saves the pasted image in Step 1.

////
// dc_imagePaste.js: Add a listener for pasted images and pass the details to php to save the item.
////

document.addEventListener('paste', function (event) {
  const items = (event.clipboardData || event.originalEvent.clipboardData).item>

  for (const item of items) {
    if (item.type.startsWith('image')) {
      const file = item.getAsFile();
      const formData = new FormData();
      formData.append('image', file);

      fetch('http://mxdc.com/assets/components/dccontent/dc_imagePaste.php', {
        method: 'POST',
        body: formData
      })
      .then(response => response.json()) // Ensure we parse JSON
      .then(data => {
        if (data.success) {
          // Dynamically find the hidden input field
          const inputElement = document.querySelector('input[type="hidden"][id^>

          if (inputElement) {
            inputElement.value = data.image_paste;
            console.log("Image path set successfully:", data.image_paste);
          } else {
            console.error('Error: MIGX-generated input field not found.');
          }
        } else {
          alert('Image upload failed: ' + data.message);
        }
      })
      .catch(err => console.error('Error uploading image:', err));
    }
  }
});

… as you can see that fetches dc_imagePaste.php:-

////
// dc_imagePaste.php: Process pasted images.
////

// Initialize MODX
require_once '../../../manager/config.core.php';
require_once MODX_CORE_PATH . 'model/modx/modx.class.php';
$modx = new modX();
$modx->initialize('mgr');
$modx->getService('error', 'error.modError', '', '');
$modx->addPackage('dccontent', MODX_CORE_PATH . 'components/dccontent/model');

$modx->log(modX::LOG_LEVEL_ERROR, 'imagePaste.php has loaded!');

// Check if an image was uploaded
if (!isset($_FILES['image']) || $_FILES['image']['error'] !== UPLOAD_ERR_OK) {
  echo json_encode(['success' => false, 'message' => 'No image uploaded or an e>
  exit;
}

// Define temporary upload folder
$targetDir = DC_ROOT_PATH . 'assets/images/content/';

// Ensure the directory exists
if (!is_dir($targetDir) && !mkdir($targetDir, 0755, true)) {
  echo json_encode(['success' => false, 'message' => 'Failed to create target d>
  exit;
}

// Generate a temporary filename
$fileExt = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION);
$tempFileName = 'temp_' . uniqid() . '.' . $fileExt;
$targetFile = $targetDir . $tempFileName;

// Save uploaded file
if (!move_uploaded_file($_FILES['image']['tmp_name'], $targetFile)) {
  echo json_encode(['success' => false, 'message' => 'Failed to save the upload>
  exit;
}

// Return temporary file path
$targetPath = 'assets/images/content/' . $tempFileName;
echo json_encode([
  'success' => true,
  'image_paste' => $targetPath, // Store this in the form for renaming later
]);
exit;

The event OnDocFormSave is only invoked when a resource is saved. So using this event would only work if you used a TV.


With a MIGX-CMP, the values get saved to the database with an AJAX-request when the update-window is closed. You either have to customize the update-processor or create a beforesave or aftersave hook snippet.

If you don’t know how to create a custom processor in MIGX, it’s explained in (the first 4 minutes of) this video.

Aah, now it makes sense.

Thank you Harry, not least for your superb videos which I have watched. I’ll follow Customizations and set up a processor.