Skip to content

Commit a84f61b

Browse files
committed
Update to 3.1.0
1 parent 08f4f2e commit a84f61b

18 files changed

Lines changed: 1777 additions & 1221 deletions

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# WProofreader Plugin for WordPress Changelog
22

3+
## 3.1.0 - 2026-04-24
4+
5+
* Refactored plugin internals into focused classes while preserving the public WProofreader class, option names, filters, and AJAX action.
6+
* Fixed PHP 8 compatibility issue caused by deprecated create_function() usage in the bundled settings API.
7+
* Enabled local user dictionary access for Free edition and disabled autocomplete for Free edition via supported bundle configuration.
8+
* Enabled AI writing assistant for Premium edition by default.
9+
* Added filterable WProofreader service configuration via wsc_service_config.
10+
* Hardened the settings AJAX language endpoint and post-content cleanup.
11+
312
## 3.0.0 - 2025-10-31
413

514
* Added a few global activation toggles (disabled by default):
@@ -120,4 +129,3 @@ New version of the WebSpellChecker plugin introduces the following updates:
120129
## 1.0
121130

122131
Initial Release of the WebSpellChecker plugin. The multi-language spell checking functionality is available for Visual Editor and excerpt all fields.
123-

assets/classic-environment.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
(function () {
2+
'use strict';
3+
4+
const EDITOR_ID = 'content';
5+
const INSTANCE_ATTRIBUTE = 'data-wsc-instance';
6+
const INIT_DELAY = 100;
7+
const MAX_ATTEMPTS = 50;
8+
9+
let attempts = 0;
10+
11+
function editorInstance() {
12+
if (!window.tinymce || typeof window.tinymce.get !== 'function') {
13+
return null;
14+
}
15+
16+
return window.tinymce.get(EDITOR_ID);
17+
}
18+
19+
function editorIframe(editor) {
20+
const contentArea = editor.getContentAreaContainer && editor.getContentAreaContainer();
21+
22+
return editor.iframeElement ||
23+
(contentArea && contentArea.querySelector('iframe'));
24+
}
25+
26+
function hasInstance(iframe) {
27+
const body = iframe.contentDocument && iframe.contentDocument.body;
28+
29+
return Boolean(body && body.hasAttribute(INSTANCE_ATTRIBUTE));
30+
}
31+
32+
function initializeClassicEditor() {
33+
attempts++;
34+
35+
const editor = editorInstance();
36+
const iframe = editor && editorIframe(editor);
37+
38+
if (!editor || !iframe || !window.WEBSPELLCHECKER || !window.WEBSPELLCHECKER_CONFIG) {
39+
if (attempts < MAX_ATTEMPTS) {
40+
window.setTimeout(initializeClassicEditor, INIT_DELAY);
41+
}
42+
43+
return;
44+
}
45+
46+
if (hasInstance(iframe)) {
47+
return;
48+
}
49+
50+
window.WEBSPELLCHECKER.init(Object.assign({}, window.WEBSPELLCHECKER_CONFIG, {
51+
container: iframe,
52+
}));
53+
}
54+
55+
if (document.readyState === 'loading') {
56+
document.addEventListener('DOMContentLoaded', initializeClassicEditor);
57+
} else {
58+
initializeClassicEditor();
59+
}
60+
})();

