Highlighted

Script : Replacing text with an image within an archive

Community Beginner ,
Aug 26, 2020

Copy link to clipboard

Copied

Hello everyone!

Is there someone able to help me with a script?

My file has 16 pages, each page has 20-30 frames with a number written inside (i.e. 123456), that is part of the name of the image that has to be placed inside.

I need to replace the texts with actual images and I found multiple scripts that do this. The problem is that images are saved in a photo archive, with multiple sub-folders, and the images are named not only with the numeric code but even with text (i.e. RedBlueCar_year1990_123456.jpg)

 

Thank you in advance for your help and for your time!

 

 

TOPICS
Scripting

Views

78

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more

Script : Replacing text with an image within an archive

Community Beginner ,
Aug 26, 2020

Copy link to clipboard

Copied

Hello everyone!

Is there someone able to help me with a script?

My file has 16 pages, each page has 20-30 frames with a number written inside (i.e. 123456), that is part of the name of the image that has to be placed inside.

I need to replace the texts with actual images and I found multiple scripts that do this. The problem is that images are saved in a photo archive, with multiple sub-folders, and the images are named not only with the numeric code but even with text (i.e. RedBlueCar_year1990_123456.jpg)

 

Thank you in advance for your help and for your time!

 

 

TOPICS
Scripting

Views

79

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Aug 26, 2020 0
Adobe Community Professional ,
Aug 26, 2020

Copy link to clipboard

Copied

Have you looked into using Data Merge to do this? Otherwise, you'd probably need to pay a developer to write something custom for this.  

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 26, 2020 0
Community Beginner ,
Aug 26, 2020

Copy link to clipboard

Copied

Hi brianp311

I have already tried with Data Merge but it does not work because the images are not in the same folder (I have sub folders too) and it actually recognize the full name, not just a part of it.

 

I found an interesting script that does most of the job I need, but it replaces images only if their names match exactly with the written text in the text frame.

 

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 26, 2020 0
Adobe Community Professional ,
Aug 26, 2020

Copy link to clipboard

Copied

Data Merge can look into multiple folders as long as you provide the full path to each image. 

 

Do you want to post the promising script? It could just be a matter of setting up a recursive function to check within each subfolder of a given folder. 

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 26, 2020 0
Community Beginner ,
Aug 26, 2020

Copy link to clipboard

Copied

Yes sure! 

here where I found the script. the name of the script is Place Images

the author is Kasyan Servetsky

http://kasyan.ho.ua/indesign/image/place_images.html

 

and here is the script

 

 

#target indesign
var myDialogResult = CreateDialog();
if (myDialogResult == undefined) exit();
CheckIfChosenFoldersExist();
var myIndFiles = [];
var mySubFolders = [];
CheckFolder(myDialogResult.indFolder);
if (myIndFiles.length == 0) err("No InDesign files have been found in the selected folder and its subfolders.");
var myStartFolder = myDialogResult.imagesFolder.parent;
 
mySubFolders = getSubFolders(myStartFolder);
 
WriteToFile("\r--------------------- Script started -- " + GetDate() + "---------------------\n");
app.scriptPreferences.userInteractionLevel = UserInteractionLevels.neverInteract;
 
for (f = 0; f < myIndFiles.length; f++) {
ProcessIndFile(myIndFiles[f]);
}
 
