267 lines
7.1 KiB
PHP
267 lines
7.1 KiB
PHP
<?php
|
|
|
|
// Отключить табы в редакторе групп полей ACF
|
|
add_filter('acf/field_group/disable_field_settings_tabs', '__return_true');
|
|
|
|
// Просмотры изменяются автоматически, поле в админке доступно только для просмотра.
|
|
add_filter('acf/prepare_field/name=viewers', function ($field) {
|
|
if (is_admin()) {
|
|
$field['disabled'] = 1;
|
|
}
|
|
return $field;
|
|
});
|
|
|
|
|
|
// Страница опций ACF
|
|
if (function_exists('acf_add_options_page')) {
|
|
acf_add_options_page(array(
|
|
'page_title' => 'Общие блоки страниц',
|
|
'menu_title' => 'Блоки страниц',
|
|
'menu_slug' => 'common-blocks',
|
|
'capability' => 'edit_posts',
|
|
'position' => 25,
|
|
'redirect' => false
|
|
));
|
|
}
|
|
|
|
|
|
// Большой редактор ACF
|
|
new AcfWysiwygHeight();
|
|
class AcfWysiwygHeight {
|
|
public function __construct() {
|
|
add_action(
|
|
'acf/render_field_presentation_settings/type=wysiwyg',
|
|
[$this, 'add_height_setting']
|
|
);
|
|
add_filter(
|
|
'acf/prepare_field/type=wysiwyg',
|
|
[$this, 'prepare_wysiwyg_field']
|
|
);
|
|
add_action(
|
|
'acf/input/admin_head',
|
|
[$this, 'print_admin_css']
|
|
);
|
|
}
|
|
public function add_height_setting(array $field): void {
|
|
acf_render_field_setting(
|
|
$field,
|
|
[
|
|
'label' => __('Большой редактор', 'lang'),
|
|
'name' => 'wpf_wysiwyg_large',
|
|
'type' => 'true_false',
|
|
'ui' => 1,
|
|
]
|
|
);
|
|
}
|
|
public function prepare_wysiwyg_field(array $field): array {
|
|
if (empty($field['wpf_wysiwyg_large'])) {
|
|
return $field;
|
|
}
|
|
$field['wrapper']['class'] = trim(
|
|
($field['wrapper']['class'] ?? '') . ' wpf-wysiwyg-large'
|
|
);
|
|
return $field;
|
|
}
|
|
public function print_admin_css(): void {
|
|
?>
|
|
<style>
|
|
.acf-field.wpf-wysiwyg-large .acf-editor-wrap iframe,
|
|
.acf-field.wpf-wysiwyg-large .acf-editor-wrap textarea {
|
|
height: 550px !important;
|
|
min-height: 550px !important;
|
|
}
|
|
</style>
|
|
<?php
|
|
}
|
|
}
|
|
|
|
|
|
// Очистка текста при вставке в визуальный редактор ACF
|
|
function theme_clean_tinymce_html_on_paste($init) {
|
|
$init['paste_preprocess'] = <<<'JS'
|
|
function(plugin, args) {
|
|
const wrapper = document.createElement('div');
|
|
wrapper.innerHTML = args.content;
|
|
const allowedTags = new Set([
|
|
'p',
|
|
'br',
|
|
'strong',
|
|
'em',
|
|
'a',
|
|
'blockquote',
|
|
'ul',
|
|
'ol',
|
|
'li',
|
|
'h2',
|
|
'h3',
|
|
'h4',
|
|
'h5',
|
|
'h6',
|
|
'img',
|
|
'table',
|
|
'thead',
|
|
'tbody',
|
|
'tfoot',
|
|
'tr',
|
|
'th',
|
|
'td'
|
|
]);
|
|
function unwrapElement(element) {
|
|
const parent = element.parentNode;
|
|
if (!parent) return;
|
|
while (element.firstChild) {
|
|
parent.insertBefore(element.firstChild, element);
|
|
}
|
|
parent.removeChild(element);
|
|
}
|
|
function replaceTag(element, newTagName) {
|
|
const newElement = document.createElement(newTagName);
|
|
while (element.firstChild) {
|
|
newElement.appendChild(element.firstChild);
|
|
}
|
|
element.parentNode.replaceChild(newElement, element);
|
|
return newElement;
|
|
}
|
|
function isBoldStyle(element) {
|
|
const style = (element.getAttribute('style') || '').toLowerCase();
|
|
return /font-weight\s*:\s*(bold|[6-9]00)/i.test(style);
|
|
}
|
|
function isItalicStyle(element) {
|
|
const style = (element.getAttribute('style') || '').toLowerCase();
|
|
return /font-style\s*:\s*italic/i.test(style);
|
|
}
|
|
function removeUnsafeHref(element) {
|
|
const href = element.getAttribute('href');
|
|
if (!href) {
|
|
element.removeAttribute('href');
|
|
return;
|
|
}
|
|
const cleanHref = href.trim();
|
|
if (!/^(https?:|mailto:|tel:|#|\/)/i.test(cleanHref)) {
|
|
element.removeAttribute('href');
|
|
}
|
|
}
|
|
function cleanAttributes(element) {
|
|
const tagName = element.tagName.toLowerCase();
|
|
[...element.attributes].forEach((attr) => {
|
|
const attrName = attr.name.toLowerCase();
|
|
if (tagName === 'a' && ['href', 'target', 'rel'].includes(attrName)) {
|
|
return;
|
|
}
|
|
if (tagName === 'img' && ['src', 'alt'].includes(attrName)) {
|
|
return;
|
|
}
|
|
if ((tagName === 'td' || tagName === 'th') && ['colspan', 'rowspan'].includes(attrName)) {
|
|
return;
|
|
}
|
|
if (tagName === 'ol' && attrName === 'start') {
|
|
return;
|
|
}
|
|
element.removeAttribute(attr.name);
|
|
});
|
|
if (tagName === 'a') {
|
|
removeUnsafeHref(element);
|
|
if (element.getAttribute('target') === '_blank') {
|
|
element.setAttribute('rel', 'noopener');
|
|
}
|
|
}
|
|
}
|
|
// Удаляем совсем служебные теги Google Docs / Office.
|
|
wrapper.querySelectorAll('script, style, meta, link, xml').forEach((element) => {
|
|
element.remove();
|
|
});
|
|
// b → strong.
|
|
wrapper.querySelectorAll('b').forEach((element) => {
|
|
replaceTag(element, 'strong');
|
|
});
|
|
// i → em.
|
|
wrapper.querySelectorAll('i').forEach((element) => {
|
|
replaceTag(element, 'em');
|
|
});
|
|
// span разворачиваем с сохранением смысла.
|
|
wrapper.querySelectorAll('span').forEach((span) => {
|
|
const isBold = isBoldStyle(span);
|
|
const isItalic = isItalicStyle(span);
|
|
if (isBold && isItalic) {
|
|
const strong = replaceTag(span, 'strong');
|
|
const em = document.createElement('em');
|
|
while (strong.firstChild) {
|
|
em.appendChild(strong.firstChild);
|
|
}
|
|
strong.appendChild(em);
|
|
return;
|
|
}
|
|
if (isBold) {
|
|
replaceTag(span, 'strong');
|
|
return;
|
|
}
|
|
if (isItalic) {
|
|
replaceTag(span, 'em');
|
|
return;
|
|
}
|
|
unwrapElement(span);
|
|
});
|
|
// В ячейках таблиц p не нужен — оставляем содержимое.
|
|
wrapper.querySelectorAll('th, td').forEach((cell) => {
|
|
cell.querySelectorAll('p').forEach((paragraph) => {
|
|
unwrapElement(paragraph);
|
|
});
|
|
});
|
|
// Удаляем все теги вне белого списка, но оставляем их содержимое.
|
|
[...wrapper.querySelectorAll('*')].reverse().forEach((element) => {
|
|
const tagName = element.tagName.toLowerCase();
|
|
if (!allowedTags.has(tagName)) {
|
|
unwrapElement(element);
|
|
}
|
|
});
|
|
// Чистим атрибуты.
|
|
wrapper.querySelectorAll('*').forEach((element) => {
|
|
cleanAttributes(element);
|
|
});
|
|
args.content = wrapper.innerHTML;
|
|
}
|
|
JS;
|
|
return $init;
|
|
}
|
|
add_filter('tiny_mce_before_init', 'theme_clean_tinymce_html_on_paste');
|
|
|
|
|
|
// Обернуть таблицы во wrapper для скролла
|
|
function wrap_tables($html) {
|
|
if (trim($html) === '' || ! class_exists('DOMDocument')) {
|
|
return $html;
|
|
}
|
|
$old_errors = libxml_use_internal_errors(true);
|
|
$dom = new DOMDocument();
|
|
$dom->loadHTML(
|
|
'<?xml encoding="utf-8" ?><div id="theme-root">' . $html . '</div>',
|
|
LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
|
|
);
|
|
$xpath = new DOMXPath($dom);
|
|
$tables = $xpath->query('//table');
|
|
foreach ($tables as $table) {
|
|
$parent = $table->parentNode;
|
|
if (
|
|
$parent instanceof DOMElement
|
|
&& $parent->tagName === 'div'
|
|
&& strpos(' ' . $parent->getAttribute('class') . ' ', ' table-wrapper ') !== false
|
|
) {
|
|
continue;
|
|
}
|
|
$wrapper = $dom->createElement('div');
|
|
$wrapper->setAttribute('class', 'table-wrapper');
|
|
$parent->insertBefore($wrapper, $table);
|
|
$wrapper->appendChild($table);
|
|
}
|
|
$root = $dom->getElementById('theme-root');
|
|
$output = '';
|
|
if ($root) {
|
|
foreach ($root->childNodes as $child) {
|
|
$output .= $dom->saveHTML($child);
|
|
}
|
|
}
|
|
libxml_clear_errors();
|
|
libxml_use_internal_errors($old_errors);
|
|
return $output;
|
|
}
|