assets/environmentChecker.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
window.addEventListener("load", (event) => {
2-
var bundlePath = 'https://svc.webspellchecker.net/spellcheck31/wscbundle/wscbundle.js';
2+
var serviceConfig = window.WSCServiceConfig || {};
3+
var bundlePath = serviceConfig.bundleUrl || 'https://svc.webspellchecker.net/spellcheck31/wscbundle/wscbundle.js';
34

45
function loadScript(doc, src) {
56
const script = doc.createElement('script');
@@ -14,7 +15,7 @@ window.addEventListener("load", (event) => {
1415
}
1516

1617
window.gutenbergIframe = document.querySelector('[name=editor-canvas]');
17-
window.WEBSPELLCHECKER_CONFIG.globalBadge= true;
18+
window.WEBSPELLCHECKER_CONFIG.globalBadge = true;
1819

1920
if (window.gutenbergIframe) {
2021
window.WEBSPELLCHECKER_CONFIG.globalBadge = false;
@@ -23,4 +24,4 @@ window.addEventListener("load", (event) => {
2324
}
2425

2526
loadScript(document, bundlePath);
26-
});
27+
});

assets/gutenberg-environment.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
const isGutenbergActive = () => {
1515
return document.body.classList.contains(SELECTORS.GUTENBERG_PAGE) ||
16-
document.body.classList.contains(SELECTORS.GUTENBERG_IFRAME);
16+
document.body.classList.contains(SELECTORS.GUTENBERG_IFRAME);
1717
};
1818

1919
function isContentEditable(element) {

assets/instance.js

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ jQuery(function ($) {
88
// Guards
99
if (typeof WEBSPELLCHECKER === 'undefined' || typeof ProofreaderInstance === 'undefined') return;
1010

11+
const serviceConfig = window.WSCServiceConfig || {};
1112
const $root = $('#wsc_proofreader');
1213
let $select = $root.find('select[name="wsc_proofreader[slang]"]');
1314
if (!$select.length) return;
@@ -17,16 +18,15 @@ jQuery(function ($) {
1718
// Init WebSpellChecker
1819
const app = WEBSPELLCHECKER.initWebApi({
1920
autoSearch: true,
20-
serviceProtocol: 'https',
21-
serviceHost: 'svc.webspellchecker.net',
22-
servicePath: 'spellcheck31/api',
23-
servicePort: '443',
21+
serviceProtocol: serviceConfig.serviceProtocol || 'https',
22+
serviceHost: serviceConfig.serviceHost || 'svc.webspellchecker.net',
23+
servicePath: serviceConfig.servicePath || 'api',
24+
servicePort: serviceConfig.servicePort || '443',
2425
enableGrammar: enableGrammar,
2526
serviceId: ProofreaderInstance.key_for_proofreader,
2627
lang: ProofreaderInstance.slang,
2728
appType: 'wp_plugin'
2829
});
29-
3030
// Get info → send to WP → render languages
3131
app.getInfo({
3232
success(result) {
@@ -43,25 +43,19 @@ jQuery(function ($) {
4343
// Support both: raw HTML string OR {success:true, data:"..."} OR {success:true, data:{html:"..."}}
4444
const html =
4545
(typeof res === 'string') ? res :
46-
(res && res.success && typeof res.data?.html === 'string') ? res.data.html :
46+
(res && res.success && res.data && typeof res.data.html === 'string') ? res.data.html :
4747
(res && res.success && typeof res.data === 'string') ? res.data : '';
4848

4949
if (!html) {
5050
displayError('Failed to load language list. Invalid response format.');
5151
return;
5252
}
5353

54-
// If a full <select> returned — replace; otherwise swap <option>s
55-
if (/<\s*select[^>]*>/i.test(html)) {
56-
$select.replaceWith(html);
57-
$select = $root.find('select[name="wsc_proofreader[slang]"]');
58-
} else {
59-
$select.html(html);
60-
}
54+
replaceLanguageSelect(html);
6155
})
6256
.fail((jqXHR, textStatus) => {
6357
let msg = 'Failed to load language list.';
64-
if (jqXHR?.responseJSON?.data?.message) {
58+
if (jqXHR && jqXHR.responseJSON && jqXHR.responseJSON.data && jqXHR.responseJSON.data.message) {
6559
msg = jqXHR.responseJSON.data.message;
6660
} else if (textStatus) {
6761
msg += ` (${textStatus})`;
@@ -70,10 +64,29 @@ jQuery(function ($) {
7064
});
7165
},
7266
error(err) {
73-
displayError(err?.message || 'Unexpected initialization error.');
67+
displayError((err && err.message) || 'Unexpected initialization error.');
7468
}
7569
});
7670

71+
function replaceLanguageSelect(html) {
72+
const nodes = $.parseHTML(html, document, false) || [];
73+
const $newSelect = $(nodes).filter('select[name="wsc_proofreader[slang]"]').first();
74+
75+
if ($newSelect.length) {
76+
$select.replaceWith($newSelect);
77+
$select = $root.find('select[name="wsc_proofreader[slang]"]');
78+
return;
79+
}
80+
81+
const $options = $(nodes).filter('option');
82+
if ($options.length) {
83+
$select.empty().append($options);
84+
return;
85+
}
86+
87+
displayError('Failed to load language list. Invalid markup.');
88+
}
89+
7790
function displayError(message) {
7891
const html =
7992
'<div class="error">' +

assets/proofreaderConfig.js

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
(function () {
2-
var WSC_DISABLE_AUTO_SEARCH_IN = [
2+
'use strict';
3+
4+
function asBoolean(value, fallback) {
5+
if (value === true || value === 'true') return true;
6+
if (value === false || value === 'false') return false;
7+
return fallback;
8+
}
9+
10+
function asArray(value) {
11+
return Array.isArray(value) ? value : [];
12+
}
13+
14+
const serviceConfig = window.WSCServiceConfig || {};
15+
const proofreaderConfig = window.WSCProofreaderConfig || {};
16+
17+
const isBadgeEnabled = asBoolean(proofreaderConfig.enableBadgeButton, true);
18+
const badgeActions = isBadgeEnabled
19+
? ['addWord', 'ignoreAll', 'settings', 'toggle', 'proofreadDialog']
20+
: ['addWord', 'ignoreAll', 'settings', 'proofreadDialog'];
21+
22+
const disableAutoSearchIn = [
323
'.wp-block-table__cell-content',
424
'.ui-autocomplete-input',
525
'#wp-link-url',
@@ -16,46 +36,49 @@
1636
'#mailserver_login',
1737
'#ping_sites',
1838
'#permalink_structure',
19-
'.inline-edit-password-input'
39+
'.inline-edit-password-input',
2040
];
2141

2242
window.WEBSPELLCHECKER_CONFIG = {
2343
autoSearch: true,
2444
appType: 'wp_plugin',
25-
serviceProtocol: 'https',
26-
serviceHost: 'svc.webspellchecker.net',
27-
servicePath: 'spellcheck31/api',
28-
servicePort: '443',
29-
enableGrammar: (WSCProofreaderConfig.enableGrammar === 'true'),
30-
settingsSections: WSCProofreaderConfig.settingsSections,
31-
serviceId: WSCProofreaderConfig.key_for_proofreader,
32-
lang: WSCProofreaderConfig.slang,
33-
enableBadgeButton: (WSCProofreaderConfig.enableBadgeButton !== 'true'),
34-
actionItems: (WSCProofreaderConfig.disableBadgeButton === 'true') ? ['addWord', 'ignoreAll', 'settings', 'toggle', 'proofreadDialog'] : ['addWord', 'ignoreAll', 'settings', 'proofreadDialog'],
35-
disableAutoSearchIn: WSC_DISABLE_AUTO_SEARCH_IN,
36-
disableOptionsStorage: [],
37-
globalBadge: (WSCProofreaderConfig.globalBadge === 'true'),
38-
compactBadge: false,
45+
serviceProtocol: serviceConfig.serviceProtocol || 'https',
46+
serviceHost: serviceConfig.serviceHost || 'svc.webspellchecker.net',
47+
servicePath: serviceConfig.servicePath || 'api',
48+
servicePort: serviceConfig.servicePort || '443',
49+
enableGrammar: asBoolean(proofreaderConfig.enableGrammar, false),
50+
aiWritingAssistant: asBoolean(proofreaderConfig.aiWritingAssistant, false),
51+
settingsSections: asArray(proofreaderConfig.settingsSections),
52+
serviceId: proofreaderConfig.key_for_proofreader,
53+
lang: proofreaderConfig.slang,
54+
enableBadgeButton: isBadgeEnabled,
55+
actionItems: badgeActions,
56+
disableAutoSearchIn: disableAutoSearchIn,
57+
disableOptionsStorage: asArray(proofreaderConfig.disableOptionsStorage),
58+
disableDictionariesPreferences: asBoolean(proofreaderConfig.disableDictionariesPreferences, false),
59+
autocomplete: asBoolean(proofreaderConfig.autocomplete, false),
60+
globalBadge: asBoolean(proofreaderConfig.globalBadge, true),
61+
compactBadge: asBoolean(proofreaderConfig.compactBadge, false),
3962
allSuggestionsMode: true,
4063
onLoad: function () {
41-
var self = this;
64+
const instance = this;
4265

4366
this.subscribe('replaceProblem', function () {
4467
try {
45-
var element = self.getContainerNode(),
46-
event = document.createEvent('Event');
47-
48-
event.initEvent('input', true, false);
49-
element.dispatchEvent(event);
68+
const element = instance.getContainerNode();
69+
element.dispatchEvent(new Event('input', { bubbles: true }));
5070
} catch (e) {
71+
// Container may have been detached by the host editor — safe to ignore.
5172
}
5273
});
53-
5474
},
55-
onBeforeAutoSearchInstanceCreate: function (activeElement, options) {
56-
var id = activeElement.element.id;
75+
onBeforeAutoSearchInstanceCreate: function (activeElement) {
76+
const id = activeElement.element.id;
5777
return !(id && id.indexOf('url-input-control') === 0);
58-
}
78+
},
79+
};
80+
81+
if (Array.isArray(proofreaderConfig.generalOptions) && proofreaderConfig.generalOptions.length) {
82+
window.WEBSPELLCHECKER_CONFIG.generalOptions = proofreaderConfig.generalOptions;
5983
}
6084
})();
61-

0 commit comments

Comments
 (0)