app.scriptPreferences.userInteractionLevel = UserInteractionLevels.interactWithAll;
app.findGrepPreferences = app.changeGrepPreferences = null;
WriteToFile("--------------------- Script finished -- " + GetDate() + "---------------------\r\r");
alert("All done!");
// ------------------------------------------------- FUNCTIONS ------------------------------------------------- 
function PlaceImages() {
app.findGrepPreferences = app.changeGrepPreferences = null;
app.findGrepPreferences.findWhat = '@.+?@';
var myFoundItems = app.activeDocument.findGrep(); 
for (i = 0; i < myFoundItems.length; i++) {
var myName = myFoundItems[i].contents.replace (/@/g, "");
var myFile = new File(myDialogResult.imagesFolder + "/" + myName);
var myFrame = myFoundItems[i].parentTextFrames[0];
 
if (myFile.exists) {
PlaceIntoFrame(myFrame, myFile);
continue;
}
 
if (!SearchInSubfolders(myName, myFrame)) {
WriteToFile("\tNOT FOUND  -- " + myFile.fsName + "\n");
}
}
}
//--------------------------------------------------------------------------------------------------------------
function SearchInSubfolders(myName, myFrame) {
for (j = 0; j < mySubFolders.length; j++) {
var myFolder = mySubFolders[j];
var myFile = new File(myFolder + "/" + myName);
if (myFile.exists) {
PlaceIntoFrame(myFrame, myFile);
return true;
}
}
return false;
}
//-------------------------------------------------------------------------------------------------------------- 
function PlaceIntoFrame(myFrame, myFile) {
try {
if (myFrame.characters.length < 100) {
myFrame.getElements()[0].place(myFile);
 
switch(myDialogResult.myRadSelected){
case 2:
myFrame.fit(FitOptions.CENTER_CONTENT);
break;
 
case 3:
myFrame.fit(FitOptions.FRAME_TO_CONTENT);
break;
 
case 4:
myFrame.fit(FitOptions.PROPORTIONALLY);
break;
 
default:
// do nothing
}
}
 
WriteToFile("\tPlaced -- " + myFile.fsName+ "\n");
}
catch(e) {
WriteToFile("\tSome error occured while placing -- " + myFile.fsName + "\n");
}
}
//--------------------------------------------------------------------------------------------------------------
function CheckFolder(folder) {
var fileList = folder.getFiles()
for (var i = 0; i < fileList.length; i++) {
var file = fileList[i];
if (file instanceof File && file.name.match(/\.indd$/i)) {
myIndFiles.push(file);
}
else if (file instanceof Folder) {
CheckFolder(file);
}
}
}
//--------------------------------------------------------------------------------------------------------------
function getSubFolders(theFolder) {
var myFileList = theFolder.getFiles();
for (var i = 0; i < myFileList.length; i++) {
var myFile = myFileList[i];
if (myFile instanceof Folder){
mySubFolders.push(myFile.absoluteURI);
getSubFolders(myFile);
}
}
return mySubFolders;
}
//--------------------------------------------------------------------------------------------------------------
function err(e, icon){
alert(e, "Place Images Script", icon);
exit();
}
 
