changing curriculum fields in the editor
-
I want to add functionality to the curriculum. And I want this functionality to be displayed in the course editor in the curriculum fields. But I can’t find the code file that is responsible for displaying the fields in the editor. Does anyone know what file this is?
-
Hi daniilvv,
Thank you for contacting us.
To best assist you in adding functionality to the curriculum and displaying it in the course editor, could you please clarify the specific area where you’d like this functionality to appear?
Add new fields within the existing Curriculum tab in the course or add a completely new tab to the single course page?
Knowing this will help us pinpoint the relevant code files and provide you with the most accurate guidance.
Best regards,
Brianvu-tpI want to add new fields to the existing program settings. For example, so that in the course editor, when creating a curriculum, the section has an additional setting that allows you to create a special offline section. And when adding lessons to this section, the lessons are not created but are displayed simply as text that would be displayed in the program. I would do it myself, I am interested in this, but I do not understand how to quickly find required code file
Hi daniilvv,
Thank you for clarifying your requirements.
For adding a special “offline section” with text-based items within the curriculum, you might find it easier to utilize the existing FAQ section in LearnPress rather than directly modifying the core curriculum structure.
Here’s how you can achieve a similar outcome without custom code:
- Navigate to Course Settings of your desired course.
- Go to the Extra Information tab.
- You’ll find a section for FAQ. You can repurpose this section to serve as your “Offline Section.” https://prnt.sc/rHfOODd5LXpQ
- To change the “FAQ” heading to something like “Offline Section” or any other text you prefer, you can use a translation plugin like Loco Translate. This plugin allows you to easily modify the text strings used by LearnPress without directly editing the code.
We hope this approach provides a flexible solution for displaying your offline section information without requiring custom code development.
Best regards,
Brianvu-tpI am not very happy with these additions. So I would like to ask you to tell me which file is responsible for generating the curriculum in the course editor.
Hi daniilvv,
Thank you for your feedback.
Regarding displaying your custom “offline section” on the front-end, you could potentially look into the hook:
apply_filters( 'learn-press/course/html-section-item/class-section-toggle', '...');This hook, located in LearnPress/TemplateHooks/Course/SingleCourseTemplate.php, might allow you to modify the HTML output of the section items on the front-end to display your custom text-based “lessons” within your “offline section.” You would need to conditionally check if a section is your designated “offline section” and then render the text accordingly.
Best regards,
Brianvu-tpI added a snippet that adds switches to each section, but I don’t understand why the value of the switches, no matter how hard I try, is not saved for the course and when I restart the editor, it becomes the default
<?php
/**
* Рабочее решение для переключателей LearnPress
*/
add_action('admin_footer', 'add_working_section_switchers', 9999);
add_action('save_post_lp_course', 'save_course_section_modes');
function save_course_section_modes($post_id) {
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (!current_user_can('edit_post', $post_id)) return;
if (isset($_POST['lp_section_modes'])) {
update_post_meta($post_id, '_lp_section_modes', $_POST['lp_section_modes']);
}
}
function add_working_section_switchers() {
if (get_post_type() !== 'lp_course') return;
global $post;
$section_modes = get_post_meta($post->ID, '_lp_section_modes', true);
if (!is_array($section_modes)) $section_modes = array();
?>
<script type="text/javascript">
(function() {
'use strict';
console.log('[LP Switchers] Starting reliable version');
// Конфигурация
const config = {
sectionClass: 'section',
headerClasses: [
'section-head',
'section-header',
'section-title',
'section-heading'
],
maxAttempts: 30,
checkInterval: 300,
debug: true
};
// Получаем сохраненные режимы из PHP
const savedModes = <?php echo json_encode($section_modes); ?>;
let modesContainer = null;
// Добавляем стили
const style = document.createElement('style');
style.textContent =<br> .lp-mode-switcher {<br> display: inline-block;<br> margin-left: 15px;<br> position: relative;<br> vertical-align: middle;<br> }<br> .lp-section-mode {<br> appearance: none;<br> -webkit-appearance: none;<br> -moz-appearance: none;<br> padding: 6px 30px 6px 12px;<br> border-radius: 20px;<br> border: 1px solid #ddd;<br> background-color: #f5f5f5;<br> color: #555;<br> font-size: 13px;<br> font-weight: 500;<br> cursor: pointer;<br> transition: all 0.3s ease;<br> box-shadow: 0 1px 2px rgba(0,0,0,0.05);<br> background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");<br> background-repeat: no-repeat;<br> background-position: right 8px center;<br> background-size: 14px;<br> }<br> .lp-section-mode:hover {<br> border-color: #bbb;<br> background-color: #eee;<br> }<br> .lp-section-mode:focus {<br> outline: none;<br> border-color: #2271b1;<br> box-shadow: 0 0 0 1px #2271b1;<br> }<br> .lp-section-mode option {<br> padding: 8px 12px;<br> background: #fff;<br> }<br> .lp-section-mode[value="online"] {<br> background-color: #e8f5e9;<br> color: #2e7d32;<br> border-color: #c8e6c9;<br> }<br> .lp-section-mode[value="offline"] {<br> background-color: #ffebee;<br> color: #c62828;<br> border-color: #ffcdd2;<br> }<br> .lp-modes-container {<br> display: none;<br> }<br>;
document.head.appendChild(style);
// Функция поиска заголовка секции (должна быть объявлена перед использованием)
function findSectionHeader(section) {
if (!section) return null;
for (const className of config.headerClasses) {
const header = section.getElementsByClassName(className)[0];
if (header) {
if (config.debug) console.log('Found header by class:', className, header);
return header;
}
}
const headings = section.querySelectorAll('h3, h4');
if (headings.length > 0) {
if (config.debug) console.log('Found header by tag:', headings[0]);
return headings[0];
}
const elements = section.children;
for (let i = 0; i < elements.length; i++) {
const el = elements[i];
if (el.textContent && el.textContent.trim() !== '') {
if (config.debug) console.log('Found header by text content:', el);
return el;
}
}
if (config.debug) console.log('Header not found in section:', section);
return null;
}
// Создаем контейнер для скрытых полей
function createModesContainer() {
const postForm = document.getElementById('post') ||
document.querySelector('form#post') ||
document.body;
modesContainer = document.createElement('div');
modesContainer.className = 'lp-modes-container';
modesContainer.style.display = 'none';
postForm.appendChild(modesContainer);
return modesContainer;
}
// Обновляем скрытое поле
function updateHiddenInput(sectionId, value) {
let input = modesContainer.querySelector(input[name="lp_section_modes[${sectionId}]"]);
if (!input) {
input = document.createElement('input');
input.type = 'hidden';
input.name = 'lp_section_modes[' + sectionId + ']';
modesContainer.appendChild(input);
}
input.value = value;
}
// Функция добавления переключателя
function addSwitcher(section) {
try {
if (!section) return false;
const header = findSectionHeader(section);
if (!header) {
if (config.debug) console.log('Header not found for section:', section);
return false;
}
if (header.querySelector('.lp-mode-switcher')) {
return false;
}
const sectionId = section.dataset.id || section.id || 'section-' + Math.random().toString(36).substr(2, 8);
section.dataset.id = sectionId;
const switcher = document.createElement('div');
switcher.className = 'lp-mode-switcher';
const select = document.createElement('select');
select.className = 'lp-section-mode';
select.name = 'lp_section_modes[' + sectionId + ']';
select.dataset.section = sectionId;
['offline', 'online'].forEach(mode => {
const option = document.createElement('option');
option.value = mode;
option.textContent = mode === 'online' ? 'Онлайн' : 'Офлайн';
select.appendChild(option);
});
select.value = savedModes[sectionId] || 'online';
switcher.appendChild(select);
header.appendChild(switcher);
updateHiddenInput(sectionId, select.value);
select.addEventListener('change', function() {
updateHiddenInput(sectionId, this.value);
});
return true;
} catch (error) {
console.error('[LP Switchers] Error:', error);
return false;
}
}
// Инициализация переключателей
function initSwitchers() {
modesContainer = createModesContainer();
let attempts = 0;
const interval = setInterval(() => {
attempts++;
const sections = document.getElementsByClassName(config.sectionClass);
if (sections.length > 0) {
let addedCount = 0;
Array.from(sections).forEach(section => {
if (addSwitcher(section)) addedCount++;
});
if (addedCount > 0 || attempts >= config.maxAttempts) {
clearInterval(interval);
// Наблюдатель для новых секций
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1 && node.classList.contains(config.sectionClass)) {
addSwitcher(node);
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
} else if (attempts >= config.maxAttempts) {
clearInterval(interval);
}
}, config.checkInterval);
}
// Запуск после полной загрузки
if (document.readyState === 'complete') {
setTimeout(initSwitchers, 500);
} else {
window.addEventListener('load', () => setTimeout(initSwitchers, 500));
}
})();
</script>
<?php
}Hi daniilvv,
Thank you for the update.
The issue of the switch values not being saved upon editor restart indicates that the saving mechanism for your custom field isn’t correctly hooked into the LearnPress section data handling.
We recommend looking into the LP_Section_CURD class, located at wp-content/plugins/learnpress/inc/curds/class-lp-section-curd.php. This class contains the core functions for creating, reading, updating, and deleting sections.
By examining how sections are initialized and saved within class-lp-section-curd.php, you can identify the appropriate hooks to tap into and implement the logic for saving your custom switch values correctly.
Remember to always implement these customizations within your child theme to prevent them from being overwritten during plugin updates.
Best regards,
Brianvu-tpI modified the snippet taking into account the class-lp-section-curd.php file, but the values are still not saved
/**
* Рабочее решение для переключателей LearnPress
*/
add_action('admin_footer', 'add_working_section_switchers', 9999);
add_action('save_post_lp_course', 'save_course_section_modes');
function save_course_section_modes($post_id) {
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (!current_user_can('edit_post', $post_id)) return;
if (isset($_POST['lp_section_modes'])) {
// Убедимся, что данные очищены перед сохранением
$modes = array();
foreach ($_POST['lp_section_modes'] as $section_id => $mode) {
$modes[sanitize_key($section_id)] = in_array($mode, array('online', 'offline')) ? $mode : 'online';
}
update_post_meta($post_id, '_lp_section_modes', $modes);
}
}
function add_working_section_switchers() {
if (get_post_type() !== 'lp_course') return;
global $post;
$section_modes = get_post_meta($post->ID, '_lp_section_modes', true);
if (!is_array($section_modes)) $section_modes = array();
?>
<script type="text/javascript">
(function() {
'use strict';
console.log('[LP Switchers] Starting reliable version');
// Конфигурация
const config = {
sectionClass: 'section',
headerClasses: [
'section-head',
'section-header',
'section-title',
'section-heading'
],
maxAttempts: 30,
checkInterval: 300,
debug: true
};
// Получаем сохраненные режимы из PHP
const savedModes = <?php echo json_encode($section_modes); ?>;
let modesContainer = null;
// Добавляем стили
const style = document.createElement('style');
style.textContent =<br> .lp-mode-switcher {<br> display: inline-block;<br> margin-left: 15px;<br> position: relative;<br> vertical-align: middle;<br> }<br> .lp-section-mode {<br> appearance: none;<br> -webkit-appearance: none;<br> -moz-appearance: none;<br> padding: 6px 30px 6px 12px;<br> border-radius: 20px;<br> border: 1px solid #ddd;<br> background-color: #f5f5f5;<br> color: #555;<br> font-size: 13px;<br> font-weight: 500;<br> cursor: pointer;<br> transition: all 0.3s ease;<br> box-shadow: 0 1px 2px rgba(0,0,0,0.05);<br> background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");<br> background-repeat: no-repeat;<br> background-position: right 8px center;<br> background-size: 14px;<br> }<br> .lp-section-mode:hover {<br> border-color: #bbb;<br> background-color: #eee;<br> }<br> .lp-section-mode:focus {<br> outline: none;<br> border-color: #2271b1;<br> box-shadow: 0 0 0 1px #2271b1;<br> }<br> .lp-section-mode option {<br> padding: 8px 12px;<br> background: #fff;<br> }<br> .lp-section-mode[value="online"] {<br> background-color: #e8f5e9;<br> color: #2e7d32;<br> border-color: #c8e6c9;<br> }<br> .lp-section-mode[value="offline"] {<br> background-color: #ffebee;<br> color: #c62828;<br> border-color: #ffcdd2;<br> }<br> .lp-modes-container {<br> display: none;<br> }<br>;
document.head.appendChild(style);
// Функция поиска заголовка секции
function findSectionHeader(section) {
if (!section) return null;
for (const className of config.headerClasses) {
const header = section.getElementsByClassName(className)[0];
if (header) {
if (config.debug) console.log('Found header by class:', className, header);
return header;
}
}
const headings = section.querySelectorAll('h3, h4');
if (headings.length > 0) {
if (config.debug) console.log('Found header by tag:', headings[0]);
return headings[0];
}
const elements = section.children;
for (let i = 0; i < elements.length; i++) {
const el = elements[i];
if (el.textContent && el.textContent.trim() !== '') {
if (config.debug) console.log('Found header by text content:', el);
return el;
}
}
if (config.debug) console.log('Header not found in section:', section);
return null;
}
// Создаем контейнер для скрытых полей
function createModesContainer() {
const postForm = document.getElementById('post') ||
document.querySelector('form#post') ||
document.body;
modesContainer = document.createElement('div');
modesContainer.className = 'lp-modes-container';
modesContainer.style.display = 'none';
postForm.appendChild(modesContainer);
return modesContainer;
}
// Обновляем скрытое поле
function updateHiddenInput(sectionId, value) {
let input = modesContainer.querySelector(input[name="lp_section_modes[${sectionId}]"]);
if (!input) {
input = document.createElement('input');
input.type = 'hidden';
input.name = 'lp_section_modes[' + sectionId + ']';
modesContainer.appendChild(input);
}
input.value = value;
}
// Получаем ID секции
function getSectionId(section) {
// Пытаемся найти data-id в самой секции
if (section.dataset.id) {
return section.dataset.id;
}
// Пытаемся найти data-id во внутренних элементах
const itemsWithId = section.querySelectorAll('[data-id]');
for (let i = 0; i < itemsWithId.length; i++) {
const item = itemsWithId[i];
if (item.dataset.id) {
return item.dataset.id;
}
}
// Если не нашли, генерируем случайный ID
return 'section-' + Math.random().toString(36).substr(2, 8);
}
// Функция добавления переключателя
function addSwitcher(section) {
try {
if (!section) return false;
const header = findSectionHeader(section);
if (!header) {
if (config.debug) console.log('Header not found for section:', section);
return false;
}
if (header.querySelector('.lp-mode-switcher')) {
return false;
}
const sectionId = getSectionId(section);
section.dataset.id = sectionId;
const switcher = document.createElement('div');
switcher.className = 'lp-mode-switcher';
const select = document.createElement('select');
select.className = 'lp-section-mode';
select.name = 'lp_section_modes[' + sectionId + ']';
select.dataset.section = sectionId;
['offline', 'online'].forEach(mode => {
const option = document.createElement('option');
option.value = mode;
option.textContent = mode === 'online' ? 'Онлайн' : 'Офлайн';
select.appendChild(option);
});
// Используем сохраненное значение или значение по умолчанию
select.value = savedModes[sectionId] || 'online';
switcher.appendChild(select);
header.appendChild(switcher);
updateHiddenInput(sectionId, select.value);
select.addEventListener('change', function() {
updateHiddenInput(sectionId, this.value);
});
return true;
} catch (error) {
console.error('[LP Switchers] Error:', error);
return false;
}
}
// Инициализация переключателей
function initSwitchers() {
modesContainer = createModesContainer();
let attempts = 0;
const interval = setInterval(() => {
attempts++;
const sections = document.getElementsByClassName(config.sectionClass);
if (sections.length > 0) {
let addedCount = 0;
Array.from(sections).forEach(section => {
if (addSwitcher(section)) addedCount++;
});
if (addedCount > 0 || attempts >= config.maxAttempts) {
clearInterval(interval);
// Наблюдатель для новых секций
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1 && node.classList.contains(config.sectionClass)) {
addSwitcher(node);
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
} else if (attempts >= config.maxAttempts) {
clearInterval(interval);
}
}, config.checkInterval);
}
// Запуск после полной загрузки
if (document.readyState === 'complete') {
setTimeout(initSwitchers, 500);
} else {
window.addEventListener('load', () => setTimeout(initSwitchers, 500));
}
})();
</script>
<?php
}Hi daniilvv,
Thank you for the update.
This type of custom development, especially when dealing with data persistence within LearnPress’s curriculum structure, requires a more in-depth debugging process than we can provide through this forum.
To allow us to assist you more effectively and to dive deeper into the specific implementation of your custom snippet, we recommend that you create a support ticket on our official forum. Our development team will be able to work with you directly on this specific customization challenge.
Please create a free support ticket through our official forum.
Please visit , click on the “Support” and follow the instructions in this video to create your ticket
Our team will then be able to provide the precise and comprehensive assistance needed to help you resolve this issue.
Thank you for your understanding!
Best regards,
Brianvu-tp
The topic ‘changing curriculum fields in the editor’ is closed to new replies.