PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.19
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.19
2.7.1 2.7.2 2.7.0 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 All 141 releases
metasync / public / js / metasync-otto.js

metasync-otto.js in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.19, at public/js/metasync-otto.js

314 lines 13.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* eslint-disable no-console */
2 let headerInsertionCount = 0;
3 let headerTitleTagCount = 0;
4 let headerMetaTagCount = 0;
5 let bodyHtmlTopInsertionCount = 0;
6 let bodyHtmlBottomInsertionCount = 0;
7 let linksUpdationCount = 0;
8 const headersUpdationCount = { h1: 0, h2: 0, h3: 0, h4: 0, h5: 0, h6: 0 };
9 let imagesAltsUpdationCount = 0;
10 let footerHtmlInsertionCount = 0;
11
12 const urlParams = new URLSearchParams(window.location.search);
13 const diagnosticsExist = urlParams.has('diagnostics');
14
15 const consolePrint = strData => {
16 if (diagnosticsExist) {
17 console.log(strData);
18 }
19 };
20
21 // Fixed: Add HTML validation function to prevent XSS while allowing legitimate OTTO content
22 const isValidHTMLContent = (htmlContent) => {
23 if (!htmlContent || typeof htmlContent !== 'string') {
24 return false;
25 }
26
27 // Only reject obviously malicious patterns while allowing legitimate SEO content
28
29 // Reject content with potentially dangerous script content (but allow analytics/tracking)
30 // Block inline javascript execution patterns but allow legitimate tracking scripts
31 if (/<script[^>]*>[\s\S]*?(alert|prompt|confirm|eval|document\.write|document\.writeln)[\s\S]*?<\/script>/i.test(htmlContent)) {
32 return false;
33 }
34
35 // Block dangerous javascript: protocols in links but allow legitimate ones
36 if (/href\s*=\s*["']javascript:(?!void\(0\)|;)[\s\S]*?["']/i.test(htmlContent)) {
37 return false;
38 }
39
40 // Block dangerous event handlers but allow common legitimate ones
41 if (/\son(error|load)\s*=\s*["'][^"']*?(alert|prompt|eval|document\.write)[^"']*?["']/i.test(htmlContent)) {
42 return false;
43 }
44
45 // Block attempts to access sensitive objects
46 if (/(document\.cookie|localStorage|sessionStorage|window\.location\s*=)/i.test(htmlContent)) {
47 return false;
48 }
49
50 return true;
51 };
52
53
54 const replaceMetaData = metaData => {
55 let headerElementExist = false;
56 const { type, name, property, recommended_value } = metaData;
57
58 // Meta descriptions are always enabled by default
59
60 if (type === 'title') {
61 headerElementExist = document.querySelector('title') || false;
62 } else {
63 const metaSelector = `meta[name="${(name || property)?.trim()}"], meta[property="${(name || property)?.trim()}"]`;
64 headerElementExist = document.querySelector(metaSelector) || false;
65 }
66
67 if (headerElementExist) {
68 consolePrint(`Header Meta - ${headerElementExist}`);
69 if (type === 'title') {
70 headerTitleTagCount++;
71 consolePrint(`Replacing existing title content - ${recommended_value}`);
72 // headerElementExist.innerHTML = recommended_value;
73 // Fixed: Use textContent instead of innerHTML to prevent XSS
74 headerElementExist.textContent = recommended_value;
75 } else {
76 consolePrint(`Replacing existing Meta content - ${recommended_value}`);
77 headerMetaTagCount++;
78 headerElementExist.setAttribute('content', recommended_value);
79 }
80 } else {
81 if (type === 'title') {
82 consolePrint(`Header Title Not Found - ${type}`);
83 headerTitleTagCount++;
84 // const titleTag = `<title>${recommended_value}</title>`;
85 // consolePrint(`Inserting Title tag element: ${titleTag}`);
86 // document.head.insertAdjacentHTML('afterbegin', titleTag);
87
88 // Fixed: Create title element safely to prevent XSS
89 const titleElement = document.createElement('title');
90 titleElement.textContent = recommended_value;
91 consolePrint('Inserting Title tag element safely');
92 document.head.insertBefore(titleElement, document.head.firstChild);
93 } else {
94 consolePrint(`Header Meta Not Found - ${type}`);
95
96 // Fixed: Create meta element safely to prevent XSS
97 const metaElement = document.createElement('meta');
98 const metaAttribute = property ? 'property' : 'name';
99 // const metaTag = `<meta ${metaAttribute}="${property ? property : name}" content="${recommended_value}">`;
100
101 metaElement.setAttribute(metaAttribute, property ? property : name);
102 metaElement.setAttribute('content', recommended_value);
103 headerMetaTagCount++;
104 // consolePrint(`Inserting tag element: ${metaTag}`);
105 // document.head.insertAdjacentHTML('afterbegin', metaTag);
106 consolePrint('Inserting meta tag element safely');
107 document.head.insertBefore(metaElement, document.head.firstChild);
108 }
109 }
110 };
111
112 const addAltTextToImages = images => {
113 const allImgsElements = document.querySelectorAll('img');
114 allImgsElements?.forEach(imgElement => {
115 images?.forEach(([imageUrl, altText]) => {
116 Array.from(imgElement?.attributes).forEach(attribute => {
117 if (attribute.value === imageUrl) {
118 imagesAltsUpdationCount++;
119 consolePrint(`Adding/updating alt text for ${imgElement} with: ${altText}`);
120 imgElement.alt = altText;
121 }
122 });
123 });
124 });
125 consolePrint(`Total alt texts updated: ${imagesAltsUpdationCount}`);
126 };
127
128 const applyPageData = pageData => {
129 consolePrint(`Header HTML insertion started: ${pageData.header_html_insertion}`);
130 //const htmlToInject = pageData.header_html_insertion ? `<!-- OTTO SEO Header Injection Start --> ${pageData.header_html_insertion} <!-- OTTO SEO Header Injection Ended --> ` : '';
131 // if (htmlToInject) {
132 // document.head.insertAdjacentHTML('afterbegin', htmlToInject);
133 // headerInsertionCount++;
134 // }
135 consolePrint(`Header HTML insertion ended: ${htmlToInject}`);
136
137 try {
138 consolePrint(`Header replacements started: ${pageData.header_replacements}`);
139 if (Array.isArray(pageData.header_replacements)) {
140 pageData.header_replacements?.forEach(data => {
141 replaceMetaData(data);
142 });
143 }
144 consolePrint(`Header replacements ended: ${pageData.header_replacements}`);
145 } catch (error) {
146 consolePrint(`Header replacements Error: ${error}`);
147 }
148
149 const bodyElement = document.querySelector('body');
150
151 consolePrint(`Body top HTML insertion started: ${pageData.body_top_html_insertion}`);
152 const topHtmlToInject = pageData.body_top_html_insertion ? `<!-- OTTO SEO Body Top Injection Start --> ${pageData.body_top_html_insertion} <!-- OTTO SEO Body Top Injection Ended --> ` : '';
153 if (topHtmlToInject) {
154 bodyHtmlTopInsertionCount++;
155 // bodyElement.insertAdjacentHTML('afterbegin', topHtmlToInject);
156 // Fixed: Validate HTML content before insertion to prevent XSS
157 if (isValidHTMLContent(pageData.body_top_html_insertion)) {
158 bodyElement.insertAdjacentHTML('afterbegin', topHtmlToInject);
159 } else {
160 consolePrint('WARNING: Invalid HTML content detected, skipping injection');
161 }
162 }
163 consolePrint(`Body top HTML insertion ended: ${topHtmlToInject}`);
164
165 consolePrint(`Body bottom HTML insertion started: ${pageData.body_bottom_html_insertion}`);
166 const bottomHtmlToInject = pageData.body_bottom_html_insertion ? `<!-- OTTO SEO Body Bottom Injection Start --> ${pageData.body_bottom_html_insertion} <!-- OTTO SEO Body Bottom Injection Ended --> ` : '';
167 if (bottomHtmlToInject) {
168 bodyHtmlBottomInsertionCount++;
169 // bodyElement.insertAdjacentHTML('beforeend', bottomHtmlToInject);
170 // Fixed: Validate HTML content before insertion to prevent XSS
171 if (isValidHTMLContent(pageData.body_bottom_html_insertion)) {
172 bodyElement.insertAdjacentHTML('beforeend', bottomHtmlToInject);
173 } else {
174 consolePrint('WARNING: Invalid HTML content detected, skipping injection');
175 }
176 }
177 consolePrint(`Body bottom HTML insertion ended: ${bottomHtmlToInject}`);
178
179 consolePrint('Body substitutions started');
180 for (const [bodySubstitutionsKey, data] of Object.entries(pageData.body_substitutions)) {
181 if (bodySubstitutionsKey !== 'images') {
182 consolePrint(`${bodySubstitutionsKey} - updating element value: ${data}`);
183 if (bodySubstitutionsKey === 'links') {
184 const listData = Object.entries(data);
185 if (listData.length) {
186 document.querySelectorAll(`a[href="${listData[0][0]}"]`)?.forEach(element => {
187 linksUpdationCount++;
188 element.href = listData[0][1];
189 });
190 }
191 } else if (bodySubstitutionsKey === 'headings') {
192 data?.forEach(item => {
193 document.querySelectorAll(item.type)?.forEach(element => {
194 if (element.textContent.trim() === item.current_value.trim()) {
195 consolePrint(`${item.type} - heading - ${element.textContent} - Recommended heading - ${item.recommended_value}`);
196 headersUpdationCount[item.type]++;
197 // element.innerHTML = element.innerHTML.replace(item.current_value, item.recommended_value);
198 // Fixed: Safely replace content while preserving formatting and preventing XSS
199 if (item.recommended_value && typeof item.recommended_value === 'string') {
200 // If the recommended value contains HTML tags, validate it first
201 if (/<[^>]+>/.test(item.recommended_value)) {
202 if (isValidHTMLContent(item.recommended_value)) {
203 element.innerHTML = element.innerHTML.replace(item.current_value, item.recommended_value);
204 } else {
205 consolePrint('WARNING: Invalid HTML in heading recommendation, using text only');
206 element.textContent = element.textContent.replace(item.current_value, item.recommended_value);
207 }
208 } else {
209 // Safe text replacement
210 element.textContent = element.textContent.replace(item.current_value, item.recommended_value);
211 }
212 }
213 }
214 });
215 });
216 }
217 }
218 }
219 consolePrint('Body substitutions ended');
220
221 if (pageData.header_replacements?.length > 0) {
222 const canonicalLink = pageData.header_replacements.find(item=> item?.type === 'link' && item?.rel === 'canonical');
223 const element = document.querySelector('link[rel="canonical"]');
224 if (canonicalLink && element) {
225 element.href = canonicalLink?.recommended_value;
226 }
227 }
228
229 consolePrint('Intersection of missing alt tags started');
230 pageData?.body_substitutions?.images && addAltTextToImages(Object.entries(pageData?.body_substitutions?.images));
231 consolePrint('Intersection of missing alt tags ended');
232
233 consolePrint(`Footer HTML insertion started: ${pageData.footer_html_insertion}`);
234 if (pageData.footer_html_insertion) {
235 let footerElement = document.querySelector('footer');
236 if (!footerElement) {
237 consolePrint('Footer element not found');
238 footerElement = document.createElement('footer');
239 document.body.appendChild(footerElement);
240 consolePrint('Footer element added to footer');
241 }
242
243 const footerHtmlToInject = pageData.footer_html_insertion ? `<!-- OTTO SEO Footer Injection Start --> ${pageData.footer_html_insertion} <!-- OTTO SEO Footer Injection Ended --> ` : '';
244 footerHtmlInsertionCount++;
245 // footerElement.insertAdjacentHTML('beforeend', footerHtmlToInject);
246 // consolePrint(`Footer HTML inserted: ${footerHtmlToInject}`);
247 // Fixed: Validate HTML content before insertion to prevent XSS
248 if (isValidHTMLContent(pageData.footer_html_insertion)) {
249 footerElement.insertAdjacentHTML('beforeend', footerHtmlToInject);
250 consolePrint('Footer HTML inserted safely');
251 } else {
252 consolePrint('WARNING: Invalid footer HTML content detected, skipping injection');
253 }
254 }
255 consolePrint(`Footer HTML insertion ended: ${pageData.footer_html_insertion}`);
256
257 consolePrint('*************************************************************');
258 consolePrint('******************** Diagnostics Summary ********************');
259 consolePrint(`Header Insertions: ${headerInsertionCount}`);
260 consolePrint(`Header Title Updated: ${headerTitleTagCount}`);
261 consolePrint(`Body Top HTML Insertions: ${bodyHtmlTopInsertionCount}`);
262 consolePrint(`Body Bottom HTML Insertions: ${bodyHtmlBottomInsertionCount}`);
263 consolePrint(`Footer HTML Insertions: ${footerHtmlInsertionCount}`);
264
265 consolePrint(`Header Meta Tag Updated: ${headerMetaTagCount} out of ${Array.isArray(pageData.header_replacements) ? pageData.header_replacements?.filter(item => item.type !== 'title')?.length : 0}`);
266 consolePrint(`Image Alt Replacements: ${imagesAltsUpdationCount} out of ${('images' in pageData.body_substitutions) ? Object.keys(pageData.body_substitutions?.images)?.length : 0}`);
267 consolePrint(`Link Replacements: ${linksUpdationCount} out of ${('links' in pageData.body_substitutions) ? Object.keys(pageData.body_substitutions?.links)?.length : 0}`);
268 Object.entries(headersUpdationCount).forEach(value => {
269 consolePrint(`${value[0]} headings replaced - ${value[1]}`);
270 });
271 consolePrint('*************************************************************');
272 consolePrint('*************************************************************');
273 };
274
275 const initializeScript = () => {
276 consolePrint('Script initialization');
277 const uuid = document.getElementById('sa-otto')?.getAttribute('data-uuid') || document.getElementById('searchatlas')?.getAttribute('data-uuid') || document.getElementById('sa-dynamic-optimization')?.getAttribute('data-uuid');
278 window.otto_js_installed = true;
279 window.otto_js_uuid = uuid;
280 consolePrint(`UUID: ${uuid}`);
281 // const fetchData = async (pageUrl, uuid) => {
282 // let apiUrl = `https://sa.searchatlas.com/api/v2/otto-url-details/?url=${pageUrl}`;
283 // if (uuid) apiUrl += `&uuid=${uuid}`;
284
285 // try {
286 // const response = await fetch(apiUrl);
287 // if (!response.ok) throw new Error('API call failed.');
288 // const pageData = await response.json();
289 // consolePrint(`API response: ${pageData}`);
290 // applyPageData(pageData);
291 // } catch (error) {
292 // console.error('Fetch error:', error);
293 // }
294 // };
295 jQuery.ajax({
296 url: otto_ajax_object.ajax_url,
297 type: 'POST',
298 data: {
299 action: 'metasyn_otto_ajax',
300 nonce: otto_ajax_object.nonce,
301 post_id: otto_ajax_object.post_id
302 },
303 success: function (response) {
304 applyPageData(pageData);
305 },
306 error: function (xhr, status, error) {
307 console.log('Error:', error);
308 }
309 });
310
311 consolePrint('Script ended');
312 };
313
314 initializeScript();