function ProcessIndFile(myFile) {
try {
var myDoc = app.open(myFile);
WriteToFile(myDoc.name + "\n");
var myNewFile = new File(myFile.fsName.replace(/\.indd$/i, "_Backup.indd"));
myFile.copy(myNewFile);
}
catch(e) {
WriteToFile("Cannot open file -- " + myFile.fsName + "\nError: " + e.message + " (Error# " + e.number  + ")\n");
}
PlaceImages();
myDoc = myDoc.save();
myDoc.close();
}
//--------------------------------------------------------------------------------------------------------------
function CreateDialog() {
var myIndFolder, myImagesFolder;
if (app.extractLabel("Kas_PlaceImages_IndFolderPath") != "") {
var myIndFolderPath = app.extractLabel("Kas_PlaceImages_IndFolderPath");
}
else {
var myIndFolderPath = "No folder has been selected";
}
if (app.extractLabel("Kas_PlaceImages_ImageFolderPath") != "") {
var myImageFolderPath = app.extractLabel("Kas_PlaceImages_ImageFolderPath");
}
else {
var myImageFolderPath = "No folder has been selected";
}
var myDialog = new Window('dialog', 'Place Images');
myDialog.orientation = 'row';
myDialog.alignChildren = 'top';
var myPanel = myDialog.add('panel', undefined, 'Choose folders for:');
var myIndFolderStTxt = myPanel.add('statictext', undefined, myIndFolderPath);
var myButtonInd = myPanel.add('button', undefined, 'InDesign files', {name:'indd'});
var myImagesFolderStTxt = myPanel.add('statictext', undefined, myImageFolderPath);
var myButtonImages = myPanel.add('button', undefined, 'Image files', {name:'images'});
 
var myGroup = myDialog.add('group');
myGroup.orientation = 'column';
var myRadioPanel = myGroup.add('panel', undefined, 'After placing:');
myRadioPanel.alignChildren = 'left';
var myRadioBtn1 = myRadioPanel.add('radiobutton', undefined, 'do nothing');
var myRadioBtn2 = myRadioPanel.add('radiobutton', undefined, 'center content');
var myRadioBtn3 = myRadioPanel.add('radiobutton', undefined, 'fit frame to content');
var myRadioBtn4 = myRadioPanel.add('radiobutton', undefined, 'fit content proportionally');
 
if (app.extractLabel("Kas_PlaceImages_RadioSelected") != "") {
eval("myRadioBtn" + app.extractLabel("Kas_PlaceImages_RadioSelected") + ".value= true");
}
else {
myRadioBtn1.value = true;
}
 
var myOkCancelGroup = myGroup.add('group');
myOkCancelGroup.orientation = 'row';
var myOkBtn = myOkCancelGroup.add('button', undefined, 'Place', {name:'ok'});
var myCancelBtn = myOkCancelGroup.add('button', undefined, 'Quit', {name:'cancel'});
 
myButtonInd.onClick = function() {
myIndFolder = Folder.selectDialog ('Chose a folder for InDesign documents');
if (myIndFolder != null) {
myIndFolderStTxt.text = myIndFolder.fsName;
}
}
myButtonImages.onClick = function() {
myImagesFolder = Folder.selectDialog ('Chose a folder for Images');
if (myImagesFolder != null) {
myImagesFolderStTxt.text = myImagesFolder.fsName;
}
}
var myShowDialog = myDialog.show();
 
if (myIndFolder == undefined) {
if (myIndFolderStTxt.text == "No folder has been selected") {
myIndFolder = null;
}
else {
myIndFolder = new Folder(myIndFolderStTxt.text);
}
}
 
if (myImagesFolder == undefined) {
if (myImagesFolderStTxt.text == "No folder has been selected") {
myImagesFolder = null;
}
else {
myImagesFolder = new Folder(myImagesFolderStTxt.text);
}
}
 
var myRadSelected;
if (myRadioBtn1.value) {
myRadSelected = 1;
}
else if(myRadioBtn2.value) {
myRadSelected = 2;
}
else if(myRadioBtn3.value) {
myRadSelected = 3;
}
else if(myRadioBtn4.value) {
myRadSelected = 4;
}
 
app.insertLabel("Kas_PlaceImages_RadioSelected", myRadSelected + "");
app.insertLabel("Kas_PlaceImages_IndFolderPath", myIndFolderStTxt.text);
app.insertLabel("Kas_PlaceImages_ImageFolderPath", myImagesFolderStTxt.text);
 
if (myShowDialog== 1) {
var myResult = {};
myResult.indFolder = myIndFolder;
myResult.imagesFolder = myImagesFolder;
myResult.myRadSelected = myRadSelected;
}
return myResult;
}
//--------------------------------------------------------------------------------------------------------------
function WriteToFile(myText) {
myFile = new File("~/Desktop/Place Images Report.txt");
if ( myFile.exists ) {
myFile.open("e");
myFile.seek(0, 2);
}
else {
myFile.open("w");
}
myFile.write(myText); 
myFile.close();
}
//--------------------------------------------------------------------------------------------------------------
function GetDate() {
var myDate = new Date();
if ((myDate.getYear() - 100) < 10) {
var myYear = "0" + new String((myDate.getYear() - 100));
} else {
var myYear = new String ((myDate.getYear() - 100));
}
var myDateString = (myDate.getMonth() + 1) + "/" + myDate.getDate() + "/" + myYear + " " + myDate.getHours() + ":" + myDate.getMinutes() + ":" + myDate.getSeconds();
return myDateString;
 }
//--------------------------------------------------------------------------------------------------------------
function CheckIfChosenFoldersExist() {
if (myDialogResult.indFolder == null) {
err("No folder has been chosen for InDesign files.", true);
}
else if (myDialogResult.indFolder.constructor.name == "Folder") {
if (!myDialogResult.indFolder.exists) {
err("Folder \"" + myDialogResult.indFolder.fsName + "\" chosen for InDesign files does not exist.", true);
}
}
 
if (myDialogResult.imagesFolder == null) {
err("No folder has been chosen for pictures.", true);
}
else if (myDialogResult.imagesFolder.constructor.name == "Folder") {
if (!myDialogResult.imagesFolder.exists) {
err("Folder \"" + myDialogResult.imagesFolder.fsName + "\" chosen for images does not exist.", true);
}
}
}

 

 

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 26, 2020 0
Adobe Community Professional ,
Aug 26, 2020

Copy link to clipboard

Copied

Some questions

  • How to identify the partial image name? Any number within a textframe has to be treated as a part of the filename?
  • Is the extension of the file the same or it can also vary?

-Manan

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 26, 2020 0
Adobe Community Professional ,
Aug 26, 2020

Copy link to clipboard

Copied

Try the following script, change the path to the base folder within which the file needs to be searched. It will search for the first file within this folder or its subfolders and use the first file whose name contains the numerical value of the textframes in the document. The images are placed as inline image and all the numeric content of every frame is considered a potential filename

function findAndReplace(foldername) 

{ 
    app.findGrepPreferences.findWhat = "\\d+" 
    var nameList = app.activeDocument.findGrep(true) 
    for(var i = nameList.length - 1; i >= 0; i--) 
    {
		var fileName = nameList[i].contents
		var f = findFile(foldername, fileName)
		if(f != undefined)
			nameList[i].place(File(f)) 
    } 
    app.findGrepPreferences = NothingEnum.nothing; 
}

function findFile(foldername, name)
{
	var folder = new Folder(foldername)
	var f = folder.getFiles()
	for(var i = 0; i < f.length; i++)
	{
		if(f[i].constructor.name == "Folder")
		{
			var r = findFile(f[i], name)
			if(r != undefined)
				return r
		}
		var fName = decodeURI(f[i].name)
		if(fName.indexOf(name) != -1)
			return f[i].fsName
	}
}
findAndReplace("/Users/manan/Desktop/test") //Change this to your base folder

-Manan

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 26, 2020 1
Community Beginner ,
Aug 27, 2020

Copy link to clipboard

Copied

Hi Manan,

thanks for your concern to my issue! 

To answer your previous questions:

  • not any number within a textframe has to be considered as filename, but only those within the first _ and the extension .jpg. So for instance if in the textframe there is written RedOldCar1990_123456_789789.jpg only the red part is to be considered in the research
  • the file extension is always .jpg

 

I've tried your script and it's almost there. Could it be possible to limit the research to any 4 or more numbers within the first _ and the file extension .jpg? And don't place the image as an inline image but replace the entire textframe?

I could give you some visual example if you need.

many thanks 

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 27, 2020 0
Adobe Community Professional ,
Aug 27, 2020

Copy link to clipboard

Copied

So if we take an example of your string i.e. RedOldCar1990_123456_789789.jpg

Then the code should find files named like

a_123456_789789.jpg

345_123456_789789fgfhgfhfh.jpg

 

Also could you please elaborate the following with an example

Could it be possible to limit the research to any 4 or more numbers within the first _ and the file extension .jpg

 

-Manan

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 27, 2020 0
Community Beginner ,
Aug 27, 2020

Copy link to clipboard

Copied

The code should search only from the first _ to the extension, nothing before the first "_"

so taking your example, not   a_123456_789789.jpg but _123456_789789.jpg and not 345_123456_789789fgfhgfhfh.jpg but only _123456_789789fgfhgfhfh.jpg

 

I try to explain the second thing. The code should find only files named with 4 or more numbers between the _ and the .jpg. So for i.e. the code should not consider a file named abcd_123.jpg because there are just three caracters within _ and .jpg; On the contrary, It should find a files named  abcd_1234.jpg or abcd_12345.jpg and so on.

 

I could give you some real examples, so maybe it is more clear, just let me know.

thanks

 

 

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 27, 2020 0
Adobe Community Professional ,
Aug 27, 2020

Copy link to clipboard

Copied

What happens for these conditions

abc_4_d_5_7_3_abc.jpg //The total numbers of characters between first _ and jpg are 4 but they are not continuous.

-Manan

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 27, 2020 0
Community Beginner ,
Aug 27, 2020

Copy link to clipboard

Copied

you're right. the 4 characters have to be continuous, however, a case like that it won't happen, 'cause all the images in the archive should have been saved with the same "pattern" such as namenamename_123456.jpg

 

I'm going to try your code and I'll tell you

 

 

 

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 27, 2020 0
Adobe Community Professional ,
Aug 27, 2020

Copy link to clipboard

Copied

Based on what i understood i have modified the code, give it a spin and see how it behaves

 

function checkNumberCount(str)
{
	var reg = new RegExp("\\d")
	var count = 0
	var retval = reg.exec(str)
	while(retval)
	{
		count++
		if(count == 4)
			return true
		str = str.substr(retval.index + 1)
		retval = reg.exec(str)
	}
	return false
}

function findAndReplace(foldername) 

{ 
    app.findGrepPreferences.findWhat = "(_.*\\.jpg)$" 
    var nameList = app.activeDocument.findGrep(true) 
    for(var i = nameList.length - 1; i >= 0; i--) 
    {
		var fileName = nameList[i].contents
		if(!checkNumberCount(fileName))
			continue;
		var f = findFile(foldername, fileName)
		if(f != undefined)
			nameList[i].parentTextFrames[0].place(File(f)) 
    } 
    app.findGrepPreferences = NothingEnum.nothing; 
}

function findFile(foldername, name)
{
	var folder = new Folder(foldername)
	var f = folder.getFiles()
	for(var i = 0; i < f.length; i++)
	{
		if(f[i].constructor.name == "Folder")
		{
			var r = findFile(f[i], name)
			if(r != undefined)
				return r
		}
		var fName = decodeURI(f[i].name)
		if(fName.indexOf(name) != -1)
			return f[i].fsName
	}
}
findAndReplace("/Users/manan/Desktop/test") //Change this to your base folder

 

-Manan

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 27, 2020 0
Community Beginner ,
Aug 27, 2020

Copy link to clipboard

Copied

it doesn't run

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 27, 2020 0
Adobe Community Professional ,
Aug 27, 2020

Copy link to clipboard

Copied

Sorry I had used png instead of jpg as the extension, try again I have edited the code above

-Manan

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 27, 2020 1
Community Beginner ,
Aug 28, 2020

Copy link to clipboard

Copied

It works!! I have just to adjust a couple more things but the code runs!

Thanks Manan, thank you very very much

 

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 28, 2020 0
Explorer ,
Aug 28, 2020

Copy link to clipboard

Copied

hi Manan,
your code checks the folder every time by this command:

findFile(foldername, fileName)
...
folder.getFiles()

this is bad because a folder can contain 10000 or more files,

I would first get a list of files and then place them in frames, 
something like this:

function set_frames(){
	for(var i = 0; i < tf.length; i++){
		if(tf[i].contents in files){
			tf[i].place(files[tf[i].contents])
		}
	}
}

function get_files(path){
	var folder = Folder(path).getFiles()
	for(var i = 0; i < folder.length; i++){
		if(File(folder[i]).constructor.name == 'Folder'){get_files(folder[i])}
		else{
			var f = File(folder[i]).fsName
			if(f.split('.').slice(-1)[0] == extension){
				var cur = f.split('\\').slice(-1)[0].split(delimiter).slice(1).join(delimiter).match(pattern)
				if(cur != null){
					for(var ii = 0; ii < cur.length; ii++){
						files[cur[ii]] = f
					}
				}
			}
		}
	}
}

var tf = app.activeDocument.textFrames.everyItem().getElements()
var files = {}
var pattern = /\d{4,}/gi
var delimiter = '_'
var extension = 'jpg'
get_files('c:/job/test/')
app.doScript('set_frames()', ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'set_frames')

 

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 28, 2020 0
Adobe Community Professional ,
Aug 28, 2020

Copy link to clipboard

Copied

Yes, you have a point, but it depends upon the incoming data, for example, your code would always parse the whole folder and its subfolders once. So for 1000 files, you ran the loop 1000 times for making a collection of files and then a loop to replace, so the order is 1000 + n. The code complexity is constant.

 

In my case, I run the loop to find the first file that matches and return(note I return as soon as I find the result and abort the function). So if the files are found early in the iteration, my approach would lead to much faster execution. It could be made more efficient if I made a collection of found filepaths for the cases where some file is used multiple times in different text frames. So it all depends upon the nature of data arrangement, your code would be balanced in general, mine would be more extreme either way(slowness/speed).

-Manan  

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 28, 2020 0
Explorer ,
Aug 28, 2020

Copy link to clipboard

Copied

No. Folder(path).getFiles() has already received a list of files, it can take minutes or even tens of minutes.
Imagine you are working with a folder containing over 200k images (I worked, I know how long it will take), it makes no sense to execute this command every time

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 28, 2020 0
Adobe Community Professional ,
Aug 30, 2020

Copy link to clipboard

Copied

Yes, now you point the exact problem that would be a bottleneck. In that case, I will still prefer to use my approach, but as you rightly pointed to just call getFiles once. So the following changes need to be done for my solution

  • Call getFiles on the destination folder just once, and use that in the findFile method.
  • Save the files found in the current session so that it can be reused.
  • Every search would first search in the cache, if not found, search in the result of getFiles method and return on match, no further processing

I hope now we can agree to each other's approach 🙂

-Manan

 

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 30, 2020 0