Initial commit
This commit is contained in:
commit
0b7539583b
321
category_courses/README.md
Normal file
321
category_courses/README.md
Normal file
@ -0,0 +1,321 @@
|
||||
# Manual de Usuario - Category Cards Plugin
|
||||
|
||||
## 📋 Descripción General
|
||||
|
||||
El plugin **Category Cards** transforma la visualización de categorías de cursos en Moodle con un sistema de **navegación jerárquica** que permite explorar categorías y cursos de forma intuitiva. Muestra tarjetas elegantes con imágenes personalizadas, colores únicos y información de progreso, adaptándose al tipo de usuario.
|
||||
|
||||
## 🎯 Características Principales
|
||||
|
||||
- **Navegación jerárquica** con breadcrumbs y exploración por niveles
|
||||
- **Tarjetas visuales** con imágenes personalizadas o iniciales generadas automáticamente
|
||||
- **Colores personalizables** por categoría con contraste automático de texto
|
||||
- **Grid responsive** que se adapta automáticamente (1-6 cards por fila)
|
||||
- **Configuraciones granulares** para categorías y cursos por separado
|
||||
- **Gestión de imágenes** integrada con vista previa en tiempo real
|
||||
- **Barras de progreso nativas** de Moodle para consistencia visual
|
||||
- **Indicadores de visibilidad** para cursos ocultos (solo admins)
|
||||
- **Soporte multiidioma** (Español e Inglés)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Instalación
|
||||
|
||||
### Requisitos del Sistema
|
||||
- **Moodle 4.1+** o superior
|
||||
- **PHP 7.4+** o superior
|
||||
- **FontAwesome** (incluido en la mayoría de temas de Moodle)
|
||||
- Permisos de administrador del sitio
|
||||
|
||||
### Pasos de Instalación
|
||||
1. Descargar el plugin desde el repositorio
|
||||
2. Extraer en `/blocks/category_courses/`
|
||||
3. Ir a **Administración del sitio > Notificaciones**
|
||||
4. Seguir el proceso de instalación automática
|
||||
5. El plugin creará automáticamente las tablas necesarias
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configuración Global
|
||||
|
||||
### Acceso a Configuración
|
||||
**Ruta:** `Administración del sitio > Plugins > Bloques > Category Cards`
|
||||
|
||||
### Opciones Disponibles
|
||||
|
||||
#### 🎨 Configuración de Colores *(Nuevas características)*
|
||||
- **Color del Botón**
|
||||
- Color personalizado para botones "EXPLORAR" en tarjetas de categoría
|
||||
- Formato hexadecimal (ej: #951313) o nombres CSS
|
||||
- Si se deja vacío, usa el color primario del tema
|
||||
- Aplicación global en todo el sitio
|
||||
|
||||
- **Color Inicial del Progreso**
|
||||
- Color de inicio para el degradado de barras de progreso
|
||||
- Por defecto: #4285f4 (azul Google)
|
||||
- Formato hexadecimal requerido
|
||||
|
||||
- **Color Final del Progreso**
|
||||
- Color final para el degradado de barras de progreso
|
||||
- Por defecto: #34a853 (verde Google)
|
||||
- Crea degradado suave con el color inicial
|
||||
|
||||
#### 📊 Configuración de Categorías *(Todas activadas por defecto)*
|
||||
- **Mostrar progreso en categorías**
|
||||
- Muestra barra de progreso con cursos completados
|
||||
- Usa componente estándar de Moodle para consistencia visual
|
||||
|
||||
- **Mostrar descripción de categorías**
|
||||
- Muestra descripción de la categoría (truncada automáticamente)
|
||||
- Se extrae de la descripción configurada en Moodle
|
||||
|
||||
- **Mostrar contador de cursos**
|
||||
- Chip con formato "X / Y cursos"
|
||||
- Para administradores: X = completados, Y = total en categoría
|
||||
- Para usuarios: X = completados, Y = inscritos
|
||||
|
||||
#### 📚 Configuración de Cursos *(Todas activadas por defecto)*
|
||||
- **Mostrar progreso en cursos**
|
||||
- Barras de progreso individuales por curso
|
||||
- Solo para usuarios inscritos
|
||||
|
||||
- **Mostrar descripción de cursos**
|
||||
- Resumen/descripción del curso
|
||||
- Extraído del campo summary del curso
|
||||
|
||||
- **Mostrar nombre corto de cursos**
|
||||
- Código o nombre corto del curso
|
||||
- Útil para identificación rápida
|
||||
|
||||
#### 🔄 Opciones de Ordenamiento
|
||||
- **Por defecto** *(Seleccionado)*: Mantiene orden original de Moodle
|
||||
- **Alfabético**: A-Z por nombre de categoría
|
||||
- **Por cantidad de cursos**: Mayor a menor número de cursos
|
||||
- **Por progreso**: Mayor a menor porcentaje completado
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Gestión de Imágenes y Colores
|
||||
|
||||
### Acceso a Gestión Visual
|
||||
**Ruta:** `Administración del sitio > Plugins > Bloques > Category Cards > Manage Category Images`
|
||||
|
||||
### Especificaciones de Imagen
|
||||
|
||||
#### 📸 Tamaños Recomendados
|
||||
- **Resolución óptima:** 550x168px (ratio 3.27:1)
|
||||
- **Resolución alternativa:** 800x244px (ratio 3.28:1)
|
||||
- **Resolución mínima:** 300x180px
|
||||
- **Tamaño de archivo:** Máximo 2MB
|
||||
- **Formatos soportados:** JPG, PNG, GIF, WebP
|
||||
|
||||
#### 🎨 Personalización de Colores
|
||||
- **Color picker visual** integrado para selección intuitiva de colores
|
||||
- **Código hexadecimal** manual (#RRGGBB) con sincronización automática
|
||||
- **Contraste automático** de texto (blanco/negro según luminancia)
|
||||
- **Colores por defecto** generados automáticamente basados en el nombre
|
||||
- **Vista previa en tiempo real** de los cambios aplicados
|
||||
- **Selector de color nativo** del navegador con campo de texto sincronizado
|
||||
- **Colores globales** para botones y barras de progreso configurables desde administración
|
||||
- **Degradados personalizables** en barras de progreso con dos colores
|
||||
|
||||
#### 🔄 Sistema de Imágenes
|
||||
1. **Imagen personalizada:** Subida por administrador en gestión de imágenes
|
||||
2. **Iniciales automáticas:** Generadas con color personalizado cuando no hay imagen
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Configuración del Bloque
|
||||
|
||||
### Agregar el Bloque
|
||||
1. Activar **modo de edición** en la página deseada
|
||||
2. Hacer clic en **"Agregar un bloque"**
|
||||
3. Seleccionar **"Category Cards"**
|
||||
4. El bloque aparecerá en la región seleccionada
|
||||
|
||||
### Configuración Individual del Bloque
|
||||
|
||||
#### Opciones de Categorías
|
||||
- **Título personalizado:** Personaliza el título del bloque
|
||||
- **Mostrar progreso:** Override de configuración global para categorías
|
||||
- **Mostrar descripción:** Override de configuración global para categorías
|
||||
- **Mostrar contador:** Override de configuración global para categorías
|
||||
|
||||
#### Opciones de Cursos
|
||||
- **Mostrar progreso de cursos:** Override de configuración global
|
||||
- **Mostrar descripción de cursos:** Override de configuración global
|
||||
- **Mostrar nombre corto:** Override de configuración global
|
||||
|
||||
#### Configuración General
|
||||
- **Orden de clasificación:** Override de configuración global
|
||||
|
||||
---
|
||||
|
||||
## 👥 Comportamiento por Tipo de Usuario
|
||||
|
||||
### 👨💼 Administradores
|
||||
- **Navegación:** Acceso a todas las categorías del sistema
|
||||
- **Visualización de cursos:** Todos los cursos (visibles y ocultos)
|
||||
- **Indicadores especiales:** Badges y overlays para cursos ocultos
|
||||
- **Progreso:** Solo de cursos donde están inscritos
|
||||
- **Contador:** "Completados / Total en categoría"
|
||||
- **Acceso:** Página de gestión de imágenes y colores
|
||||
|
||||
### 👨🏫 Usuarios Regulares
|
||||
- **Navegación:** Solo categorías donde tienen cursos inscritos y visibles
|
||||
- **Visualización de cursos:** Solo cursos inscritos y visibles
|
||||
- **Progreso:** De todos sus cursos inscritos
|
||||
- **Contador:** "Completados / Inscritos"
|
||||
- **Acceso:** Solo navegación y visualización
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Características Técnicas
|
||||
|
||||
### 📱 Diseño Responsive
|
||||
- **Grid automático:** `repeat(auto-fill, minmax(300px, 350px))`
|
||||
- **Adaptación inteligente:** Se ajusta automáticamente al espacio disponible
|
||||
- **Tablet (768px):** `minmax(250px, 350px)` para mejor aprovechamiento
|
||||
- **Móvil (480px):** Una columna completa con gap optimizado
|
||||
|
||||
### 🧭 Navegación Jerárquica
|
||||
- **Breadcrumbs:** Navegación clara con botones clicables
|
||||
- **Exploración por niveles:** Categorías → Subcategorías → Cursos
|
||||
- **Botón EXPLORAR:** Con icono FontAwesome `fa-folder-open`
|
||||
- **Vista combinada:** Subcategorías y cursos directos en el mismo nivel
|
||||
|
||||
### 🎨 Elementos Visuales
|
||||
- **Barras de progreso:** Template personalizado con degradado configurable
|
||||
- **Botones personalizables:** Color global configurable para botones "EXPLORAR"
|
||||
- **Tarjetas:** CSS Grid responsive con flexbox interno
|
||||
- **Colores del tema:** Usa variables CSS del tema activo con overrides personalizables
|
||||
- **Indicadores de visibilidad:** Badges rojos para cursos ocultos (admins)
|
||||
- **Ícono de progreso:** FontAwesome check-circle con color sincronizado
|
||||
|
||||
### 🌐 Internacionalización
|
||||
- **Idiomas soportados:** Español (es_mx) e Inglés (en)
|
||||
- **Strings localizados:** Todos los textos usan sistema de idiomas de Moodle
|
||||
- **Extensible:** Fácil agregar nuevos idiomas
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Permisos y Capacidades
|
||||
|
||||
### Capacidades del Plugin
|
||||
|
||||
#### `block/category_courses:view`
|
||||
- **Descripción:** Ver el contenido del bloque
|
||||
- **Por defecto:** Todos los usuarios autenticados
|
||||
|
||||
#### `block/category_courses:addinstance`
|
||||
- **Descripción:** Agregar nueva instancia del bloque
|
||||
- **Por defecto:** Profesores editores y administradores
|
||||
|
||||
#### `block/category_courses:myaddinstance`
|
||||
- **Descripción:** Agregar bloque al Dashboard personal
|
||||
- **Por defecto:** Todos los usuarios
|
||||
|
||||
#### `block/category_courses:manage`
|
||||
- **Descripción:** Gestionar imágenes y colores de categorías
|
||||
- **Por defecto:** Solo administradores
|
||||
|
||||
---
|
||||
|
||||
## 📈 Mejores Prácticas
|
||||
|
||||
### 🎨 Diseño Visual
|
||||
- **Imágenes:** Usar 550x168px para resultados óptimos
|
||||
- **Colores:** Mantener paleta coherente por área de conocimiento
|
||||
- **Contraste:** El sistema calcula automáticamente el color de texto
|
||||
- **Consistencia:** Usar estilo visual uniforme en todas las categorías
|
||||
|
||||
### ⚡ Rendimiento
|
||||
- **Límites:** Máximo 12 categorías por bloque recomendado
|
||||
- **Imágenes:** Optimizar antes de subir (550x168px ideal)
|
||||
- **Ubicación:** Colocar estratégicamente en Dashboard o páginas principales
|
||||
|
||||
### 👥 Experiencia de Usuario
|
||||
- **Navegación:** Botón INGRESAR para acceso directo
|
||||
- **Información:** Progreso visual claro y contador de cursos
|
||||
- **Responsive:** Funciona perfectamente en móviles
|
||||
- **Accesibilidad:** Textos alternativos y contraste automático
|
||||
|
||||
---
|
||||
|
||||
## 📞 Información Técnica
|
||||
|
||||
### Versión del Plugin
|
||||
- **Versión actual:** 3.3.1
|
||||
- **Compatibilidad:** Moodle 4.1+
|
||||
- **Última actualización:** Enero 2025
|
||||
- **Licencia:** GPL v3
|
||||
|
||||
### Nuevas Características v3.3.1
|
||||
- **Navegación jerárquica** completa con breadcrumbs
|
||||
- **Configuraciones separadas** para categorías y cursos
|
||||
- **Grid responsive mejorado** con auto-fill y minmax
|
||||
- **Gestión de imágenes restaurada** con vista previa
|
||||
- **Color picker avanzado** con selector visual y campo de texto sincronizado
|
||||
- **Colores de botones configurables** a nivel global
|
||||
- **Barras de progreso personalizables** con degradado configurable
|
||||
- **Integración con tema** usando variables CSS
|
||||
- **Indicadores de visibilidad** para cursos ocultos
|
||||
- **Soporte multiidioma completo** (Español e Inglés)
|
||||
- **Prevención de solicitudes concurrentes** con AbortController
|
||||
|
||||
### Archivos Principales
|
||||
- **Configuración:** `/blocks/category_courses/settings.php`
|
||||
- **Templates:** `/blocks/category_courses/templates/`
|
||||
- **Idiomas:** `/blocks/category_courses/lang/`
|
||||
- **Estilos:** `/blocks/category_courses/styles.css`
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Guía de Navegación
|
||||
|
||||
### Flujo de Navegación
|
||||
1. **Nivel raíz:** Muestra categorías principales donde el usuario tiene acceso
|
||||
2. **Nivel categoría:** Muestra subcategorías + cursos directos de esa categoría
|
||||
3. **Breadcrumbs:** Permite volver a cualquier nivel anterior
|
||||
4. **Exploración:** Botón "EXPLORAR" para navegar hacia subcategorías
|
||||
|
||||
### Casos de Uso
|
||||
- **Dashboard principal:** Navegación desde categorías raíz
|
||||
- **Páginas de curso:** Exploración de categorías relacionadas
|
||||
- **Administración:** Gestión visual de toda la estructura de categorías
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Configuración Avanzada de Colores
|
||||
|
||||
### Jerarquía de Colores
|
||||
1. **Configuración global** (Administración del sitio)
|
||||
2. **Colores del tema** (fallback automático)
|
||||
3. **Valores por defecto** del plugin
|
||||
|
||||
### Colores Configurables
|
||||
|
||||
#### Botones de Categoría
|
||||
- **Campo:** Color del Botón
|
||||
- **Aplicación:** Todos los botones "EXPLORAR" del sitio
|
||||
- **Fallback:** Color primario del tema activo
|
||||
- **Formato:** Hexadecimal (#RRGGBB) o nombres CSS
|
||||
|
||||
#### Barras de Progreso
|
||||
- **Campos:** Color Inicial + Color Final del Progreso
|
||||
- **Aplicación:** Degradado en todas las barras de progreso
|
||||
- **Incluye:** Ícono de check y texto de porcentaje
|
||||
- **Formato:** Solo hexadecimal (#RRGGBB)
|
||||
- **Por defecto:** Azul a verde (estilo Google)
|
||||
|
||||
### Sincronización de Colores
|
||||
- **Carga inicial:** Colores aplicados desde PHP
|
||||
- **Navegación dinámica:** Colores aplicados vía AJAX
|
||||
- **Consistencia:** Mismos colores en toda la experiencia
|
||||
|
||||
---
|
||||
|
||||
*Este manual cubre todas las funcionalidades de la versión 3.3.1 del plugin Category Cards con navegación jerárquica y colores personalizables. El sistema está optimizado para proporcionar una experiencia de navegación intuitiva y visualmente atractiva con total control sobre la apariencia.*
|
||||
3
category_courses/amd/build/colorpicker.min.js
vendored
Normal file
3
category_courses/amd/build/colorpicker.min.js
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
define("block_category_courses/colorpicker",["exports","jquery"],(function(_exports,_jquery){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj};_exports.init=()=>{(0,_jquery.default)(document).ready((function(){const initColorPicker=function(){(0,_jquery.default)('input[name="categorycolor"], input[name*="categorycolor"], input[name="s_block_category_courses_buttoncolor"], input[id*="buttoncolor"], input[name="s_block_category_courses_progresscolor1"], input[name="s_block_category_courses_progresscolor2"]').each((function(){if((0,_jquery.default)(this).next(".color-picker-wrapper").length)return;const $input=(0,_jquery.default)(this),$wrapper=(0,_jquery.default)('<div class="color-picker-wrapper"></div>'),$colorInput=(0,_jquery.default)('<input type="color" class="color-picker-input">');let defaultColor="#667eea";$input.attr("name").includes("buttoncolor")&&(defaultColor="#007bff"),$input.attr("name").includes("progresscolor1")&&(defaultColor="#4285f4"),$input.attr("name").includes("progresscolor2")&&(defaultColor="#34a853"),$colorInput.val($input.val()||defaultColor),$colorInput.on("change",(function(){$input.val((0,_jquery.default)(this).val())})),$input.on("change",(function(){const value=(0,_jquery.default)(this).val();/^#[0-9A-F]{6}$/i.test(value)&&$colorInput.val(value)})),$wrapper.append($colorInput),$input.after($wrapper),$input.css("width","100px"),$colorInput.css({width:"40px",height:"30px",border:"1px solid #ccc","border-radius":"4px","margin-left":"10px",cursor:"pointer"})}))};initColorPicker(),setTimeout(initColorPicker,500),setTimeout(initColorPicker,1e3)}))}}));
|
||||
|
||||
//# sourceMappingURL=colorpicker.min.js.map
|
||||
1
category_courses/amd/build/colorpicker.min.js.map
Normal file
1
category_courses/amd/build/colorpicker.min.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"colorpicker.min.js","sources":["../src/colorpicker.js"],"sourcesContent":["// This file is part of Moodle: https://moodle.org/\n//\n// @module block_category_courses/colorpicker\nimport $ from 'jquery';\n/**\n * Initialize color picker for color fields\n * Works with global plugin settings and category management forms\n */\nexport const init = () => {\n // Wait for DOM to be ready\n $(document).ready(function() {\n // Add color picker to color fields - only global settings and category management\n const selector =\n 'input[name=\"categorycolor\"], input[name*=\"categorycolor\"], ' +\n 'input[name=\"s_block_category_courses_buttoncolor\"], input[id*=\"buttoncolor\"], ' +\n 'input[name=\"s_block_category_courses_progresscolor1\"], input[name=\"s_block_category_courses_progresscolor2\"]';\n const initColorPicker = function() {\n $(selector).each(function() {\n if ($(this).next('.color-picker-wrapper').length) {\n return; // Already initialized\n }\n const $input = $(this);\n // Create color picker wrapper\n const $wrapper = $('<div class=\"color-picker-wrapper\"></div>');\n const $colorInput = $('<input type=\"color\" class=\"color-picker-input\">');\n // Set initial value - use different defaults based on field type\n const isButtonColor = $input.attr('name').includes('buttoncolor');\n const isProgressColor1 = $input.attr('name').includes('progresscolor1');\n const isProgressColor2 = $input.attr('name').includes('progresscolor2');\n let defaultColor = '#667eea';\n if (isButtonColor) {\n defaultColor = '#007bff';\n }\n if (isProgressColor1) {\n defaultColor = '#4285f4';\n }\n if (isProgressColor2) {\n defaultColor = '#34a853';\n }\n $colorInput.val($input.val() || defaultColor);\n // Update text input when color changes\n $colorInput.on('change', function() {\n $input.val($(this).val());\n });\n // Update color picker when text input changes\n $input.on('change', function() {\n const value = $(this).val();\n if (/^#[0-9A-F]{6}$/i.test(value)) {\n $colorInput.val(value);\n }\n });\n // Insert color picker after text input\n $wrapper.append($colorInput);\n $input.after($wrapper);\n // Add some styling\n $input.css('width', '100px');\n $colorInput.css({\n width: '40px',\n height: '30px',\n border: '1px solid #ccc',\n 'border-radius': '4px',\n 'margin-left': '10px',\n cursor: 'pointer',\n });\n });\n };\n // Initialize immediately\n initColorPicker();\n // Also try after a delay for dynamically loaded content\n setTimeout(initColorPicker, 500);\n setTimeout(initColorPicker, 1000);\n });\n};\n"],"names":["document","ready","initColorPicker","each","this","next","length","$input","$wrapper","$colorInput","defaultColor","attr","includes","val","on","value","test","append","after","css","width","height","border","cursor","setTimeout"],"mappings":"wPAQoB,yBAEdA,UAAUC,OAAM,iBAMRC,gBAAkB,+BAHpB,yPAIYC,MAAK,eACT,mBAAEC,MAAMC,KAAK,yBAAyBC,oBAGpCC,QAAS,mBAAEH,MAEXI,UAAW,mBAAE,4CACbC,aAAc,mBAAE,uDAKlBC,aAAe,UAHGH,OAAOI,KAAK,QAAQC,SAAS,iBAK/CF,aAAe,WAJMH,OAAOI,KAAK,QAAQC,SAAS,oBAOlDF,aAAe,WANMH,OAAOI,KAAK,QAAQC,SAAS,oBASlDF,aAAe,WAEnBD,YAAYI,IAAIN,OAAOM,OAASH,cAEhCD,YAAYK,GAAG,UAAU,WACrBP,OAAOM,KAAI,mBAAET,MAAMS,UAGvBN,OAAOO,GAAG,UAAU,iBACVC,OAAQ,mBAAEX,MAAMS,MAClB,kBAAkBG,KAAKD,QACvBN,YAAYI,IAAIE,UAIxBP,SAASS,OAAOR,aAChBF,OAAOW,MAAMV,UAEbD,OAAOY,IAAI,QAAS,SACpBV,YAAYU,IAAI,CACZC,MAAO,OACPC,OAAQ,OACRC,OAAQ,iCACS,oBACF,OACfC,OAAQ,gBAKpBrB,kBAEAsB,WAAWtB,gBAAiB,KAC5BsB,WAAWtB,gBAAiB"}
|
||||
3
category_courses/amd/build/contrast_helper.min.js
vendored
Normal file
3
category_courses/amd/build/contrast_helper.min.js
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
define("block_category_courses/contrast_helper",["exports","jquery"],(function(_exports,_jquery){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj};const getLuminance=color=>{const hex=color.replace("#",""),sRGB=[parseInt(hex.substr(0,2),16)/255,parseInt(hex.substr(2,2),16)/255,parseInt(hex.substr(4,2),16)/255].map((c=>c<=.03928?c/12.92:Math.pow((c+.055)/1.055,2.4)));return.2126*sRGB[0]+.7152*sRGB[1]+.0722*sRGB[2]},getContrastRatio=(color1,color2)=>{const lum1=getLuminance(color1),lum2=getLuminance(color2);return(Math.max(lum1,lum2)+.05)/(Math.min(lum1,lum2)+.05)},getOptimalTextColor=backgroundColor=>getContrastRatio(backgroundColor,"#ffffff")>getContrastRatio(backgroundColor,"#000000")?"#ffffff":"#000000",adjustProgressContainerColors=()=>{(0,_jquery.default)(".category-card").each((function(){const $card=(0,_jquery.default)(this),bgColor=$card.css("background-color");if(!bgColor||"transparent"===bgColor)return;let hexColor=bgColor;if(bgColor.startsWith("rgb")){hexColor="#"+bgColor.match(/\d+/g).map((x=>{const hex=parseInt(x).toString(16);return 1===hex.length?"0"+hex:hex})).join("")}const optimalTextColor=getOptimalTextColor(hexColor),isDarkBg="#ffffff"===optimalTextColor;$card.attr("data-dark-bg",isDarkBg),$card.find(".progress-label, .progress-info span").css("color",optimalTextColor),$card.find(".course-info .course-count").css("color",optimalTextColor);const progressBg=isDarkBg?"rgba(255,255,255,0.2)":"rgba(0,0,0,0.2)";$card.find(".progress-bar").css("background-color",progressBg)})),(0,_jquery.default)(".course-card .custom-progress-container").each((function(){const $container=(0,_jquery.default)(this),bgColor=$container.closest(".course-card").css("background-color");if(!bgColor||"transparent"===bgColor)return;let hexColor=bgColor;if(bgColor.startsWith("rgb")){hexColor="#"+bgColor.match(/\d+/g).map((x=>{const hex=parseInt(x).toString(16);return 1===hex.length?"0"+hex:hex})).join("")}const optimalTextColor=getOptimalTextColor(hexColor);$container.find("span, i").css("color",optimalTextColor);const progressBg="#ffffff"===optimalTextColor?"rgba(255,255,255,0.2)":"rgba(0,0,0,0.2)";$container.next(".progress").css("background-color",progressBg)}))};_exports.init=()=>{(0,_jquery.default)(document).ready((()=>{adjustProgressContainerColors();new MutationObserver((()=>{adjustProgressContainerColors()})).observe(document.body,{childList:!0,subtree:!0}),setTimeout(adjustProgressContainerColors,500),setTimeout(adjustProgressContainerColors,1e3)}))}}));
|
||||
|
||||
//# sourceMappingURL=contrast_helper.min.js.map
|
||||
1
category_courses/amd/build/contrast_helper.min.js.map
Normal file
1
category_courses/amd/build/contrast_helper.min.js.map
Normal file
File diff suppressed because one or more lines are too long
3
category_courses/amd/build/dashboard.min.js
vendored
Normal file
3
category_courses/amd/build/dashboard.min.js
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
define("block_category_courses/dashboard",["exports"],(function(_exports){function handleCardClick(e){if(e.target.closest("a, button, input, select, textarea"))return;const card=this,categoryId=card.dataset.categoryId,clickBehavior=card.dataset.clickBehavior||"category",linkElement=card.querySelector(".card-link"),url=linkElement?linkElement.dataset.url:null;if("courses"===clickBehavior){e.preventDefault();const form=document.createElement("form");form.method="POST",form.action=M.cfg.wwwroot+"/blocks/category_courses/view_courses.php";const cat=document.createElement("input");return cat.type="hidden",cat.name="categoryid",cat.value=categoryId,form.appendChild(cat),document.body.appendChild(form),void form.submit()}url&&(e.preventDefault(),card.classList.add("card-clicked"),setTimeout((()=>{window.location.href=url}),150))}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;_exports.init=()=>{document.querySelectorAll(".category-card").forEach((card=>{card.setAttribute("role","button"),card.setAttribute("tabindex","0"),card.addEventListener("click",handleCardClick),card.addEventListener("keydown",(e=>{"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),handleCardClick.call(card,e))}))}))}}));
|
||||
|
||||
//# sourceMappingURL=dashboard.min.js.map
|
||||
1
category_courses/amd/build/dashboard.min.js.map
Normal file
1
category_courses/amd/build/dashboard.min.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"dashboard.min.js","sources":["../src/dashboard.js"],"sourcesContent":["// This file is part of Moodle: https://moodle.org/\n// @module block_category_courses/dashboard\n\n/**\n * Maneja el clic (o enter/espacio) en la tarjeta de categoría.\n * Envía el categoryid por POST hacia view_courses.php\n * @param {Event} e - El evento de clic o teclado\n */\nfunction handleCardClick(e) {\n // No navegar si se hace clic en un elemento interactivo interno\n if (e.target.closest('a, button, input, select, textarea')) {\n return;\n }\n\n const card = this;\n const categoryId = card.dataset.categoryId;\n const clickBehavior = card.dataset.clickBehavior || 'category';\n const linkElement = card.querySelector('.card-link');\n const url = linkElement ? linkElement.dataset.url : null;\n\n if (clickBehavior === 'courses') {\n e.preventDefault();\n\n // --- Crear formulario invisible para POST ---\n const form = document.createElement('form');\n form.method = 'POST';\n form.action = M.cfg.wwwroot + '/blocks/category_courses/view_courses.php';\n\n const cat = document.createElement('input');\n cat.type = 'hidden';\n cat.name = 'categoryid';\n cat.value = categoryId;\n form.appendChild(cat);\n\n // Opcional: añadir sesskey si tu página valida tokens\n // const sk = document.createElement('input');\n // sk.type = 'hidden';\n // sk.name = 'sesskey';\n // sk.value = M.cfg.sesskey;\n // form.appendChild(sk);\n\n document.body.appendChild(form);\n form.submit();\n return;\n }\n\n // Comportamiento normal (navegación con URL)\n if (url) {\n e.preventDefault();\n card.classList.add('card-clicked');\n setTimeout(() => {\n window.location.href = url;\n }, 150);\n }\n}\n\n/**\n * Inicializa la funcionalidad de tarjetas de categorías.\n */\nexport const init = () => {\n const cards = document.querySelectorAll('.category-card');\n\n cards.forEach((card) => {\n // Accesibilidad\n card.setAttribute('role', 'button');\n card.setAttribute('tabindex', '0');\n\n // Click handler\n card.addEventListener('click', handleCardClick);\n\n // Teclado (Enter o espacio)\n card.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleCardClick.call(card, e);\n }\n });\n });\n};\n"],"names":["handleCardClick","e","target","closest","card","this","categoryId","dataset","clickBehavior","linkElement","querySelector","url","preventDefault","form","document","createElement","method","action","M","cfg","wwwroot","cat","type","name","value","appendChild","body","submit","classList","add","setTimeout","window","location","href","querySelectorAll","forEach","setAttribute","addEventListener","key","call"],"mappings":"mFAQSA,gBAAgBC,MAEjBA,EAAEC,OAAOC,QAAQ,mDAIfC,KAAOC,KACPC,WAAaF,KAAKG,QAAQD,WAC1BE,cAAgBJ,KAAKG,QAAQC,eAAiB,WAC9CC,YAAcL,KAAKM,cAAc,cACjCC,IAAMF,YAAcA,YAAYF,QAAQI,IAAM,QAE9B,YAAlBH,cAA6B,CAC7BP,EAAEW,uBAGIC,KAAOC,SAASC,cAAc,QACpCF,KAAKG,OAAS,OACdH,KAAKI,OAASC,EAAEC,IAAIC,QAAU,kDAExBC,IAAMP,SAASC,cAAc,gBACnCM,IAAIC,KAAO,SACXD,IAAIE,KAAO,aACXF,IAAIG,MAAQlB,WACZO,KAAKY,YAAYJ,KASjBP,SAASY,KAAKD,YAAYZ,WAC1BA,KAAKc,SAKLhB,MACAV,EAAEW,iBACFR,KAAKwB,UAAUC,IAAI,gBACnBC,YAAW,KACPC,OAAOC,SAASC,KAAOtB,MACxB,iGAOS,KACFG,SAASoB,iBAAiB,kBAElCC,SAAS/B,OAEXA,KAAKgC,aAAa,OAAQ,UAC1BhC,KAAKgC,aAAa,WAAY,KAG9BhC,KAAKiC,iBAAiB,QAASrC,iBAG/BI,KAAKiC,iBAAiB,WAAYpC,IAChB,UAAVA,EAAEqC,KAA6B,MAAVrC,EAAEqC,MACvBrC,EAAEW,iBACFZ,gBAAgBuC,KAAKnC,KAAMH"}
|
||||
3
category_courses/amd/build/hierarchy_navigation.min.js
vendored
Normal file
3
category_courses/amd/build/hierarchy_navigation.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3
category_courses/amd/build/manage_images.min.js
vendored
Normal file
3
category_courses/amd/build/manage_images.min.js
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
define("block_category_courses/manage_images",["exports","core/ajax","core/notification","jquery"],(function(_exports,_ajax,_notification,_jquery){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification),_jquery=_interopRequireDefault(_jquery);_exports.init=()=>{(0,_jquery.default)(".save-category").on("click",(function(){const $item=(0,_jquery.default)(this).closest(".category-item"),categoryid=(0,_jquery.default)(this).data("categoryid"),imageurl=$item.find(".image-url").val(),bgcolor=$item.find(".bg-color").val();_ajax.default.call([{methodname:"block_category_courses_save_image",args:{categoryid:categoryid,imageurl:imageurl,bgcolor:bgcolor}}])[0].done((function(response){response.success&&_notification.default.addNotification({message:M.util.get_string("categoryupdated","block_category_courses"),type:"success"})})).fail((function(error){_notification.default.exception(error)}))}))}}));
|
||||
|
||||
//# sourceMappingURL=manage_images.min.js.map
|
||||
1
category_courses/amd/build/manage_images.min.js.map
Normal file
1
category_courses/amd/build/manage_images.min.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"manage_images.min.js","sources":["../src/manage_images.js"],"sourcesContent":["// This file is part of Moodle: https://moodle.org/\n//\n// @module block_category_courses/manage_images\n\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\nimport $ from 'jquery';\n\nexport const init = () => {\n $('.save-category').on('click', function() {\n const $item = $(this).closest('.category-item');\n const categoryid = $(this).data('categoryid');\n const imageurl = $item.find('.image-url').val();\n const bgcolor = $item.find('.bg-color').val();\n Ajax.call([\n {\n methodname: 'block_category_courses_save_image',\n args: {\n categoryid: categoryid,\n imageurl: imageurl,\n bgcolor: bgcolor,\n },\n },\n ])[0]\n .done(function(response) {\n if (response.success) {\n Notification.addNotification({\n message: M.util.get_string('categoryupdated', 'block_category_courses'),\n type: 'success',\n });\n }\n })\n .fail(function(error) {\n Notification.exception(error);\n });\n });\n};\n"],"names":["on","$item","this","closest","categoryid","data","imageurl","find","val","bgcolor","call","methodname","args","done","response","success","addNotification","message","M","util","get_string","type","fail","error","exception"],"mappings":"gcAQoB,yBAChB,kBAAkBA,GAAG,SAAS,iBACxBC,OAAQ,mBAAEC,MAAMC,QAAQ,kBACxBC,YAAa,mBAAEF,MAAMG,KAAK,cAC1BC,SAAWL,MAAMM,KAAK,cAAcC,MACpCC,QAAUR,MAAMM,KAAK,aAAaC,oBACnCE,KAAK,CACR,CACEC,WAAY,oCACZC,KAAM,CACJR,WAAYA,WACZE,SAAUA,SACVG,QAASA,YAGZ,GACAI,MAAK,SAASC,UACTA,SAASC,+BACEC,gBAAgB,CAC3BC,QAASC,EAAEC,KAAKC,WAAW,kBAAmB,0BAC9CC,KAAM,eAIXC,MAAK,SAASC,6BACAC,UAAUD"}
|
||||
10
category_courses/amd/build/rating_system.min.js
vendored
Normal file
10
category_courses/amd/build/rating_system.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
category_courses/amd/build/rating_system.min.js.map
Normal file
1
category_courses/amd/build/rating_system.min.js.map
Normal file
File diff suppressed because one or more lines are too long
73
category_courses/amd/src/colorpicker.js
Normal file
73
category_courses/amd/src/colorpicker.js
Normal file
@ -0,0 +1,73 @@
|
||||
// This file is part of Moodle: https://moodle.org/
|
||||
//
|
||||
// @module block_category_courses/colorpicker
|
||||
import $ from 'jquery';
|
||||
/**
|
||||
* Initialize color picker for color fields
|
||||
* Works with global plugin settings and category management forms
|
||||
*/
|
||||
export const init = () => {
|
||||
// Wait for DOM to be ready
|
||||
$(document).ready(function() {
|
||||
// Add color picker to color fields - only global settings and category management
|
||||
const selector =
|
||||
'input[name="categorycolor"], input[name*="categorycolor"], ' +
|
||||
'input[name="s_block_category_courses_buttoncolor"], input[id*="buttoncolor"], ' +
|
||||
'input[name="s_block_category_courses_progresscolor1"], input[name="s_block_category_courses_progresscolor2"]';
|
||||
const initColorPicker = function() {
|
||||
$(selector).each(function() {
|
||||
if ($(this).next('.color-picker-wrapper').length) {
|
||||
return; // Already initialized
|
||||
}
|
||||
const $input = $(this);
|
||||
// Create color picker wrapper
|
||||
const $wrapper = $('<div class="color-picker-wrapper"></div>');
|
||||
const $colorInput = $('<input type="color" class="color-picker-input">');
|
||||
// Set initial value - use different defaults based on field type
|
||||
const isButtonColor = $input.attr('name').includes('buttoncolor');
|
||||
const isProgressColor1 = $input.attr('name').includes('progresscolor1');
|
||||
const isProgressColor2 = $input.attr('name').includes('progresscolor2');
|
||||
let defaultColor = '#667eea';
|
||||
if (isButtonColor) {
|
||||
defaultColor = '#007bff';
|
||||
}
|
||||
if (isProgressColor1) {
|
||||
defaultColor = '#4285f4';
|
||||
}
|
||||
if (isProgressColor2) {
|
||||
defaultColor = '#34a853';
|
||||
}
|
||||
$colorInput.val($input.val() || defaultColor);
|
||||
// Update text input when color changes
|
||||
$colorInput.on('change', function() {
|
||||
$input.val($(this).val());
|
||||
});
|
||||
// Update color picker when text input changes
|
||||
$input.on('change', function() {
|
||||
const value = $(this).val();
|
||||
if (/^#[0-9A-F]{6}$/i.test(value)) {
|
||||
$colorInput.val(value);
|
||||
}
|
||||
});
|
||||
// Insert color picker after text input
|
||||
$wrapper.append($colorInput);
|
||||
$input.after($wrapper);
|
||||
// Add some styling
|
||||
$input.css('width', '100px');
|
||||
$colorInput.css({
|
||||
width: '40px',
|
||||
height: '30px',
|
||||
border: '1px solid #ccc',
|
||||
'border-radius': '4px',
|
||||
'margin-left': '10px',
|
||||
cursor: 'pointer',
|
||||
});
|
||||
});
|
||||
};
|
||||
// Initialize immediately
|
||||
initColorPicker();
|
||||
// Also try after a delay for dynamically loaded content
|
||||
setTimeout(initColorPicker, 500);
|
||||
setTimeout(initColorPicker, 1000);
|
||||
});
|
||||
};
|
||||
139
category_courses/amd/src/contrast_helper.js
Normal file
139
category_courses/amd/src/contrast_helper.js
Normal file
@ -0,0 +1,139 @@
|
||||
// This file is part of Moodle: https://moodle.org/
|
||||
//
|
||||
// @module block_category_courses/contrast_helper
|
||||
|
||||
import $ from 'jquery';
|
||||
|
||||
/**
|
||||
* Calculate luminance of a color
|
||||
* @param {string} color - Hex color string
|
||||
* @returns {number} Luminance value
|
||||
*/
|
||||
const getLuminance = (color) => {
|
||||
const hex = color.replace('#', '');
|
||||
const r = parseInt(hex.substr(0, 2), 16) / 255;
|
||||
const g = parseInt(hex.substr(2, 2), 16) / 255;
|
||||
const b = parseInt(hex.substr(4, 2), 16) / 255;
|
||||
|
||||
const sRGB = [r, g, b].map(c => {
|
||||
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
||||
});
|
||||
|
||||
return 0.2126 * sRGB[0] + 0.7152 * sRGB[1] + 0.0722 * sRGB[2];
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate contrast ratio between two colors
|
||||
* @param {string} color1 - First color
|
||||
* @param {string} color2 - Second color
|
||||
* @returns {number} Contrast ratio
|
||||
*/
|
||||
const getContrastRatio = (color1, color2) => {
|
||||
const lum1 = getLuminance(color1);
|
||||
const lum2 = getLuminance(color2);
|
||||
const brightest = Math.max(lum1, lum2);
|
||||
const darkest = Math.min(lum1, lum2);
|
||||
return (brightest + 0.05) / (darkest + 0.05);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get optimal text color for background
|
||||
* @param {string} backgroundColor - Background color
|
||||
* @returns {string} Optimal text color (white or black)
|
||||
*/
|
||||
const getOptimalTextColor = (backgroundColor) => {
|
||||
const whiteContrast = getContrastRatio(backgroundColor, '#ffffff');
|
||||
const blackContrast = getContrastRatio(backgroundColor, '#000000');
|
||||
return whiteContrast > blackContrast ? '#ffffff' : '#000000';
|
||||
};
|
||||
|
||||
/**
|
||||
* Adjust progress container colors for better contrast
|
||||
*/
|
||||
const adjustProgressContainerColors = () => {
|
||||
$('.category-card').each(function() {
|
||||
const $card = $(this);
|
||||
const bgColor = $card.css('background-color');
|
||||
|
||||
if (!bgColor || bgColor === 'transparent') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert RGB to hex if needed
|
||||
let hexColor = bgColor;
|
||||
if (bgColor.startsWith('rgb')) {
|
||||
const rgb = bgColor.match(/\d+/g);
|
||||
hexColor = '#' + rgb.map(x => {
|
||||
const hex = parseInt(x).toString(16);
|
||||
return hex.length === 1 ? '0' + hex : hex;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
const optimalTextColor = getOptimalTextColor(hexColor);
|
||||
const isDarkBg = optimalTextColor === '#ffffff';
|
||||
|
||||
// Set data attribute for CSS targeting
|
||||
$card.attr('data-dark-bg', isDarkBg);
|
||||
|
||||
// Apply optimal colors to progress elements
|
||||
$card.find('.progress-label, .progress-info span').css('color', optimalTextColor);
|
||||
$card.find('.course-info .course-count').css('color', optimalTextColor);
|
||||
|
||||
// Adjust progress bar background for better visibility
|
||||
const progressBg = isDarkBg ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.2)';
|
||||
$card.find('.progress-bar').css('background-color', progressBg);
|
||||
});
|
||||
|
||||
// Also handle course cards
|
||||
$('.course-card .custom-progress-container').each(function() {
|
||||
const $container = $(this);
|
||||
const $card = $container.closest('.course-card');
|
||||
const bgColor = $card.css('background-color');
|
||||
|
||||
if (!bgColor || bgColor === 'transparent') {
|
||||
return;
|
||||
}
|
||||
|
||||
let hexColor = bgColor;
|
||||
if (bgColor.startsWith('rgb')) {
|
||||
const rgb = bgColor.match(/\d+/g);
|
||||
hexColor = '#' + rgb.map(x => {
|
||||
const hex = parseInt(x).toString(16);
|
||||
return hex.length === 1 ? '0' + hex : hex;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
const optimalTextColor = getOptimalTextColor(hexColor);
|
||||
|
||||
// Apply optimal colors to progress text and icon
|
||||
$container.find('span, i').css('color', optimalTextColor);
|
||||
|
||||
// Adjust progress bar background
|
||||
const progressBg = optimalTextColor === '#ffffff' ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.2)';
|
||||
$container.next('.progress').css('background-color', progressBg);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize contrast helper
|
||||
*/
|
||||
export const init = () => {
|
||||
$(document).ready(() => {
|
||||
// Initial adjustment
|
||||
adjustProgressContainerColors();
|
||||
|
||||
// Re-adjust when content changes (for dynamic loading)
|
||||
const observer = new MutationObserver(() => {
|
||||
adjustProgressContainerColors();
|
||||
});
|
||||
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
// Also adjust after a delay to catch any late-loading content
|
||||
setTimeout(adjustProgressContainerColors, 500);
|
||||
setTimeout(adjustProgressContainerColors, 1000);
|
||||
});
|
||||
};
|
||||
79
category_courses/amd/src/dashboard.js
Normal file
79
category_courses/amd/src/dashboard.js
Normal file
@ -0,0 +1,79 @@
|
||||
// This file is part of Moodle: https://moodle.org/
|
||||
// @module block_category_courses/dashboard
|
||||
|
||||
/**
|
||||
* Maneja el clic (o enter/espacio) en la tarjeta de categoría.
|
||||
* Envía el categoryid por POST hacia view_courses.php
|
||||
* @param {Event} e - El evento de clic o teclado
|
||||
*/
|
||||
function handleCardClick(e) {
|
||||
// No navegar si se hace clic en un elemento interactivo interno
|
||||
if (e.target.closest('a, button, input, select, textarea')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const card = this;
|
||||
const categoryId = card.dataset.categoryId;
|
||||
const clickBehavior = card.dataset.clickBehavior || 'category';
|
||||
const linkElement = card.querySelector('.card-link');
|
||||
const url = linkElement ? linkElement.dataset.url : null;
|
||||
|
||||
if (clickBehavior === 'courses') {
|
||||
e.preventDefault();
|
||||
|
||||
// --- Crear formulario invisible para POST ---
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = M.cfg.wwwroot + '/blocks/category_courses/view_courses.php';
|
||||
|
||||
const cat = document.createElement('input');
|
||||
cat.type = 'hidden';
|
||||
cat.name = 'categoryid';
|
||||
cat.value = categoryId;
|
||||
form.appendChild(cat);
|
||||
|
||||
// Opcional: añadir sesskey si tu página valida tokens
|
||||
// const sk = document.createElement('input');
|
||||
// sk.type = 'hidden';
|
||||
// sk.name = 'sesskey';
|
||||
// sk.value = M.cfg.sesskey;
|
||||
// form.appendChild(sk);
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
return;
|
||||
}
|
||||
|
||||
// Comportamiento normal (navegación con URL)
|
||||
if (url) {
|
||||
e.preventDefault();
|
||||
card.classList.add('card-clicked');
|
||||
setTimeout(() => {
|
||||
window.location.href = url;
|
||||
}, 150);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inicializa la funcionalidad de tarjetas de categorías.
|
||||
*/
|
||||
export const init = () => {
|
||||
const cards = document.querySelectorAll('.category-card');
|
||||
|
||||
cards.forEach((card) => {
|
||||
// Accesibilidad
|
||||
card.setAttribute('role', 'button');
|
||||
card.setAttribute('tabindex', '0');
|
||||
|
||||
// Click handler
|
||||
card.addEventListener('click', handleCardClick);
|
||||
|
||||
// Teclado (Enter o espacio)
|
||||
card.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
handleCardClick.call(card, e);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
351
category_courses/amd/src/hierarchy_navigation.js
Normal file
351
category_courses/amd/src/hierarchy_navigation.js
Normal file
@ -0,0 +1,351 @@
|
||||
// This file is part of Moodle: https://moodle.org/
|
||||
// @module block_category_courses/hierarchy_navigation
|
||||
import Ajax from 'core/ajax';
|
||||
import Notification from 'core/notification';
|
||||
import Templates from 'core/templates';
|
||||
|
||||
/**
|
||||
* Hierarchical category navigation
|
||||
*/
|
||||
class HierarchyNavigation {
|
||||
constructor() {
|
||||
this.currentCategoryId = 0;
|
||||
this.navigationHistory = [];
|
||||
this.container = null;
|
||||
this.contentContainer = null;
|
||||
this.isNavigating = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the hierarchical navigation
|
||||
*/
|
||||
init() {
|
||||
this.container = document.querySelector('.hierarchy-navigation');
|
||||
if (!this.container) {
|
||||
return;
|
||||
}
|
||||
this.currentCategoryId = parseInt(this.container.dataset.currentCategory, 10) || 0;
|
||||
this.contentContainer = this.container.querySelector('.navigation-content');
|
||||
this.setupEventListeners();
|
||||
this.setupHistoryNavigation();
|
||||
this.checkUrlCategory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up event listeners
|
||||
*/
|
||||
setupEventListeners() {
|
||||
// Event delegation para tarjetas de categoría
|
||||
this.container.addEventListener('click', (e) => {
|
||||
const categoryCard = e.target.closest('[data-action="navigate-category"]');
|
||||
const breadcrumbLink = e.target.closest('[data-action="navigate-breadcrumb"]');
|
||||
const categoryBtn = e.target.closest('.category-navigate-btn');
|
||||
|
||||
if (categoryCard || categoryBtn) {
|
||||
e.preventDefault();
|
||||
const categoryId = categoryCard ? categoryCard.dataset.categoryId : categoryBtn.dataset.categoryId;
|
||||
if (!categoryId || isNaN(categoryId)) {
|
||||
return;
|
||||
}
|
||||
// Agregar feedback visual
|
||||
if (categoryCard) {
|
||||
categoryCard.classList.add('navigating');
|
||||
}
|
||||
this.navigateToCategory(parseInt(categoryId, 10));
|
||||
}
|
||||
|
||||
if (breadcrumbLink) {
|
||||
e.preventDefault();
|
||||
const categoryId = breadcrumbLink.dataset.categoryid;
|
||||
if (!categoryId || isNaN(categoryId)) {
|
||||
return;
|
||||
}
|
||||
this.navigateToCategory(parseInt(categoryId, 10));
|
||||
}
|
||||
});
|
||||
|
||||
// Soporte para teclado
|
||||
this.container.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
const categoryCard = e.target.closest('[data-action="navigate-category"]');
|
||||
if (categoryCard) {
|
||||
e.preventDefault();
|
||||
// Agregar feedback visual
|
||||
categoryCard.classList.add('navigating');
|
||||
const categoryId = categoryCard.dataset.categoryId;
|
||||
if (!categoryId || isNaN(categoryId)) {
|
||||
return;
|
||||
}
|
||||
this.navigateToCategory(parseInt(categoryId, 10));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to a specific category
|
||||
* @param {number} categoryId - Category ID
|
||||
* @param {boolean} updateHistory - Whether to update browser history
|
||||
*/
|
||||
async navigateToCategory(categoryId, updateHistory = true) {
|
||||
// Bloquear completamente si ya está navegando
|
||||
if (this.isNavigating) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Para navegación a raíz (categoryId = 0), siempre permitir navegación
|
||||
if (categoryId !== 0 && categoryId === this.currentCategoryId) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.isNavigating = true;
|
||||
this.showLoading();
|
||||
// Scroll al inicio del bloque después de cargar el contenido
|
||||
this.scrollToBlockStart();
|
||||
// Obtener datos de la categoría
|
||||
const data = await this.getCategoryData(categoryId);
|
||||
// Actualizar contenido
|
||||
await this.updateContent(data);
|
||||
|
||||
// Actualizar estado
|
||||
this.currentCategoryId = categoryId;
|
||||
|
||||
// Actualizar historial del navegador
|
||||
if (updateHistory) {
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set('categoryid', categoryId);
|
||||
history.pushState({categoryId}, '', url);
|
||||
}
|
||||
} catch (error) {
|
||||
// En caso de error, liberar el bloqueo
|
||||
this.hideLoading();
|
||||
this.isNavigating = false;
|
||||
Notification.exception(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets category data via AJAX
|
||||
* @param {number} categoryId - Category ID
|
||||
* @returns {Promise} Category data
|
||||
*/
|
||||
getCategoryData(categoryId) {
|
||||
return Ajax.call([
|
||||
{
|
||||
methodname: 'block_category_courses_get_category_data',
|
||||
args: {categoryid: categoryId},
|
||||
},
|
||||
])[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the navigation content
|
||||
* @param {Object} data - Category data
|
||||
*/
|
||||
async updateContent(data) {
|
||||
// Actualizar breadcrumbs
|
||||
if (data.breadcrumbs && data.breadcrumbs.length > 0) {
|
||||
const breadcrumbsHtml = await Templates.render('block_category_courses/breadcrumbs', {breadcrumbs: data.breadcrumbs});
|
||||
const breadcrumbsContainer = this.container.querySelector('.breadcrumb-nav');
|
||||
if (breadcrumbsContainer) {
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = breadcrumbsHtml;
|
||||
breadcrumbsContainer.replaceWith(tempDiv.firstElementChild);
|
||||
} else {
|
||||
// Insertar breadcrumbs al inicio si no existen
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = breadcrumbsHtml;
|
||||
this.container.insertBefore(tempDiv.firstElementChild, this.container.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
const contentContainer = this.container.querySelector('.navigation-content');
|
||||
if (!contentContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Limpiar contenido anterior con transición suave
|
||||
contentContainer.style.opacity = '0';
|
||||
|
||||
// Esperar a que termine la transición antes de limpiar
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
|
||||
contentContainer.innerHTML = '';
|
||||
|
||||
try {
|
||||
// Renderizar categorías si las hay
|
||||
if (data.hascategories && data.categories.length > 0) {
|
||||
const categoriesContainer = document.createElement('div');
|
||||
categoriesContainer.className = 'level-content level-categories';
|
||||
categoriesContainer.setAttribute('data-level', data.currentcategoryid);
|
||||
const cardsContainer = document.createElement('div');
|
||||
cardsContainer.className = 'category-cards-container';
|
||||
|
||||
// Renderizar categorías concurrentemente para mejor rendimiento
|
||||
const categoryPromises = data.categories.map((category) => {
|
||||
category.config = data.config;
|
||||
return Templates.render('block_category_courses/category_card_hierarchical', category);
|
||||
});
|
||||
const categoryHtmls = await Promise.all(categoryPromises);
|
||||
|
||||
categoryHtmls.forEach((html) => {
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = html;
|
||||
cardsContainer.appendChild(tempDiv.firstElementChild);
|
||||
});
|
||||
categoriesContainer.appendChild(cardsContainer);
|
||||
contentContainer.appendChild(categoriesContainer);
|
||||
}
|
||||
|
||||
// Renderizar cursos si los hay
|
||||
if (data.hascourses && data.courses.length > 0) {
|
||||
const coursesContainer = document.createElement('div');
|
||||
coursesContainer.className = 'level-content level-courses';
|
||||
coursesContainer.setAttribute('data-level', data.currentcategoryid);
|
||||
const coursesGrid = document.createElement('div');
|
||||
coursesGrid.className = 'category-courses-grid';
|
||||
|
||||
// Renderizar cursos concurrentemente para mejor rendimiento
|
||||
const coursePromises = data.courses.map((course) =>
|
||||
Templates.render('block_category_courses/course_card', course)
|
||||
);
|
||||
const courseHtmls = await Promise.all(coursePromises);
|
||||
|
||||
courseHtmls.forEach((html) => {
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = html;
|
||||
coursesGrid.appendChild(tempDiv.firstElementChild);
|
||||
});
|
||||
coursesContainer.appendChild(coursesGrid);
|
||||
contentContainer.appendChild(coursesContainer);
|
||||
}
|
||||
|
||||
// Mensaje si no hay contenido
|
||||
if (!data.hascategories && !data.hascourses) {
|
||||
const noContentDiv = document.createElement('div');
|
||||
noContentDiv.className = 'no-content';
|
||||
noContentDiv.textContent = data.nocontent || '';
|
||||
contentContainer.appendChild(noContentDiv);
|
||||
}
|
||||
|
||||
// Restaurar opacidad con transición suave
|
||||
contentContainer.style.opacity = '1';
|
||||
|
||||
// Initialize rating system for new course cards
|
||||
this.initializeRatingSystem();
|
||||
|
||||
// Re-initialize the main rating system for new elements
|
||||
if (window.require) {
|
||||
require(['block_category_courses/rating_system'], function(ratingSystem) {
|
||||
ratingSystem.init();
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// Si hay error durante la actualización, restaurar opacidad
|
||||
contentContainer.style.opacity = '1';
|
||||
throw error;
|
||||
} finally {
|
||||
// Liberar bloqueo solo después de que TODO el renderizado esté completo
|
||||
this.hideLoading();
|
||||
this.isNavigating = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows loading indicator
|
||||
*/
|
||||
showLoading() {
|
||||
if (this.contentContainer) {
|
||||
this.contentContainer.classList.add('loading');
|
||||
}
|
||||
// Agregar clase al contenedor principal para deshabilitar interacciones
|
||||
this.container.classList.add('navigating');
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides loading indicator
|
||||
*/
|
||||
hideLoading() {
|
||||
if (this.contentContainer) {
|
||||
this.contentContainer.classList.remove('loading');
|
||||
}
|
||||
// Remover clase del contenedor principal
|
||||
this.container.classList.remove('navigating');
|
||||
}
|
||||
/**
|
||||
* Sets up browser history navigation
|
||||
*/
|
||||
setupHistoryNavigation() {
|
||||
// Escuchar eventos de navegación del navegador
|
||||
window.addEventListener('popstate', (event) => {
|
||||
if (event.state && event.state.categoryId !== undefined) {
|
||||
this.navigateToCategory(event.state.categoryId, false);
|
||||
}
|
||||
});
|
||||
|
||||
// Establecer estado inicial
|
||||
const url = new URL(window.location);
|
||||
const initialCategoryId = url.searchParams.get('categoryid') || this.currentCategoryId;
|
||||
history.replaceState({categoryId: parseInt(initialCategoryId, 10)}, '', url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks URL for category parameter and navigates if needed
|
||||
*/
|
||||
checkUrlCategory() {
|
||||
const url = new URL(window.location);
|
||||
const urlCategoryId = parseInt(url.searchParams.get('categoryid'), 10);
|
||||
|
||||
if (urlCategoryId && urlCategoryId !== this.currentCategoryId) {
|
||||
this.navigateToCategory(urlCategoryId, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls to the beginning of the block after content loads
|
||||
*/
|
||||
scrollToBlockStart() {
|
||||
const blockSection = document.body.querySelector('section.block_category_courses');
|
||||
if (blockSection) {
|
||||
const parentElement = blockSection.parentElement;
|
||||
if (parentElement) {
|
||||
parentElement.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize rating system for newly rendered course cards
|
||||
*/
|
||||
initializeRatingSystem() {
|
||||
// Initialize user ratings display
|
||||
this.container.querySelectorAll('.rating-stars').forEach((container) => {
|
||||
const userRating = parseInt(container.dataset.userRating) || 0;
|
||||
const stars = container.querySelectorAll('.rating-star');
|
||||
|
||||
// Clear existing classes first
|
||||
stars.forEach((star) => {
|
||||
star.classList.remove('selected', 'hover');
|
||||
});
|
||||
|
||||
// Apply selected class based on user rating
|
||||
stars.forEach((star, index) => {
|
||||
if (index < userRating) {
|
||||
star.classList.add('selected');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the hierarchical navigation
|
||||
*/
|
||||
export const init = () => {
|
||||
const navigation = new HierarchyNavigation();
|
||||
navigation.init();
|
||||
};
|
||||
37
category_courses/amd/src/manage_images.js
Normal file
37
category_courses/amd/src/manage_images.js
Normal file
@ -0,0 +1,37 @@
|
||||
// This file is part of Moodle: https://moodle.org/
|
||||
//
|
||||
// @module block_category_courses/manage_images
|
||||
|
||||
import Ajax from 'core/ajax';
|
||||
import Notification from 'core/notification';
|
||||
import $ from 'jquery';
|
||||
|
||||
export const init = () => {
|
||||
$('.save-category').on('click', function() {
|
||||
const $item = $(this).closest('.category-item');
|
||||
const categoryid = $(this).data('categoryid');
|
||||
const imageurl = $item.find('.image-url').val();
|
||||
const bgcolor = $item.find('.bg-color').val();
|
||||
Ajax.call([
|
||||
{
|
||||
methodname: 'block_category_courses_save_image',
|
||||
args: {
|
||||
categoryid: categoryid,
|
||||
imageurl: imageurl,
|
||||
bgcolor: bgcolor,
|
||||
},
|
||||
},
|
||||
])[0]
|
||||
.done(function(response) {
|
||||
if (response.success) {
|
||||
Notification.addNotification({
|
||||
message: M.util.get_string('categoryupdated', 'block_category_courses'),
|
||||
type: 'success',
|
||||
});
|
||||
}
|
||||
})
|
||||
.fail(function(error) {
|
||||
Notification.exception(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
349
category_courses/amd/src/rating_system.js
Normal file
349
category_courses/amd/src/rating_system.js
Normal file
@ -0,0 +1,349 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Rating system for category courses.
|
||||
*
|
||||
* @module block_category_courses/rating_system
|
||||
* @copyright 2025 Tu Nombre
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import Ajax from 'core/ajax';
|
||||
import ModalEvents from 'core/modal_events';
|
||||
import ModalFactory from 'core/modal_factory';
|
||||
import Notification from 'core/notification';
|
||||
import Templates from 'core/templates';
|
||||
|
||||
export const init = () => {
|
||||
initStarRatings();
|
||||
initCommentButtons();
|
||||
initializeUserRatings();
|
||||
};
|
||||
|
||||
const initializeUserRatings = () => {
|
||||
document.querySelectorAll('.rating-stars').forEach((container) => {
|
||||
const userRating = parseInt(container.dataset.userRating) || 0;
|
||||
const stars = container.querySelectorAll('.rating-star');
|
||||
|
||||
stars.forEach((star, index) => {
|
||||
if (index < userRating) {
|
||||
star.classList.add('selected');
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
let starRatingsInitialized = false;
|
||||
|
||||
const initStarRatings = () => {
|
||||
if (starRatingsInitialized) {
|
||||
return;
|
||||
}
|
||||
starRatingsInitialized = true;
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target.matches('.rating-star') && e.target.closest('.course-rating-section')) {
|
||||
e.stopPropagation();
|
||||
handleStarClick(e.target);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('mouseover', (e) => {
|
||||
if (e.target.matches('.rating-star') && e.target.closest('.course-rating-section')) {
|
||||
highlightStars(e.target);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('mouseout', (e) => {
|
||||
if (e.target.matches('.rating-stars') && e.target.closest('.course-rating-section')) {
|
||||
resetStarHighlight(e.target);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleStarClick = (star) => {
|
||||
const rating = parseInt(star.dataset.rating);
|
||||
const courseid = parseInt(star.closest('.course-card').dataset.courseid);
|
||||
|
||||
setRating(courseid, rating);
|
||||
};
|
||||
|
||||
const highlightStars = (star) => {
|
||||
const rating = parseInt(star.dataset.rating);
|
||||
const container = star.closest('.rating-stars');
|
||||
const stars = container.querySelectorAll('.rating-star');
|
||||
|
||||
stars.forEach((s, index) => {
|
||||
if (index < rating) {
|
||||
s.classList.add('hover');
|
||||
} else {
|
||||
s.classList.remove('hover');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const resetStarHighlight = (container) => {
|
||||
const stars = container.querySelectorAll('.rating-star');
|
||||
stars.forEach((s) => s.classList.remove('hover'));
|
||||
};
|
||||
|
||||
const setRating = (courseid, rating) => {
|
||||
Ajax.call([
|
||||
{
|
||||
methodname: 'block_category_courses_set_rating',
|
||||
args: {
|
||||
courseid: courseid,
|
||||
rating: rating,
|
||||
},
|
||||
},
|
||||
])[0]
|
||||
.then((result) => {
|
||||
updateRatingDisplay(courseid, result.average_rating, result.total_ratings, rating);
|
||||
Notification.addNotification({
|
||||
message: M.util.get_string('ratingadded', 'block_category_courses'),
|
||||
type: 'success',
|
||||
});
|
||||
return result;
|
||||
})
|
||||
.catch(Notification.exception);
|
||||
};
|
||||
|
||||
const updateRatingDisplay = (courseid, average, total, userRating) => {
|
||||
const card = document.querySelector(`[data-courseid="${courseid}"]`);
|
||||
if (!card) {
|
||||
return;
|
||||
}
|
||||
|
||||
const avgElement = card.querySelector('.average-rating');
|
||||
const totalElement = card.querySelector('.total-ratings');
|
||||
const stars = card.querySelectorAll('.rating-star');
|
||||
const starsContainer = card.querySelector('.rating-stars');
|
||||
|
||||
// Update average and total
|
||||
if (avgElement) {
|
||||
avgElement.textContent = parseFloat(average).toFixed(1);
|
||||
}
|
||||
if (totalElement) {
|
||||
totalElement.textContent = `(${total})`;
|
||||
}
|
||||
|
||||
// Update user's stars and container data
|
||||
if (starsContainer) {
|
||||
starsContainer.dataset.userRating = userRating;
|
||||
}
|
||||
|
||||
stars.forEach((star, index) => {
|
||||
star.classList.remove('selected', 'hover');
|
||||
if (index < userRating) {
|
||||
star.classList.add('selected');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
let commentButtonsInitialized = false;
|
||||
|
||||
const initCommentButtons = () => {
|
||||
if (commentButtonsInitialized) {
|
||||
return;
|
||||
}
|
||||
commentButtonsInitialized = true;
|
||||
document.addEventListener('click', (e) => {
|
||||
const isCommentsBtn = e.target.matches('.comments-btn') || e.target.closest('.comments-btn');
|
||||
if (isCommentsBtn && e.target.closest('.course-rating-section')) {
|
||||
e.stopPropagation();
|
||||
const btn = e.target.closest('.comments-btn');
|
||||
const courseid = parseInt(btn.closest('.course-card').dataset.courseid);
|
||||
openCommentsModal(courseid);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const openCommentsModal = (courseid) => {
|
||||
ModalFactory.create({
|
||||
type: ModalFactory.types.SAVE_CANCEL,
|
||||
title: M.util.get_string('comments', 'block_category_courses'),
|
||||
body: '<div class="text-center"><i class="fa fa-spinner fa-spin"></i></div>',
|
||||
large: true,
|
||||
})
|
||||
.then((modal) => {
|
||||
loadCommentsContent(modal, courseid);
|
||||
|
||||
modal.getRoot().on(ModalEvents.save, () => {
|
||||
submitComment(modal, courseid);
|
||||
});
|
||||
|
||||
modal.show();
|
||||
return modal;
|
||||
})
|
||||
.catch(Notification.exception);
|
||||
};
|
||||
|
||||
const loadCommentsContent = (modal, courseid) => {
|
||||
const card = document.querySelector(`[data-courseid="${courseid}"]`);
|
||||
const userRating = card ? parseInt(card.querySelector('.rating-stars').dataset.userRating) || 0 : 0;
|
||||
Ajax.call([
|
||||
{
|
||||
methodname: 'block_category_courses_get_comments',
|
||||
args: {courseid: courseid},
|
||||
},
|
||||
])[0]
|
||||
.then((comments) => {
|
||||
return Templates.render('block_category_courses/comments_modal', {
|
||||
courseid: courseid,
|
||||
comments: comments,
|
||||
userRating: userRating,
|
||||
});
|
||||
})
|
||||
.then((html) => {
|
||||
modal.setBody(html);
|
||||
initModalRatingStars(modal, userRating);
|
||||
return html;
|
||||
})
|
||||
.catch(Notification.exception);
|
||||
};
|
||||
|
||||
const submitComment = (modal, courseid) => {
|
||||
const form = modal.getRoot().find('form')[0];
|
||||
const formData = new FormData(form);
|
||||
const rating = formData.get('rating');
|
||||
const comment = formData.get('comment');
|
||||
|
||||
Ajax.call([
|
||||
{
|
||||
methodname: 'block_category_courses_set_rating',
|
||||
args: {
|
||||
courseid: courseid,
|
||||
rating: parseInt(rating),
|
||||
comment: comment,
|
||||
},
|
||||
},
|
||||
])[0]
|
||||
.then((result) => {
|
||||
updateRatingDisplay(courseid, result.average_rating, result.total_ratings, rating);
|
||||
updateCommentsCount(courseid);
|
||||
modal.destroy();
|
||||
return result;
|
||||
})
|
||||
.catch(Notification.exception);
|
||||
};
|
||||
|
||||
const updateCommentsCount = (courseid) => {
|
||||
Ajax.call([
|
||||
{
|
||||
methodname: 'block_category_courses_get_comments',
|
||||
args: {courseid: courseid, limit: 1},
|
||||
},
|
||||
])[0]
|
||||
.then((comments) => {
|
||||
const card = document.querySelector(`[data-courseid="${courseid}"]`);
|
||||
const countElement = card.querySelector('.comments-count');
|
||||
if (countElement) {
|
||||
countElement.textContent = comments.length;
|
||||
}
|
||||
return comments;
|
||||
})
|
||||
.catch(Notification.exception);
|
||||
};
|
||||
|
||||
const initModalRatingStars = (modal, userRating = 0) => {
|
||||
const modalRoot = modal.getRoot()[0];
|
||||
|
||||
// Initialize rating displays
|
||||
modalRoot.querySelectorAll('.rating-display').forEach(display => {
|
||||
const rating = parseInt(display.dataset.rating) || 0;
|
||||
const stars = display.querySelectorAll('.star-filled');
|
||||
|
||||
stars.forEach((star, index) => {
|
||||
if (index < rating) {
|
||||
star.style.color = '#ffc107';
|
||||
} else {
|
||||
star.style.color = '#ddd';
|
||||
star.innerHTML = '☆';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize form stars with user's current rating
|
||||
const formStars = modalRoot.querySelectorAll('.rating-star-form');
|
||||
const hiddenInput = modalRoot.querySelector('input[name="rating"]');
|
||||
|
||||
if (hiddenInput) {
|
||||
hiddenInput.value = userRating;
|
||||
}
|
||||
|
||||
formStars.forEach((star, index) => {
|
||||
if (index < userRating) {
|
||||
star.classList.add('selected');
|
||||
star.style.color = '#ffc107';
|
||||
} else {
|
||||
star.classList.remove('selected');
|
||||
star.style.color = '#ddd';
|
||||
}
|
||||
});
|
||||
|
||||
// Handle star selection
|
||||
modalRoot.addEventListener('click', (e) => {
|
||||
if (e.target.matches('.rating-star-form')) {
|
||||
const rating = parseInt(e.target.dataset.rating);
|
||||
const container = e.target.closest('.rating-stars-form');
|
||||
const stars = container.querySelectorAll('.rating-star-form');
|
||||
const hiddenInput = container.nextElementSibling;
|
||||
|
||||
stars.forEach((star, index) => {
|
||||
if (index < rating) {
|
||||
star.classList.add('selected');
|
||||
star.style.color = '#ffc107';
|
||||
} else {
|
||||
star.classList.remove('selected');
|
||||
star.style.color = '#ddd';
|
||||
}
|
||||
});
|
||||
|
||||
hiddenInput.value = rating;
|
||||
}
|
||||
});
|
||||
|
||||
// Handle star hover
|
||||
modalRoot.addEventListener('mouseover', (e) => {
|
||||
if (e.target.matches('.rating-star-form')) {
|
||||
const rating = parseInt(e.target.dataset.rating);
|
||||
const container = e.target.closest('.rating-stars-form');
|
||||
const stars = container.querySelectorAll('.rating-star-form');
|
||||
|
||||
stars.forEach((star, index) => {
|
||||
if (index < rating) {
|
||||
star.style.color = '#ffc107';
|
||||
} else {
|
||||
star.style.color = '#ddd';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
modalRoot.addEventListener('mouseout', (e) => {
|
||||
if (e.target.matches('.rating-stars-form')) {
|
||||
const container = e.target;
|
||||
const stars = container.querySelectorAll('.rating-star-form');
|
||||
|
||||
stars.forEach((star) => {
|
||||
if (star.classList.contains('selected')) {
|
||||
star.style.color = '#ffc107';
|
||||
} else {
|
||||
star.style.color = '#ddd';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
153
category_courses/block_category_courses.php
Normal file
153
category_courses/block_category_courses.php
Normal file
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Category Courses block.
|
||||
*
|
||||
* @package block_category_courses
|
||||
* @copyright 2023 Tu Nombre
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
class block_category_courses extends block_base
|
||||
{
|
||||
|
||||
/**
|
||||
* Initialize the block.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->title = get_string('pluginname', 'block_category_courses');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the block.
|
||||
*/
|
||||
|
||||
public function get_content()
|
||||
{
|
||||
global $USER, $PAGE;
|
||||
if ($this->content !== null) {
|
||||
return $this->content;
|
||||
}
|
||||
$this->content = new stdClass();
|
||||
$this->content->text = '';
|
||||
$this->content->footer = '';
|
||||
|
||||
if (!isloggedin() || isguestuser()) {
|
||||
$this->content = new stdClass();
|
||||
$this->content->text = get_string('notauth', 'block_category_courses');
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
// Check view capability
|
||||
$context = context_block::instance($this->instance->id);
|
||||
if (!has_capability('block/category_courses:view', $context)) {
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
$renderer = $PAGE->get_renderer('core');
|
||||
$outputdata = (new \block_category_courses\output\main($USER->id, $this->config))->export_for_template($renderer);
|
||||
$this->content->text = $renderer->render_from_template('block_category_courses/main', $outputdata);
|
||||
|
||||
return $this->content;
|
||||
}
|
||||
/**
|
||||
* Load required JS.
|
||||
*/
|
||||
public function get_required_javascript()
|
||||
{
|
||||
parent::get_required_javascript();
|
||||
$this->page->requires->js_call_amd('block_category_courses/hierarchy_navigation', 'init');
|
||||
$this->page->requires->js_call_amd('block_category_courses/contrast_helper', 'init');
|
||||
$this->page->requires->js_call_amd('block_category_courses/rating_system', 'init');
|
||||
|
||||
// Load language strings for rating system and other JS modules
|
||||
$this->page->requires->strings_for_js([
|
||||
'ratingadded',
|
||||
'ratingupdated',
|
||||
'ratingerror',
|
||||
'comments',
|
||||
'categoryupdated'
|
||||
], 'block_category_courses');
|
||||
}
|
||||
|
||||
/**
|
||||
* Where the block can be displayed.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function applicable_formats()
|
||||
{
|
||||
return [
|
||||
'site-index' => true,
|
||||
'course-view' => true,
|
||||
'my' => true,
|
||||
'my-index' => true
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The block should only be dockable when the title is not empty.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function instance_can_be_docked()
|
||||
{
|
||||
return (!empty($this->config->title) && parent::instance_can_be_docked());
|
||||
}
|
||||
|
||||
/**
|
||||
* The block has configuration.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_config()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function hide_header()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Allow instance configuration.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function instance_allow_config()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specialization for this block.
|
||||
*/
|
||||
public function specialization()
|
||||
{
|
||||
if (isset($this->config->title)) {
|
||||
$this->title = format_string($this->config->title, true, ['context' => $this->context]);
|
||||
} else {
|
||||
$this->title = get_string('pluginname', 'block_category_courses');
|
||||
}
|
||||
}
|
||||
}
|
||||
73
category_courses/category_image_form.php
Normal file
73
category_courses/category_image_form.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir.'/formslib.php');
|
||||
|
||||
class category_image_form extends moodleform {
|
||||
|
||||
protected function definition() {
|
||||
global $DB;
|
||||
|
||||
$mform = $this->_form;
|
||||
$categoryid = $this->_customdata['categoryid'];
|
||||
|
||||
$category = core_course_category::get($categoryid);
|
||||
$customdata = $DB->get_record('block_catcourse_images', ['categoryid' => $categoryid]);
|
||||
|
||||
$mform->addElement('header', 'categoryheader', format_string($category->name));
|
||||
|
||||
// File upload
|
||||
$mform->addElement('filemanager', 'categoryimage', get_string('categoryimage', 'block_category_courses'), null, [
|
||||
'subdirs' => 0,
|
||||
'maxbytes' => 2097152, // 2MB
|
||||
'maxfiles' => 1,
|
||||
'accepted_types' => ['web_image']
|
||||
]);
|
||||
$mform->addHelpButton('categoryimage', 'categoryimage', 'block_category_courses');
|
||||
|
||||
// Color picker group
|
||||
$colorgroup = [];
|
||||
$colorgroup[] = $mform->createElement('text', 'bgcolor', '', ['size' => 10, 'placeholder' => '#667eea', 'id' => 'id_bgcolor']);
|
||||
$colorgroup[] = $mform->createElement('html', '<input type="color" id="color_picker" value="' . ($customdata->bgcolor ?? '#667eea') . '" style="width: 50px; height: 35px; border: 1px solid #ccc; border-radius: 4px; cursor: pointer; margin-left: 10px;">');
|
||||
$mform->addGroup($colorgroup, 'colorgroup', get_string('categorycolor', 'block_category_courses'), ' ', false);
|
||||
$mform->setType('bgcolor', PARAM_TEXT);
|
||||
$mform->setDefault('bgcolor', $customdata->bgcolor ?? '#667eea');
|
||||
|
||||
// Hidden field
|
||||
$mform->addElement('hidden', 'categoryid', $categoryid);
|
||||
$mform->setType('categoryid', PARAM_INT);
|
||||
|
||||
$this->add_action_buttons(true, get_string('savechanges'));
|
||||
|
||||
// Add JavaScript for color picker
|
||||
$mform->addElement('html', '<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
var colorPicker = document.getElementById("color_picker");
|
||||
var textInput = document.getElementById("id_bgcolor");
|
||||
|
||||
if (colorPicker && textInput) {
|
||||
colorPicker.addEventListener("change", function() {
|
||||
textInput.value = this.value;
|
||||
});
|
||||
|
||||
textInput.addEventListener("change", function() {
|
||||
if (/^#[0-9A-Fa-f]{6}$/.test(this.value)) {
|
||||
colorPicker.value = this.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>');
|
||||
|
||||
// Set existing file data
|
||||
if ($customdata && $customdata->imageurl) {
|
||||
$context = context_system::instance();
|
||||
$draftitemid = file_get_submitted_draft_itemid('categoryimage');
|
||||
file_prepare_draft_area($draftitemid, $context->id, 'block_category_courses', 'categoryimage', $categoryid, [
|
||||
'subdirs' => 0,
|
||||
'maxfiles' => 1
|
||||
]);
|
||||
$mform->setDefault('categoryimage', $draftitemid);
|
||||
}
|
||||
}
|
||||
}
|
||||
176
category_courses/classes/external/get_category_data.php
vendored
Normal file
176
category_courses/classes/external/get_category_data.php
vendored
Normal file
@ -0,0 +1,176 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Servicio web para obtener datos de categorías.
|
||||
*
|
||||
* @package block_category_courses
|
||||
* @copyright 2023 Tu Nombre
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace block_category_courses\external;
|
||||
|
||||
use external_api;
|
||||
use external_function_parameters;
|
||||
use external_value;
|
||||
use external_single_structure;
|
||||
use external_multiple_structure;
|
||||
use context_system;
|
||||
use block_category_courses\output\main;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . '/externallib.php');
|
||||
|
||||
/**
|
||||
* Clase para el servicio web get_category_data
|
||||
*/
|
||||
class get_category_data extends external_api {
|
||||
|
||||
/**
|
||||
* Describe los parámetros de entrada
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function execute_parameters() {
|
||||
return new external_function_parameters([
|
||||
'categoryid' => new external_value(PARAM_INT, 'Category ID', VALUE_DEFAULT, 0)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ejecuta el servicio web
|
||||
* @param int $categoryid ID de la categoría
|
||||
* @return array Datos de la categoría
|
||||
*/
|
||||
public static function execute($categoryid = 0) {
|
||||
global $USER, $PAGE;
|
||||
|
||||
// Validar parámetros
|
||||
$params = self::validate_parameters(self::execute_parameters(), [
|
||||
'categoryid' => $categoryid
|
||||
]);
|
||||
|
||||
// Verificar contexto y permisos
|
||||
$context = context_system::instance();
|
||||
self::validate_context($context);
|
||||
|
||||
require_login();
|
||||
|
||||
if (isguestuser()) {
|
||||
throw new \moodle_exception('guestsarenotallowed');
|
||||
}
|
||||
|
||||
// Obtener datos usando la clase main
|
||||
$renderer = $PAGE->get_renderer('core');
|
||||
$main = new main($USER->id, null, $params['categoryid']);
|
||||
$data = $main->export_for_template($renderer);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe la estructura de retorno
|
||||
* @return external_single_structure
|
||||
*/
|
||||
public static function execute_returns() {
|
||||
return new external_single_structure([
|
||||
'breadcrumbs' => new external_multiple_structure(
|
||||
new external_single_structure([
|
||||
'name' => new external_value(PARAM_TEXT, 'Breadcrumb name'),
|
||||
'url' => new external_value(PARAM_RAW, 'Breadcrumb URL'),
|
||||
'categoryid' => new external_value(PARAM_INT, 'Category ID'),
|
||||
'active' => new external_value(PARAM_BOOL, 'Is active breadcrumb')
|
||||
]), VALUE_DEFAULT, []
|
||||
),
|
||||
'categories' => new external_multiple_structure(
|
||||
new external_single_structure([
|
||||
'id' => new external_value(PARAM_INT, 'Category ID'),
|
||||
'name' => new external_value(PARAM_TEXT, 'Category name'),
|
||||
'description' => new external_value(PARAM_RAW, 'Category description', VALUE_OPTIONAL),
|
||||
'image' => new external_single_structure([
|
||||
'type' => new external_value(PARAM_TEXT, 'Image type', VALUE_OPTIONAL),
|
||||
'text' => new external_value(PARAM_TEXT, 'Image text/initials', VALUE_OPTIONAL),
|
||||
'color' => new external_value(PARAM_TEXT, 'Image color', VALUE_OPTIONAL),
|
||||
'url' => new external_value(PARAM_RAW, 'Image URL or data URI', VALUE_OPTIONAL)
|
||||
], 'Category image data', VALUE_OPTIONAL),
|
||||
'color' => new external_value(PARAM_TEXT, 'Category color'),
|
||||
'textcolor' => new external_value(PARAM_TEXT, 'Text color'),
|
||||
'visible' => new external_value(PARAM_BOOL, 'Category visibility'),
|
||||
'ishidden' => new external_value(PARAM_BOOL, 'Is category hidden (including parent inheritance)'),
|
||||
'url' => new external_value(PARAM_RAW, 'Category URL'),
|
||||
'courses' => new external_multiple_structure(new external_value(PARAM_RAW, 'Course data'), VALUE_DEFAULT, []),
|
||||
'total_courses' => new external_value(PARAM_INT, 'Total courses'),
|
||||
'completed_courses' => new external_value(PARAM_INT, 'Completed courses'),
|
||||
'progress_percentage' => new external_value(PARAM_INT, 'Progress percentage'),
|
||||
'hasprogress' => new external_value(PARAM_BOOL, 'Has progress'),
|
||||
'progress' => new external_value(PARAM_INT, 'Progress value'),
|
||||
'admin_total_courses' => new external_value(PARAM_INT, 'Total courses for admin', VALUE_DEFAULT, 0),
|
||||
'is_admin' => new external_value(PARAM_BOOL, 'Is site admin', VALUE_DEFAULT, false),
|
||||
'config' => new external_single_structure([
|
||||
'showprogress' => new external_value(PARAM_BOOL, 'Show progress'),
|
||||
'showdescription' => new external_value(PARAM_BOOL, 'Show description'),
|
||||
'showcoursecount' => new external_value(PARAM_BOOL, 'Show course count'),
|
||||
'buttoncolor' => new external_value(PARAM_TEXT, 'Button color', VALUE_OPTIONAL),
|
||||
'progresscolor1' => new external_value(PARAM_TEXT, 'Progress start color', VALUE_OPTIONAL),
|
||||
'progresscolor2' => new external_value(PARAM_TEXT, 'Progress end color', VALUE_OPTIONAL)
|
||||
])
|
||||
])
|
||||
),
|
||||
'courses' => new external_multiple_structure(
|
||||
new external_single_structure([
|
||||
'id' => new external_value(PARAM_INT, 'Course ID'),
|
||||
'fullname' => new external_value(PARAM_TEXT, 'Course full name'),
|
||||
'shortname' => new external_value(PARAM_TEXT, 'Course short name'),
|
||||
'summary' => new external_value(PARAM_RAW, 'Course summary', VALUE_OPTIONAL),
|
||||
'viewurl' => new external_value(PARAM_RAW, 'Course view URL'),
|
||||
'courseimage' => new external_value(PARAM_RAW, 'Course image URL or data URI'),
|
||||
'visible' => new external_value(PARAM_BOOL, 'Course visibility'),
|
||||
'isenrolled' => new external_value(PARAM_BOOL, 'User is enrolled'),
|
||||
'hasprogress' => new external_value(PARAM_BOOL, 'Has progress data'),
|
||||
'progress' => new external_value(PARAM_INT, 'Progress percentage'),
|
||||
'average_rating' => new external_value(PARAM_TEXT, 'Average rating', VALUE_DEFAULT, '0.0'),
|
||||
'total_ratings' => new external_value(PARAM_INT, 'Total ratings', VALUE_DEFAULT, 0),
|
||||
'total_comments' => new external_value(PARAM_INT, 'Total comments', VALUE_DEFAULT, 0),
|
||||
'user_rating' => new external_value(PARAM_INT, 'User rating', VALUE_DEFAULT, 0),
|
||||
'config' => new external_single_structure([
|
||||
'showcourseprogress' => new external_value(PARAM_BOOL, 'Show course progress'),
|
||||
'showcoursedescription' => new external_value(PARAM_BOOL, 'Show course description'),
|
||||
'showcourseshortname' => new external_value(PARAM_BOOL, 'Show course short name'),
|
||||
'buttoncolor' => new external_value(PARAM_TEXT, 'Button color', VALUE_OPTIONAL),
|
||||
'progresscolor1' => new external_value(PARAM_TEXT, 'Progress start color', VALUE_OPTIONAL),
|
||||
'progresscolor2' => new external_value(PARAM_TEXT, 'Progress end color', VALUE_OPTIONAL)
|
||||
])
|
||||
]), VALUE_DEFAULT, []
|
||||
),
|
||||
'config' => new external_single_structure([
|
||||
'showprogress' => new external_value(PARAM_BOOL, 'Show progress'),
|
||||
'showdescription' => new external_value(PARAM_BOOL, 'Show description'),
|
||||
'showcoursecount' => new external_value(PARAM_BOOL, 'Show course count'),
|
||||
'showcourseprogress' => new external_value(PARAM_BOOL, 'Show course progress'),
|
||||
'showcoursedescription' => new external_value(PARAM_BOOL, 'Show course description'),
|
||||
'showcourseshortname' => new external_value(PARAM_BOOL, 'Show course short name'),
|
||||
'buttoncolor' => new external_value(PARAM_TEXT, 'Button color', VALUE_OPTIONAL),
|
||||
'progresscolor1' => new external_value(PARAM_TEXT, 'Progress start color', VALUE_OPTIONAL),
|
||||
'progresscolor2' => new external_value(PARAM_TEXT, 'Progress end color', VALUE_OPTIONAL)
|
||||
]),
|
||||
'currentcategoryid' => new external_value(PARAM_INT, 'Current category ID'),
|
||||
'isroot' => new external_value(PARAM_BOOL, 'Is root level'),
|
||||
'hascategories' => new external_value(PARAM_BOOL, 'Has categories', VALUE_DEFAULT, false),
|
||||
'hascourses' => new external_value(PARAM_BOOL, 'Has courses', VALUE_DEFAULT, false)
|
||||
]);
|
||||
}
|
||||
}
|
||||
167
category_courses/classes/external/rating_service.php
vendored
Normal file
167
category_courses/classes/external/rating_service.php
vendored
Normal file
@ -0,0 +1,167 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* External rating service for AJAX calls.
|
||||
*
|
||||
* @package block_category_courses
|
||||
* @copyright 2025 Tu Nombre
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace block_category_courses\external;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . '/externallib.php');
|
||||
|
||||
use external_api;
|
||||
use external_function_parameters;
|
||||
use external_value;
|
||||
use external_single_structure;
|
||||
use external_multiple_structure;
|
||||
use block_category_courses\rating_manager;
|
||||
|
||||
/**
|
||||
* External rating service class.
|
||||
*/
|
||||
class rating_service extends external_api {
|
||||
|
||||
/**
|
||||
* Set rating parameters.
|
||||
*/
|
||||
public static function set_rating_parameters() {
|
||||
return new external_function_parameters([
|
||||
'courseid' => new external_value(PARAM_INT, 'Course ID'),
|
||||
'rating' => new external_value(PARAM_INT, 'Rating 1-5'),
|
||||
'comment' => new external_value(PARAM_TEXT, 'Comment', VALUE_DEFAULT, '')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set course rating.
|
||||
*/
|
||||
public static function set_rating($courseid, $rating, $comment = '') {
|
||||
global $USER;
|
||||
|
||||
$params = self::validate_parameters(self::set_rating_parameters(), [
|
||||
'courseid' => $courseid,
|
||||
'rating' => $rating,
|
||||
'comment' => $comment
|
||||
]);
|
||||
|
||||
$context = \context_course::instance($params['courseid']);
|
||||
self::validate_context($context);
|
||||
|
||||
if (!isloggedin() || isguestuser()) {
|
||||
throw new \moodle_exception('notloggedin');
|
||||
}
|
||||
|
||||
$success = rating_manager::set_rating(
|
||||
$params['courseid'],
|
||||
$USER->id,
|
||||
$params['rating'],
|
||||
$params['comment']
|
||||
);
|
||||
|
||||
if (!$success) {
|
||||
throw new \moodle_exception('errorsetrating', 'block_category_courses');
|
||||
}
|
||||
|
||||
$stats = rating_manager::get_course_stats($params['courseid']);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'average_rating' => $stats->average_rating,
|
||||
'total_ratings' => $stats->total_ratings
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set rating return values.
|
||||
*/
|
||||
public static function set_rating_returns() {
|
||||
return new external_single_structure([
|
||||
'success' => new external_value(PARAM_BOOL, 'Success'),
|
||||
'average_rating' => new external_value(PARAM_FLOAT, 'Average rating'),
|
||||
'total_ratings' => new external_value(PARAM_INT, 'Total ratings')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comments parameters.
|
||||
*/
|
||||
public static function get_comments_parameters() {
|
||||
return new external_function_parameters([
|
||||
'courseid' => new external_value(PARAM_INT, 'Course ID'),
|
||||
'limit' => new external_value(PARAM_INT, 'Limit', VALUE_DEFAULT, 10)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get course comments.
|
||||
*/
|
||||
public static function get_comments($courseid, $limit = 10) {
|
||||
global $OUTPUT;
|
||||
|
||||
$params = self::validate_parameters(self::get_comments_parameters(), [
|
||||
'courseid' => $courseid,
|
||||
'limit' => $limit
|
||||
]);
|
||||
|
||||
$context = \context_course::instance($params['courseid']);
|
||||
self::validate_context($context);
|
||||
|
||||
$comments = rating_manager::get_course_comments($params['courseid'], $params['limit']);
|
||||
$result = [];
|
||||
|
||||
foreach ($comments as $comment) {
|
||||
$user = (object)[
|
||||
'id' => $comment->userid,
|
||||
'firstname' => $comment->firstname,
|
||||
'lastname' => $comment->lastname,
|
||||
'picture' => $comment->picture,
|
||||
'imagealt' => $comment->imagealt,
|
||||
'email' => $comment->email
|
||||
];
|
||||
|
||||
$result[] = [
|
||||
'rating' => $comment->rating,
|
||||
'comment' => $comment->comment,
|
||||
'timemodified' => $comment->timemodified,
|
||||
'user_fullname' => fullname($user),
|
||||
'user_picture' => $OUTPUT->user_picture($user, ['size' => 35])
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comments return values.
|
||||
*/
|
||||
public static function get_comments_returns() {
|
||||
return new external_multiple_structure(
|
||||
new external_single_structure([
|
||||
'rating' => new external_value(PARAM_INT, 'Rating'),
|
||||
'comment' => new external_value(PARAM_TEXT, 'Comment'),
|
||||
'timemodified' => new external_value(PARAM_INT, 'Time modified'),
|
||||
'user_fullname' => new external_value(PARAM_TEXT, 'User full name'),
|
||||
'user_picture' => new external_value(PARAM_RAW, 'User picture HTML')
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
59
category_courses/classes/external/save_image.php
vendored
Normal file
59
category_courses/classes/external/save_image.php
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
namespace block_category_courses\external;
|
||||
|
||||
use external_api;
|
||||
use external_function_parameters;
|
||||
use external_single_structure;
|
||||
use external_value;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
class save_image extends external_api {
|
||||
|
||||
public static function execute_parameters() {
|
||||
return new external_function_parameters([
|
||||
'categoryid' => new external_value(PARAM_INT, 'Category ID'),
|
||||
'imageurl' => new external_value(PARAM_URL, 'Image URL', VALUE_DEFAULT, ''),
|
||||
'bgcolor' => new external_value(PARAM_TEXT, 'Background color', VALUE_DEFAULT, '#667eea'),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function execute($categoryid, $imageurl, $bgcolor) {
|
||||
global $DB;
|
||||
|
||||
$params = self::validate_parameters(self::execute_parameters(), [
|
||||
'categoryid' => $categoryid,
|
||||
'imageurl' => $imageurl,
|
||||
'bgcolor' => $bgcolor,
|
||||
]);
|
||||
|
||||
$context = \context_system::instance();
|
||||
self::validate_context($context);
|
||||
require_capability('block/category_courses:manage', $context);
|
||||
|
||||
$record = $DB->get_record('block_category_courses_images', ['categoryid' => $params['categoryid']]);
|
||||
|
||||
if ($record) {
|
||||
$record->imageurl = $params['imageurl'];
|
||||
$record->bgcolor = $params['bgcolor'];
|
||||
$record->timemodified = time();
|
||||
$DB->update_record('block_category_courses_images', $record);
|
||||
} else {
|
||||
$record = new \stdClass();
|
||||
$record->categoryid = $params['categoryid'];
|
||||
$record->imageurl = $params['imageurl'];
|
||||
$record->bgcolor = $params['bgcolor'];
|
||||
$record->timecreated = time();
|
||||
$record->timemodified = time();
|
||||
$DB->insert_record('block_category_courses_images', $record);
|
||||
}
|
||||
|
||||
return ['success' => true];
|
||||
}
|
||||
|
||||
public static function execute_returns() {
|
||||
return new external_single_structure([
|
||||
'success' => new external_value(PARAM_BOOL, 'Success status'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
46
category_courses/classes/hook_callbacks/output_callbacks.php
Normal file
46
category_courses/classes/hook_callbacks/output_callbacks.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Hook callbacks for output events.
|
||||
*
|
||||
* @package block_category_courses
|
||||
* @copyright 2025 Your Name
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace block_category_courses\hook_callbacks;
|
||||
|
||||
/**
|
||||
* Hook callbacks for output events.
|
||||
*/
|
||||
class output_callbacks {
|
||||
|
||||
/**
|
||||
* Callback for before footer HTML generation.
|
||||
*
|
||||
* @param \core\hook\output\before_footer_html_generation $hook
|
||||
*/
|
||||
public static function before_footer_html_generation(\core\hook\output\before_footer_html_generation $hook): void {
|
||||
global $PAGE;
|
||||
|
||||
// Load color picker on category edit pages
|
||||
if (strpos($PAGE->url->get_path(), '/course/editcategory.php') !== false ||
|
||||
strpos($PAGE->url->get_path(), '/course/management.php') !== false) {
|
||||
$PAGE->requires->js_call_amd('block_category_courses/colorpicker', 'init');
|
||||
}
|
||||
}
|
||||
}
|
||||
14
category_courses/classes/observer.php
Normal file
14
category_courses/classes/observer.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
class block_category_courses_observer {
|
||||
|
||||
public static function load_colorpicker_js($event) {
|
||||
global $PAGE;
|
||||
|
||||
// Only load on category management pages
|
||||
if (strpos($PAGE->url->get_path(), '/course/editcategory.php') !== false) {
|
||||
$PAGE->requires->js_call_amd('block_category_courses/colorpicker', 'init');
|
||||
}
|
||||
}
|
||||
}
|
||||
883
category_courses/classes/output/main.php
Normal file
883
category_courses/classes/output/main.php
Normal file
@ -0,0 +1,883 @@
|
||||
<?php
|
||||
namespace block_category_courses\output;
|
||||
|
||||
use Exception;
|
||||
use cache;
|
||||
use context_coursecat;
|
||||
use context_system;
|
||||
use core_completion\progress;
|
||||
use core_course_category;
|
||||
use moodle_url;
|
||||
use renderable;
|
||||
use templatable;
|
||||
use block_category_courses\rating_manager;
|
||||
|
||||
/**
|
||||
* Renderable y Templatable para tarjetas de categorías.
|
||||
*/
|
||||
class main implements renderable, templatable {
|
||||
private $userid;
|
||||
private $config;
|
||||
private $currentcategoryid;
|
||||
|
||||
public function __construct($userid, $config = null, $currentcategoryid = 0) {
|
||||
$this->userid = $userid;
|
||||
$this->config = $config;
|
||||
$this->currentcategoryid = $currentcategoryid;
|
||||
}
|
||||
|
||||
public function export_for_template($output = null) {
|
||||
global $DB;
|
||||
|
||||
// Obtener estructura jerárquica
|
||||
$hierarchydata = $this->get_category_hierarchy();
|
||||
|
||||
$config = $this->get_display_config();
|
||||
|
||||
$data = [
|
||||
'categories' => $hierarchydata['categories'],
|
||||
'courses' => $hierarchydata['courses'],
|
||||
'breadcrumbs' => $hierarchydata['breadcrumbs'],
|
||||
'config' => $config,
|
||||
'currentcategoryid' => $this->currentcategoryid,
|
||||
'isroot' => $this->currentcategoryid == 0,
|
||||
'hascategories' => !empty($hierarchydata['categories']),
|
||||
'hascourses' => !empty($hierarchydata['courses'])
|
||||
];
|
||||
|
||||
// Add config to each course for template access
|
||||
foreach ($data['courses'] as &$course) {
|
||||
$course['config'] = $config;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene la jerarquía de categorías y cursos para el nivel actual
|
||||
* @return array Estructura con categorías, cursos y breadcrumbs
|
||||
*/
|
||||
private function get_category_hierarchy() {
|
||||
$categories = [];
|
||||
$courses = [];
|
||||
$breadcrumbs = $this->build_breadcrumbs();
|
||||
|
||||
if ($this->currentcategoryid == 0) {
|
||||
// Nivel raíz: obtener categorías principales del usuario
|
||||
$categories = $this->get_user_root_categories();
|
||||
} else {
|
||||
// Nivel específico: obtener subcategorías y cursos
|
||||
$categories = $this->get_subcategories($this->currentcategoryid);
|
||||
$courses = $this->get_category_courses($this->currentcategoryid);
|
||||
}
|
||||
|
||||
$categories = $this->apply_sorting($categories);
|
||||
$categories = $this->apply_limits($categories);
|
||||
$courses = $this->apply_course_sorting($courses);
|
||||
|
||||
return [
|
||||
'categories' => $categories,
|
||||
'courses' => $courses,
|
||||
'breadcrumbs' => $breadcrumbs
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Construye breadcrumbs para navegación
|
||||
* @return array Lista de breadcrumbs
|
||||
*/
|
||||
private function build_breadcrumbs() {
|
||||
$breadcrumbs = [];
|
||||
|
||||
// Siempre agregar "Inicio" como primer breadcrumb
|
||||
$breadcrumbs[] = [
|
||||
'name' => get_string('home', 'block_category_courses'),
|
||||
'url' => '#',
|
||||
'categoryid' => 0,
|
||||
'active' => $this->currentcategoryid == 0
|
||||
];
|
||||
|
||||
if ($this->currentcategoryid > 0) {
|
||||
try {
|
||||
$category = core_course_category::get($this->currentcategoryid, IGNORE_MISSING);
|
||||
if ($category) {
|
||||
// Obtener path de padres (sin incluir la categoría actual)
|
||||
$parents = $category->get_parents();
|
||||
|
||||
// Agregar breadcrumbs para cada padre
|
||||
foreach ($parents as $parentid) {
|
||||
if ($parentid == 0) continue;
|
||||
try {
|
||||
$parentcat = core_course_category::get($parentid, IGNORE_MISSING);
|
||||
if ($parentcat) {
|
||||
$breadcrumbs[] = [
|
||||
'name' => format_string($parentcat->name),
|
||||
'url' => '#',
|
||||
'categoryid' => $parentid,
|
||||
'active' => false
|
||||
];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Agregar la categoría actual como último breadcrumb
|
||||
$breadcrumbs[] = [
|
||||
'name' => format_string($category->name),
|
||||
'url' => '#',
|
||||
'categoryid' => $this->currentcategoryid,
|
||||
'active' => true
|
||||
];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Error obteniendo categoría, solo mostrar "Inicio"
|
||||
}
|
||||
}
|
||||
|
||||
return $breadcrumbs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene categorías raíz del usuario (solo nivel superior)
|
||||
* @return array Lista de categorías raíz
|
||||
*/
|
||||
private function get_user_root_categories() {
|
||||
$categories = [];
|
||||
$rootcategories = [];
|
||||
|
||||
$showAllCategories = $this->get_config_value('showallcategories', false);
|
||||
|
||||
if (is_siteadmin() || has_capability('moodle/category:manage', context_system::instance())) {
|
||||
// Admins and managers: obtener todas las categorías raíz
|
||||
$allcategories = core_course_category::get_all();
|
||||
foreach ($allcategories as $category) {
|
||||
if ($category->parent == 0) { // Solo categorías raíz
|
||||
$rootcategories[$category->id] = $category;
|
||||
}
|
||||
}
|
||||
} else if ($showAllCategories) {
|
||||
// Mostrar todas las categorías visibles
|
||||
$allcategories = core_course_category::get_all();
|
||||
foreach ($allcategories as $category) {
|
||||
if ($category->parent == 0 && $category->visible) {
|
||||
$rootcategories[$category->id] = $category;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Usuarios normales: solo categorías raíz visibles donde tienen cursos (recursivo)
|
||||
$usercourses = enrol_get_my_courses();
|
||||
$usercategoryids = [];
|
||||
|
||||
// Obtener todas las categorías donde el usuario tiene cursos
|
||||
foreach ($usercourses as $course) {
|
||||
$usercategoryids[] = $course->category;
|
||||
}
|
||||
|
||||
// Para cada categoría, encontrar su categoría raíz
|
||||
foreach ($usercategoryids as $categoryid) {
|
||||
try {
|
||||
$category = core_course_category::get($categoryid);
|
||||
$rootcategoryid = $this->get_root_category_id($category);
|
||||
|
||||
if (!isset($rootcategories[$rootcategoryid])) {
|
||||
$rootcategory = core_course_category::get($rootcategoryid, IGNORE_MISSING);
|
||||
// Solo agregar si la categoría raíz es visible
|
||||
if ($rootcategory && $rootcategory->visible) {
|
||||
$rootcategories[$rootcategoryid] = $rootcategory;
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Categoría no encontrada, continuar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Construir datos para cada categoría raíz
|
||||
foreach ($rootcategories as $category) {
|
||||
// Para estudiantes, filtrar categorías ocultas
|
||||
if (!is_siteadmin() && !has_capability('moodle/category:manage', context_system::instance())) {
|
||||
if ($this->is_category_or_parent_hidden($category)) {
|
||||
continue; // Saltar categorías ocultas para estudiantes
|
||||
}
|
||||
}
|
||||
|
||||
$categorydata = $this->build_category_data($category);
|
||||
|
||||
// Calcular estadísticas recursivas para toda la rama
|
||||
$stats = $this->get_category_recursive_stats($category->id);
|
||||
$categorydata['total_courses'] = $stats['total_courses'];
|
||||
$categorydata['completed_courses'] = $stats['completed_courses'];
|
||||
$categorydata['progress_percentage'] = $stats['progress_percentage'];
|
||||
$categorydata['hasprogress'] = $stats['hasprogress'];
|
||||
$categorydata['progress'] = $stats['progress_percentage'];
|
||||
|
||||
$categories[] = $categorydata;
|
||||
}
|
||||
|
||||
return $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene subcategorías de una categoría específica
|
||||
* @param int $parentid ID de la categoría padre
|
||||
* @return array Lista de subcategorías
|
||||
*/
|
||||
private function get_subcategories($parentid) {
|
||||
$categories = [];
|
||||
|
||||
try {
|
||||
$parentcategory = core_course_category::get($parentid);
|
||||
|
||||
// Si la categoría padre está oculta, no mostrar subcategorías a usuarios normales
|
||||
if (!$parentcategory->visible && !is_siteadmin() && !has_capability('moodle/category:manage', context_system::instance())) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$subcategories = $parentcategory->get_children();
|
||||
|
||||
$showAllCategories = $this->get_config_value('showallcategories', false);
|
||||
|
||||
foreach ($subcategories as $subcategory) {
|
||||
// Para estudiantes, filtrar subcategorías ocultas
|
||||
if (!is_siteadmin() && !has_capability('moodle/category:manage', context_system::instance())) {
|
||||
if (!$subcategory->visible) {
|
||||
continue; // Saltar subcategorías ocultas para estudiantes
|
||||
}
|
||||
}
|
||||
|
||||
// Verificar si el usuario tiene acceso a esta categoría
|
||||
if ($showAllCategories || $this->user_has_category_access($subcategory)) {
|
||||
$categorydata = $this->build_category_data($subcategory);
|
||||
|
||||
// Para subcategorías, calcular estadísticas recursivamente
|
||||
$stats = $this->get_category_recursive_stats($subcategory->id);
|
||||
$categorydata['total_courses'] = $stats['total_courses'];
|
||||
$categorydata['completed_courses'] = $stats['completed_courses'];
|
||||
$categorydata['progress_percentage'] = $stats['progress_percentage'];
|
||||
$categorydata['hasprogress'] = $stats['hasprogress'];
|
||||
$categorydata['progress'] = $stats['progress_percentage'];
|
||||
|
||||
$categories[] = $categorydata;
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Categoría no encontrada o sin permisos
|
||||
}
|
||||
|
||||
return $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene cursos directos de una categoría
|
||||
* @param int $categoryid ID de la categoría
|
||||
* @return array Lista de cursos
|
||||
*/
|
||||
private function get_category_courses($categoryid) {
|
||||
global $DB;
|
||||
$courses = [];
|
||||
|
||||
try {
|
||||
$category = core_course_category::get($categoryid);
|
||||
|
||||
if (is_siteadmin() || has_capability('moodle/category:manage', context_system::instance())) {
|
||||
// Admins and managers: obtener solo cursos directos de la categoría actual
|
||||
$sql = "SELECT c.* FROM {course} c WHERE c.category = ? AND c.id != ?";
|
||||
$allcourses = $DB->get_records_sql($sql, [$categoryid, SITEID]);
|
||||
$enrolledcourses = enrol_get_my_courses();
|
||||
|
||||
foreach ($allcourses as $course) {
|
||||
$coursedata = $this->build_course_data($course);
|
||||
$coursedata['isenrolled'] = isset($enrolledcourses[$course->id]);
|
||||
|
||||
// Calcular progreso solo si está inscrito
|
||||
if ($coursedata['isenrolled']) {
|
||||
$progress = progress::get_course_progress_percentage($course, $this->userid);
|
||||
$coursedata['hasprogress'] = !is_null($progress);
|
||||
$coursedata['progress'] = $coursedata['hasprogress'] ? floor($progress) : 0;
|
||||
} else {
|
||||
$coursedata['hasprogress'] = false;
|
||||
$coursedata['progress'] = 0;
|
||||
}
|
||||
|
||||
// Add rating data
|
||||
$this->add_rating_data($coursedata, $course->id);
|
||||
|
||||
$courses[] = $coursedata;
|
||||
}
|
||||
} else {
|
||||
// Usuarios normales: solo cursos inscritos y visibles directos de la categoría
|
||||
$enrolledcourses = enrol_get_my_courses();
|
||||
|
||||
foreach ($enrolledcourses as $course) {
|
||||
if ($course->category == $categoryid && $course->visible) {
|
||||
$coursedata = $this->build_course_data($course);
|
||||
$coursedata['isenrolled'] = true;
|
||||
|
||||
$progress = progress::get_course_progress_percentage($course, $this->userid);
|
||||
$coursedata['hasprogress'] = !is_null($progress);
|
||||
$coursedata['progress'] = $coursedata['hasprogress'] ? floor($progress) : 0;
|
||||
|
||||
// Add rating data
|
||||
$this->add_rating_data($coursedata, $course->id);
|
||||
|
||||
$courses[] = $coursedata;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Error getting courses for category
|
||||
}
|
||||
return $courses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construye datos de un curso compatible con template estándar de Moodle
|
||||
* @param object $course Objeto curso
|
||||
* @return array Datos del curso
|
||||
*/
|
||||
private function build_course_data($course) {
|
||||
global $OUTPUT;
|
||||
|
||||
$courseimage = \core_course\external\course_summary_exporter::get_course_image($course);
|
||||
if (!$courseimage) {
|
||||
$courseimage = $OUTPUT->get_generated_image_for_id($course->id);
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $course->id,
|
||||
'fullname' => format_string($course->fullname),
|
||||
'shortname' => format_string($course->shortname),
|
||||
'summary' => strip_tags($course->summary),
|
||||
'viewurl' => (new moodle_url('/course/view.php', ['id' => $course->id]))->out(false),
|
||||
'courseimage' => $courseimage,
|
||||
'visible' => $course->visible,
|
||||
'coursecategory' => '',
|
||||
'showcoursecategory' => false,
|
||||
'isenrolled' => false, // Se establece en el método llamador
|
||||
'hasprogress' => false, // Se establece en el método llamador
|
||||
'progress' => 0 // Se establece en el método llamador
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica si el usuario tiene acceso a una categoría
|
||||
* @param object $category Categoría
|
||||
* @return bool True si tiene acceso
|
||||
*/
|
||||
private function user_has_category_access($category) {
|
||||
if (is_siteadmin() || has_capability('moodle/category:manage', context_system::instance())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Verificar si tiene cursos inscritos y visibles en esta categoría o subcategorías
|
||||
return $this->has_visible_enrolled_courses_in_category($category->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica si el usuario tiene cursos inscritos en una categoría (recursivo)
|
||||
* @param int $categoryid ID de la categoría
|
||||
* @return bool True si tiene cursos inscritos
|
||||
*/
|
||||
private function has_enrolled_courses_in_category($categoryid) {
|
||||
$enrolledcourses = enrol_get_my_courses();
|
||||
|
||||
foreach ($enrolledcourses as $course) {
|
||||
if ($this->course_belongs_to_category_tree($course->category, $categoryid)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica si el usuario tiene cursos inscritos y visibles en una categoría (recursivo)
|
||||
* @param int $categoryid ID de la categoría
|
||||
* @return bool True si tiene cursos inscritos y visibles
|
||||
*/
|
||||
private function has_visible_enrolled_courses_in_category($categoryid) {
|
||||
$enrolledcourses = enrol_get_my_courses();
|
||||
|
||||
foreach ($enrolledcourses as $course) {
|
||||
if ($course->visible && $this->course_belongs_to_category_tree($course->category, $categoryid)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica si un curso pertenece al árbol de una categoría
|
||||
* @param int $coursecategoryid ID de categoría del curso
|
||||
* @param int $targetcategoryid ID de categoría objetivo
|
||||
* @return bool True si pertenece al árbol
|
||||
*/
|
||||
private function course_belongs_to_category_tree($coursecategoryid, $targetcategoryid) {
|
||||
if ($coursecategoryid == $targetcategoryid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
$coursecategory = core_course_category::get($coursecategoryid);
|
||||
$parents = $coursecategory->get_parents();
|
||||
return in_array($targetcategoryid, $parents);
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene estadísticas recursivas de una categoría
|
||||
* @param int $categoryid ID de la categoría
|
||||
* @return array Estadísticas
|
||||
*/
|
||||
private function get_category_recursive_stats($categoryid) {
|
||||
$totalcourses = 0;
|
||||
$completedcourses = 0;
|
||||
$enrolledcourses = enrol_get_my_courses();
|
||||
|
||||
if (is_siteadmin() || has_capability('moodle/category:manage', context_system::instance())) {
|
||||
// Admins and managers: mostrar solo cursos inscritos (como usuarios normales)
|
||||
// Esto evita confusión de mostrar "0 de 27" cuando no está inscrito
|
||||
foreach ($enrolledcourses as $course) {
|
||||
if ($course->visible && $this->course_belongs_to_category_tree($course->category, $categoryid)) {
|
||||
$totalcourses++;
|
||||
|
||||
$progress = progress::get_course_progress_percentage($course, $this->userid);
|
||||
if ($progress === 100.0 || $progress === 100) {
|
||||
$completedcourses++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Usuarios normales: solo cursos inscritos y visibles
|
||||
foreach ($enrolledcourses as $course) {
|
||||
if ($course->visible && $this->course_belongs_to_category_tree($course->category, $categoryid)) {
|
||||
$totalcourses++;
|
||||
|
||||
$progress = progress::get_course_progress_percentage($course, $this->userid);
|
||||
if ($progress === 100.0 || $progress === 100) {
|
||||
$completedcourses++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$percentage = $totalcourses > 0 ? round(($completedcourses / $totalcourses) * 100) : 0;
|
||||
|
||||
return [
|
||||
'total_courses' => $totalcourses,
|
||||
'completed_courses' => $completedcourses,
|
||||
'progress_percentage' => $percentage,
|
||||
'hasprogress' => $totalcourses > 0,
|
||||
'progress' => $percentage
|
||||
];
|
||||
}
|
||||
|
||||
private function build_category_data($category) {
|
||||
$image = $this->get_category_image($category);
|
||||
$color = $this->get_category_color($category);
|
||||
$textcolor = $this->get_text_color_for_background($color);
|
||||
|
||||
// Para admins: mostrar conteo total de cursos en la categoría (recursivo)
|
||||
// Para usuarios: se calculará con cursos inscritos
|
||||
$total_courses = 0;
|
||||
$admin_total_courses = 0;
|
||||
|
||||
if (is_siteadmin() || has_capability('moodle/category:manage', context_system::instance())) {
|
||||
$admin_total_courses = $this->get_category_total_courses($category->id);
|
||||
}
|
||||
|
||||
// Check if category or any parent is hidden (recursive)
|
||||
$ishidden = $this->is_category_or_parent_hidden($category);
|
||||
|
||||
return [
|
||||
'id' => $category->id,
|
||||
'name' => format_string($category->name),
|
||||
'description' => $this->get_category_description($category),
|
||||
'image' => $image,
|
||||
'color' => $color,
|
||||
'textcolor' => $textcolor,
|
||||
'visible' => $category->visible,
|
||||
'ishidden' => $ishidden,
|
||||
'url' => (new moodle_url('/course/index.php', ['categoryid' => $category->id]))->out(false),
|
||||
'courses' => [],
|
||||
'total_courses' => $total_courses,
|
||||
'completed_courses' => 0,
|
||||
'progress_percentage' => 0,
|
||||
'hasprogress' => false,
|
||||
'progress' => 0,
|
||||
'admin_total_courses' => $admin_total_courses,
|
||||
'is_admin' => is_siteadmin() || has_capability('moodle/category:manage', context_system::instance()),
|
||||
'config' => $this->get_display_config()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el ID de la categoría raíz para una categoría dada
|
||||
* @param object $category Categoría
|
||||
* @return int ID de la categoría raíz
|
||||
*/
|
||||
private function get_root_category_id($category) {
|
||||
if ($category->parent == 0) {
|
||||
return $category->id;
|
||||
}
|
||||
|
||||
try {
|
||||
$parentcategory = core_course_category::get($category->parent);
|
||||
return $this->get_root_category_id($parentcategory);
|
||||
} catch (Exception $e) {
|
||||
return $category->id; // Fallback
|
||||
}
|
||||
}
|
||||
|
||||
private function add_course_to_category(&$category, $course) {
|
||||
$progress = progress::get_course_progress_percentage($course, $this->userid);
|
||||
|
||||
|
||||
|
||||
$iscompleted = ($progress === 100.0 || $progress === 100);
|
||||
|
||||
// For regular users, increment total_courses
|
||||
// For admins, we need to track enrolled courses separately
|
||||
if (!is_siteadmin() && !has_capability('moodle/category:manage', context_system::instance())) {
|
||||
$category['total_courses']++;
|
||||
} else {
|
||||
// For admins and managers, track enrolled courses separately
|
||||
if (!isset($category['enrolled_courses'])) {
|
||||
$category['enrolled_courses'] = 0;
|
||||
}
|
||||
$category['enrolled_courses']++;
|
||||
}
|
||||
|
||||
if ($iscompleted) {
|
||||
$category['completed_courses']++;
|
||||
}
|
||||
|
||||
// Calculate percentage based on enrolled courses for admins
|
||||
if (is_siteadmin() || has_capability('moodle/category:manage', context_system::instance())) {
|
||||
$percentage = $category['enrolled_courses'] > 0 ?
|
||||
round(($category['completed_courses'] / $category['enrolled_courses']) * 100) : 0;
|
||||
} else {
|
||||
$percentage = $category['total_courses'] > 0 ?
|
||||
round(($category['completed_courses'] / $category['total_courses']) * 100) : 0;
|
||||
}
|
||||
|
||||
$category['progress_percentage'] = $percentage;
|
||||
$category['hasprogress'] = true; // Siempre mostrar si hay cursos inscritos
|
||||
$category['progress'] = $percentage;
|
||||
}
|
||||
|
||||
private function get_category_description($category) {
|
||||
if (!$this->get_config_value('showdescription', true)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$limit = $this->get_config_value('descriptionlimit', 150);
|
||||
$description = strip_tags($category->description);
|
||||
|
||||
// Limpiar entidades HTML y caracteres especiales
|
||||
$description = html_entity_decode($description, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$description = preg_replace('/\s+/', ' ', $description); // Normalizar espacios
|
||||
$description = trim($description);
|
||||
|
||||
if (strlen($description) > $limit) {
|
||||
$description = substr($description, 0, $limit) . '...';
|
||||
}
|
||||
|
||||
return $description;
|
||||
}
|
||||
|
||||
private function get_category_image($category) {
|
||||
global $DB;
|
||||
|
||||
// 1. Try uploaded file first (if exists)
|
||||
try {
|
||||
if ($DB->get_manager()->table_exists('block_catcourse_images')) {
|
||||
$customdata = $DB->get_record('block_catcourse_images', ['categoryid' => $category->id]);
|
||||
if ($customdata && !empty($customdata->imageurl)) {
|
||||
// Check if file actually exists
|
||||
$context = context_system::instance();
|
||||
$fs = get_file_storage();
|
||||
$files = $fs->get_area_files($context->id, 'block_category_courses', 'categoryimage', $category->id, 'filename', false);
|
||||
if (!empty($files)) {
|
||||
$file = reset($files);
|
||||
$imageurl = moodle_url::make_pluginfile_url(
|
||||
$context->id,
|
||||
'block_category_courses',
|
||||
'categoryimage',
|
||||
$category->id,
|
||||
'/',
|
||||
$file->get_filename()
|
||||
)->out();
|
||||
return [
|
||||
'type' => null,
|
||||
'text' => null,
|
||||
'color' => null,
|
||||
'url' => $imageurl
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Table doesn't exist yet
|
||||
}
|
||||
|
||||
// 2. Try to extract from description
|
||||
if (!empty($category->description)) {
|
||||
if (preg_match('/<img[^>]+src=["\'](["\'">]+)["\'"][^>]*>/i', $category->description, $matches)) {
|
||||
$imageurl = $matches[1];
|
||||
if ($this->is_valid_image_url($imageurl)) {
|
||||
return [
|
||||
'type' => null,
|
||||
'text' => null,
|
||||
'color' => null,
|
||||
'url' => $imageurl
|
||||
];
|
||||
}
|
||||
}
|
||||
if (preg_match('/pluginfile\.php\/[^\s"\'">]+\.(jpg|jpeg|png|gif|webp)/i', $category->description, $matches)) {
|
||||
return [
|
||||
'type' => null,
|
||||
'text' => null,
|
||||
'color' => null,
|
||||
'url' => $matches[0]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Fallback: generate initials with custom color
|
||||
$color = $this->get_category_color($category);
|
||||
return $this->generate_fallback_image($category->name, $color);
|
||||
}
|
||||
|
||||
private function get_category_color($category) {
|
||||
global $DB;
|
||||
|
||||
// Try custom table first (if exists)
|
||||
try {
|
||||
if ($DB->get_manager()->table_exists('block_catcourse_images')) {
|
||||
$customdata = $DB->get_record('block_catcourse_images', ['categoryid' => $category->id]);
|
||||
if ($customdata && !empty($customdata->bgcolor)) {
|
||||
return $customdata->bgcolor;
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Table doesn't exist yet
|
||||
}
|
||||
|
||||
// Fallback: generate color based on category name
|
||||
$colors = ['#667eea', '#764ba2', '#f093fb', '#f5576c', '#4facfe', '#43e97b', '#fa709a', '#ffecd2'];
|
||||
$index = abs(crc32($category->name)) % count($colors);
|
||||
return $colors[$index];
|
||||
}
|
||||
|
||||
private function is_valid_image_url($url) {
|
||||
$imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'];
|
||||
$extension = strtolower(pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION));
|
||||
return in_array($extension, $imageExtensions);
|
||||
}
|
||||
|
||||
private function generate_fallback_image($name, $color = '#667eea') {
|
||||
$initials = '';
|
||||
$words = explode(' ', $name);
|
||||
foreach (array_slice($words, 0, 2) as $word) {
|
||||
$initials .= strtoupper(substr($word, 0, 1));
|
||||
}
|
||||
return [
|
||||
'type' => 'initials',
|
||||
'text' => $initials,
|
||||
'color' => $color,
|
||||
'url' => null
|
||||
];
|
||||
}
|
||||
|
||||
private function apply_sorting($categories) {
|
||||
$sortorder = $this->get_config_value('sortorder', 'core');
|
||||
|
||||
switch ($sortorder) {
|
||||
case 'alphabetical':
|
||||
usort($categories, function($a, $b) {
|
||||
return strcmp($a['name'], $b['name']);
|
||||
});
|
||||
break;
|
||||
case 'coursecount':
|
||||
usort($categories, function($a, $b) {
|
||||
return $b['total_courses'] - $a['total_courses'];
|
||||
});
|
||||
break;
|
||||
case 'progress':
|
||||
usort($categories, function($a, $b) {
|
||||
return $b['progress_percentage'] - $a['progress_percentage'];
|
||||
});
|
||||
break;
|
||||
// 'core' keeps original order
|
||||
}
|
||||
|
||||
return $categories;
|
||||
}
|
||||
|
||||
private function apply_limits($categories) {
|
||||
// No limits applied in hierarchical navigation
|
||||
return $categories;
|
||||
}
|
||||
|
||||
private function apply_course_sorting($courses) {
|
||||
$sortorder = $this->get_config_value('sortorder', 'core');
|
||||
|
||||
switch ($sortorder) {
|
||||
case 'alphabetical':
|
||||
usort($courses, function($a, $b) {
|
||||
return strcmp($a['fullname'], $b['fullname']);
|
||||
});
|
||||
break;
|
||||
case 'progress':
|
||||
usort($courses, function($a, $b) {
|
||||
return $b['progress'] - $a['progress'];
|
||||
});
|
||||
break;
|
||||
// 'core' and 'coursecount' keep original order for courses
|
||||
}
|
||||
|
||||
return $courses;
|
||||
}
|
||||
|
||||
private function get_display_config() {
|
||||
return [
|
||||
'showprogress' => $this->get_config_value('showprogress', true),
|
||||
'showdescription' => $this->get_config_value('showdescription', true),
|
||||
'showcoursecount' => $this->get_config_value('showcoursecount', true),
|
||||
'showcourseprogress' => $this->get_config_value('showcourseprogress', true),
|
||||
'showcoursedescription' => $this->get_config_value('showcoursedescription', true),
|
||||
'showcourseshortname' => $this->get_config_value('showcourseshortname', true),
|
||||
'buttoncolor' => $this->get_config_value('buttoncolor', ''),
|
||||
'progresscolor1' => $this->get_config_value('progresscolor1', '#4285f4'),
|
||||
'progresscolor2' => $this->get_config_value('progresscolor2', '#34a853')
|
||||
];
|
||||
}
|
||||
|
||||
private function get_config_value($key, $default) {
|
||||
// For color settings, only use global config
|
||||
if (in_array($key, ['buttoncolor', 'progresscolor1', 'progresscolor2'])) {
|
||||
return get_config('block_category_courses', $key) ?? $default;
|
||||
}
|
||||
|
||||
// Instance config takes precedence for other settings
|
||||
if (isset($this->config->$key)) {
|
||||
return $this->config->$key;
|
||||
}
|
||||
|
||||
// Fall back to global config
|
||||
return get_config('block_category_courses', $key) ?? $default;
|
||||
}
|
||||
|
||||
private function get_text_color_for_background($hexcolor) {
|
||||
// Remove # if present
|
||||
$hex = ltrim($hexcolor, '#');
|
||||
|
||||
// Convert to RGB
|
||||
$r = hexdec(substr($hex, 0, 2));
|
||||
$g = hexdec(substr($hex, 2, 2));
|
||||
$b = hexdec(substr($hex, 4, 2));
|
||||
|
||||
// Calculate luminance using WCAG formula
|
||||
$luminance = (0.299 * $r + 0.587 * $g + 0.114 * $b) / 255;
|
||||
|
||||
// Return white for dark backgrounds, dark for light backgrounds
|
||||
return $luminance > 0.5 ? '#1f2937' : '#ffffff';
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el conteo total de cursos en una categoría (recursivo)
|
||||
* @param int $categoryid ID de la categoría
|
||||
* @return int Total de cursos
|
||||
*/
|
||||
private function get_category_total_courses($categoryid) {
|
||||
try {
|
||||
$category = core_course_category::get($categoryid, IGNORE_MISSING);
|
||||
if (!$category) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Usar el método nativo de Moodle para contar cursos recursivamente
|
||||
return $category->get_courses_count(['recursive' => true]);
|
||||
} catch (Exception $e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a category is effectively hidden (itself or any parent hidden)
|
||||
* @param object $category Category object
|
||||
* @return bool True if category is effectively hidden for students
|
||||
*/
|
||||
private function is_category_or_parent_hidden($category) {
|
||||
// For admins, show the actual visibility status with inheritance indication
|
||||
if (is_siteadmin() || has_capability('moodle/category:manage', context_system::instance())) {
|
||||
return $this->check_inherited_visibility($category);
|
||||
}
|
||||
|
||||
// For students, if any parent is hidden, this category is effectively hidden
|
||||
return $this->check_inherited_visibility($category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively checks if category or any parent is hidden
|
||||
* @param object $category Category object
|
||||
* @return bool True if category or any parent is hidden
|
||||
*/
|
||||
private function check_inherited_visibility($category) {
|
||||
if (!$category->visible) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($category->parent == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$parent = core_course_category::get($category->parent, IGNORE_MISSING);
|
||||
if ($parent) {
|
||||
return $this->check_inherited_visibility($parent);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Parent not found, assume visible
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds rating data to course data array
|
||||
* @param array &$coursedata Course data array (passed by reference)
|
||||
* @param int $courseid Course ID
|
||||
*/
|
||||
private function add_rating_data(&$coursedata, $courseid) {
|
||||
// Set default values
|
||||
$coursedata['average_rating'] = '0.0';
|
||||
$coursedata['total_ratings'] = 0;
|
||||
$coursedata['total_comments'] = 0;
|
||||
$coursedata['user_rating'] = 0;
|
||||
|
||||
try {
|
||||
if (class_exists('block_category_courses\rating_manager')) {
|
||||
$stats = rating_manager::get_course_stats($courseid);
|
||||
$userRating = rating_manager::get_user_rating($courseid, $this->userid);
|
||||
|
||||
if ($stats) {
|
||||
$coursedata['average_rating'] = $stats->average_rating > 0 ? number_format($stats->average_rating, 1) : '0.0';
|
||||
$coursedata['total_ratings'] = $stats->total_ratings;
|
||||
$coursedata['total_comments'] = $stats->total_comments;
|
||||
}
|
||||
|
||||
if ($userRating) {
|
||||
$coursedata['user_rating'] = $userRating->rating;
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Keep default values
|
||||
}
|
||||
}
|
||||
}
|
||||
24
category_courses/classes/privacy/provider.php
Normal file
24
category_courses/classes/privacy/provider.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace block_category_courses\privacy;
|
||||
|
||||
use core_privacy\local\metadata\null_provider;
|
||||
|
||||
/**
|
||||
* Privacy provider for block_category_courses.
|
||||
*
|
||||
* @package block_category_courses
|
||||
* @copyright 2023
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements null_provider {
|
||||
|
||||
/**
|
||||
* Get the language string identifier with the component's language
|
||||
* file to explain why this plugin stores no data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_reason() : string {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
||||
167
category_courses/classes/rating_manager.php
Normal file
167
category_courses/classes/rating_manager.php
Normal file
@ -0,0 +1,167 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Rating manager for category courses block.
|
||||
*
|
||||
* @package block_category_courses
|
||||
* @copyright 2025 Tu Nombre
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace block_category_courses;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Manages course ratings and comments.
|
||||
*/
|
||||
class rating_manager {
|
||||
|
||||
/**
|
||||
* Add or update a course rating.
|
||||
*
|
||||
* @param int $courseid Course ID
|
||||
* @param int $userid User ID
|
||||
* @param int $rating Rating (1-5)
|
||||
* @param string $comment Optional comment
|
||||
* @return bool Success
|
||||
*/
|
||||
public static function set_rating($courseid, $userid, $rating, $comment = '') {
|
||||
global $DB;
|
||||
|
||||
if ($rating < 1 || $rating > 5) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$time = time();
|
||||
$record = $DB->get_record('block_catcourse_ratings',
|
||||
['courseid' => $courseid, 'userid' => $userid]);
|
||||
|
||||
if ($record) {
|
||||
$record->rating = $rating;
|
||||
$record->comment = $comment;
|
||||
$record->timemodified = $time;
|
||||
$result = $DB->update_record('block_catcourse_ratings', $record);
|
||||
} else {
|
||||
$record = (object)[
|
||||
'courseid' => $courseid,
|
||||
'userid' => $userid,
|
||||
'rating' => $rating,
|
||||
'comment' => $comment,
|
||||
'timecreated' => $time,
|
||||
'timemodified' => $time
|
||||
];
|
||||
$result = $DB->insert_record('block_catcourse_ratings', $record);
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
self::update_stats($courseid);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's rating for a course.
|
||||
*
|
||||
* @param int $courseid Course ID
|
||||
* @param int $userid User ID
|
||||
* @return object|false Rating record or false
|
||||
*/
|
||||
public static function get_user_rating($courseid, $userid) {
|
||||
global $DB;
|
||||
return $DB->get_record('block_catcourse_ratings',
|
||||
['courseid' => $courseid, 'userid' => $userid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get course statistics.
|
||||
*
|
||||
* @param int $courseid Course ID
|
||||
* @return object Statistics
|
||||
*/
|
||||
public static function get_course_stats($courseid) {
|
||||
global $DB;
|
||||
$stats = $DB->get_record('block_catcourse_stats', ['courseid' => $courseid]);
|
||||
|
||||
if (!$stats) {
|
||||
return (object)[
|
||||
'total_ratings' => 0,
|
||||
'average_rating' => 0,
|
||||
'total_comments' => 0
|
||||
];
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get course comments.
|
||||
*
|
||||
* @param int $courseid Course ID
|
||||
* @param int $limit Limit results
|
||||
* @return array Comments with user info
|
||||
*/
|
||||
public static function get_course_comments($courseid, $limit = 10) {
|
||||
global $DB;
|
||||
|
||||
$sql = "SELECT r.*, u.firstname, u.lastname, u.picture, u.imagealt, u.email
|
||||
FROM {block_catcourse_ratings} r
|
||||
JOIN {user} u ON r.userid = u.id
|
||||
WHERE r.courseid = ? AND r.comment IS NOT NULL AND r.comment != ''
|
||||
ORDER BY r.timemodified DESC";
|
||||
|
||||
return $DB->get_records_sql($sql, [$courseid], 0, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update course statistics.
|
||||
*
|
||||
* @param int $courseid Course ID
|
||||
*/
|
||||
private static function update_stats($courseid) {
|
||||
global $DB;
|
||||
|
||||
$sql = "SELECT COUNT(*) as total_ratings,
|
||||
AVG(rating) as average_rating,
|
||||
SUM(CASE WHEN comment IS NOT NULL AND comment != '' THEN 1 ELSE 0 END) as total_comments
|
||||
FROM {block_catcourse_ratings}
|
||||
WHERE courseid = ?";
|
||||
|
||||
$stats = $DB->get_record_sql($sql, [$courseid]);
|
||||
$time = time();
|
||||
|
||||
$record = $DB->get_record('block_catcourse_stats', ['courseid' => $courseid]);
|
||||
|
||||
if ($record) {
|
||||
$record->total_ratings = $stats->total_ratings;
|
||||
$record->average_rating = round($stats->average_rating, 2);
|
||||
$record->total_comments = $stats->total_comments;
|
||||
$record->timemodified = $time;
|
||||
$DB->update_record('block_catcourse_stats', $record);
|
||||
} else {
|
||||
$record = (object)[
|
||||
'courseid' => $courseid,
|
||||
'total_ratings' => $stats->total_ratings,
|
||||
'average_rating' => round($stats->average_rating, 2),
|
||||
'total_comments' => $stats->total_comments,
|
||||
'timemodified' => $time
|
||||
];
|
||||
$DB->insert_record('block_catcourse_stats', $record);
|
||||
}
|
||||
}
|
||||
}
|
||||
39
category_courses/db/access.php
Normal file
39
category_courses/db/access.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$capabilities = [
|
||||
'block/category_courses:view' => [
|
||||
'captype' => 'read',
|
||||
'contextlevel' => CONTEXT_BLOCK,
|
||||
'archetypes' => [
|
||||
'user' => CAP_ALLOW,
|
||||
'student' => CAP_ALLOW,
|
||||
'teacher' => CAP_ALLOW,
|
||||
'editingteacher' => CAP_ALLOW,
|
||||
'manager' => CAP_ALLOW
|
||||
],
|
||||
],
|
||||
'block/category_courses:manage' => [
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_SYSTEM,
|
||||
'archetypes' => [
|
||||
'manager' => CAP_ALLOW
|
||||
],
|
||||
],
|
||||
'block/category_courses:myaddinstance' => [
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_SYSTEM,
|
||||
'archetypes' => [
|
||||
'user' => CAP_ALLOW
|
||||
],
|
||||
],
|
||||
'block/category_courses:addinstance' => [
|
||||
'riskbitmask' => RISK_SPAM | RISK_XSS,
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_BLOCK,
|
||||
'archetypes' => [
|
||||
'editingteacher' => CAP_ALLOW,
|
||||
'manager' => CAP_ALLOW
|
||||
],
|
||||
],
|
||||
];
|
||||
13
category_courses/db/caches.php
Normal file
13
category_courses/db/caches.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$definitions = [
|
||||
'categorydata' => [
|
||||
'mode' => cache_store::MODE_APPLICATION,
|
||||
'simplekeys' => true,
|
||||
'simpledata' => false,
|
||||
'ttl' => 300,
|
||||
'staticacceleration' => true,
|
||||
'staticaccelerationsize' => 100,
|
||||
],
|
||||
];
|
||||
9
category_courses/db/events.php
Normal file
9
category_courses/db/events.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$observers = [
|
||||
[
|
||||
'eventname' => '\core\event\course_category_viewed',
|
||||
'callback' => 'block_category_courses_observer::load_colorpicker_js',
|
||||
],
|
||||
];
|
||||
32
category_courses/db/hooks.php
Normal file
32
category_courses/db/hooks.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Hook callbacks for block_category_courses.
|
||||
*
|
||||
* @package block_category_courses
|
||||
* @copyright 2025 Your Name
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$callbacks = [
|
||||
[
|
||||
'hook' => core\hook\output\before_footer_html_generation::class,
|
||||
'callback' => block_category_courses\hook_callbacks\output_callbacks::class . '::before_footer_html_generation',
|
||||
],
|
||||
];
|
||||
7
category_courses/db/install.php
Normal file
7
category_courses/db/install.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
function xmldb_block_category_courses_install() {
|
||||
// The table is created automatically from install.xml
|
||||
return true;
|
||||
}
|
||||
61
category_courses/db/install.xml
Normal file
61
category_courses/db/install.xml
Normal file
@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="blocks/category_courses/db" VERSION="20250127" COMMENT="XMLDB file for Moodle blocks/category_courses"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
<TABLES>
|
||||
<TABLE NAME="block_catcourse_ratings" COMMENT="Course ratings and comments">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="courseid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="rating" TYPE="int" LENGTH="1" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="comment" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="courseid" TYPE="foreign" FIELDS="courseid" REFTABLE="course" REFFIELDS="id"/>
|
||||
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
<INDEXES>
|
||||
<INDEX NAME="courseid_userid" UNIQUE="true" FIELDS="courseid, userid"/>
|
||||
<INDEX NAME="courseid" UNIQUE="false" FIELDS="courseid"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="block_catcourse_stats" COMMENT="Precalculated course rating statistics">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="courseid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="total_ratings" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="average_rating" TYPE="number" LENGTH="3" DECIMALS="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="total_comments" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="courseid" TYPE="foreign" FIELDS="courseid" REFTABLE="course" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
<INDEXES>
|
||||
<INDEX NAME="courseid" UNIQUE="true" FIELDS="courseid"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="block_catcourse_images" COMMENT="Category custom images and colors">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="categoryid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="imageurl" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="bgcolor" TYPE="char" LENGTH="7" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
</KEYS>
|
||||
<INDEXES>
|
||||
<INDEX NAME="categoryid" UNIQUE="true" FIELDS="categoryid"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
60
category_courses/db/services.php
Normal file
60
category_courses/db/services.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* External services for block_category_courses.
|
||||
*
|
||||
* @package block_category_courses
|
||||
* @copyright 2025 Tu Nombre
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$functions = [
|
||||
'block_category_courses_get_category_data' => [
|
||||
'classname' => 'block_category_courses\external\get_category_data',
|
||||
'methodname' => 'execute',
|
||||
'description' => 'Get category data for hierarchical navigation',
|
||||
'type' => 'read',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'block_category_courses_save_image' => [
|
||||
'classname' => 'block_category_courses\external\save_image',
|
||||
'methodname' => 'execute',
|
||||
'description' => 'Save category image and color',
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'block_category_courses_set_rating' => [
|
||||
'classname' => 'block_category_courses\external\rating_service',
|
||||
'methodname' => 'set_rating',
|
||||
'description' => 'Set course rating and comment',
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'block_category_courses_get_comments' => [
|
||||
'classname' => 'block_category_courses\external\rating_service',
|
||||
'methodname' => 'get_comments',
|
||||
'description' => 'Get course comments',
|
||||
'type' => 'read',
|
||||
'ajax' => true,
|
||||
'loginrequired' => false,
|
||||
],
|
||||
];
|
||||
90
category_courses/db/upgrade.php
Normal file
90
category_courses/db/upgrade.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Upgrade script for block_category_courses.
|
||||
*
|
||||
* @package block_category_courses
|
||||
* @copyright 2025 Tu Nombre
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Upgrade function for block_category_courses.
|
||||
*
|
||||
* @param int $oldversion The old version of the plugin
|
||||
* @return bool
|
||||
*/
|
||||
function xmldb_block_category_courses_upgrade($oldversion) {
|
||||
global $DB;
|
||||
|
||||
$dbman = $DB->get_manager();
|
||||
|
||||
// Add rating system tables if they don't exist
|
||||
if ($oldversion < 2025073205) {
|
||||
|
||||
// Define table block_catcourse_ratings to be created.
|
||||
$table = new xmldb_table('block_catcourse_ratings');
|
||||
|
||||
// Adding fields to table block_catcourse_ratings.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('courseid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('rating', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('comment', XMLDB_TYPE_TEXT, null, null, null, null, null);
|
||||
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
|
||||
// Adding keys to table block_catcourse_ratings.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
|
||||
// Adding indexes to table block_catcourse_ratings.
|
||||
$table->add_index('idx_courseid_userid', XMLDB_INDEX_UNIQUE, ['courseid', 'userid']);
|
||||
|
||||
// Conditionally launch create table for block_catcourse_ratings.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Define table block_catcourse_stats to be created.
|
||||
$table = new xmldb_table('block_catcourse_stats');
|
||||
|
||||
// Adding fields to table block_catcourse_stats.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('courseid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('average_rating', XMLDB_TYPE_NUMBER, '3,2', null, XMLDB_NOTNULL, null, '0.00');
|
||||
$table->add_field('total_ratings', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('total_comments', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
|
||||
// Adding keys to table block_catcourse_stats.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
|
||||
// Adding indexes to table block_catcourse_stats.
|
||||
$table->add_index('idx_courseid', XMLDB_INDEX_UNIQUE, ['courseid']);
|
||||
|
||||
// Conditionally launch create table for block_catcourse_stats.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Block_category_courses savepoint reached.
|
||||
upgrade_block_savepoint(true, 2025073205, 'category_courses');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
43
category_courses/edit_form.php
Normal file
43
category_courses/edit_form.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
class block_category_courses_edit_form extends block_edit_form {
|
||||
|
||||
protected function specific_definition($mform) {
|
||||
// Section header
|
||||
$mform->addElement('header', 'configheader', get_string('blocksettings', 'block'));
|
||||
|
||||
// Custom title
|
||||
$mform->addElement('text', 'config_title', get_string('customtitle', 'block_category_courses'));
|
||||
$mform->setType('config_title', PARAM_TEXT);
|
||||
|
||||
// Category display options
|
||||
$mform->addElement('advcheckbox', 'config_showprogress', get_string('showprogress', 'block_category_courses'));
|
||||
$mform->addElement('advcheckbox', 'config_showdescription', get_string('showdescription', 'block_category_courses'));
|
||||
$mform->addElement('advcheckbox', 'config_showcoursecount', get_string('showcoursecount', 'block_category_courses'));
|
||||
|
||||
// Course display options
|
||||
$mform->addElement('advcheckbox', 'config_showcourseprogress', get_string('showcourseprogress', 'block_category_courses'));
|
||||
$mform->addElement('advcheckbox', 'config_showcoursedescription', get_string('showcoursedescription', 'block_category_courses'));
|
||||
$mform->addElement('advcheckbox', 'config_showcourseshortname', get_string('showcourseshortname', 'block_category_courses'));
|
||||
|
||||
// Sort order override
|
||||
$mform->addElement('select', 'config_sortorder', get_string('sortorder', 'block_category_courses'), [
|
||||
'' => get_string('default'),
|
||||
'core' => get_string('sortorder_core', 'block_category_courses'),
|
||||
'alphabetical' => get_string('sortorder_alphabetical', 'block_category_courses'),
|
||||
'coursecount' => get_string('sortorder_coursecount', 'block_category_courses'),
|
||||
'progress' => get_string('sortorder_progress', 'block_category_courses')
|
||||
]);
|
||||
|
||||
|
||||
|
||||
// Set defaults
|
||||
$mform->setDefault('config_showprogress', 1);
|
||||
$mform->setDefault('config_showdescription', 1);
|
||||
$mform->setDefault('config_showcoursecount', 1);
|
||||
$mform->setDefault('config_showcourseprogress', 1);
|
||||
$mform->setDefault('config_showcoursedescription', 1);
|
||||
$mform->setDefault('config_showcourseshortname', 1);
|
||||
}
|
||||
}
|
||||
160
category_courses/edit_image.php
Normal file
160
category_courses/edit_image.php
Normal file
@ -0,0 +1,160 @@
|
||||
<?php
|
||||
require_once('../../config.php');
|
||||
require_once($CFG->libdir.'/adminlib.php');
|
||||
require_once($CFG->libdir.'/formslib.php');
|
||||
|
||||
require_login();
|
||||
require_capability('block/category_courses:manage', context_system::instance());
|
||||
|
||||
$categoryid = required_param('categoryid', PARAM_INT);
|
||||
|
||||
$PAGE->set_url('/blocks/category_courses/edit_image.php', ['categoryid' => $categoryid]);
|
||||
$PAGE->set_context(context_system::instance());
|
||||
|
||||
$category = core_course_category::get($categoryid);
|
||||
$PAGE->set_title(get_string('editcategoryimage', 'block_category_courses') . ': ' . $category->name);
|
||||
$PAGE->set_heading(get_string('editcategoryimage', 'block_category_courses'));
|
||||
|
||||
admin_externalpage_setup('block_category_courses_images');
|
||||
|
||||
// Load colorpicker JavaScript
|
||||
$PAGE->requires->js_call_amd('block_category_courses/colorpicker', 'init');
|
||||
|
||||
class category_image_form extends moodleform {
|
||||
protected function definition() {
|
||||
$mform = $this->_form;
|
||||
$categoryid = $this->_customdata['categoryid'];
|
||||
|
||||
$mform->addElement('hidden', 'categoryid', $categoryid);
|
||||
$mform->setType('categoryid', PARAM_INT);
|
||||
|
||||
// Image upload
|
||||
$mform->addElement('filemanager', 'categoryimage', get_string('categoryimage', 'block_category_courses'),
|
||||
null, [
|
||||
'subdirs' => 0,
|
||||
'maxbytes' => 2097152, // 2MB
|
||||
'maxfiles' => 1,
|
||||
'accepted_types' => ['web_image']
|
||||
]);
|
||||
$mform->addHelpButton('categoryimage', 'categoryimage', 'block_category_courses');
|
||||
|
||||
// Background color with colorpicker
|
||||
$mform->addElement('text', 'categorycolor', get_string('categorycolor', 'block_category_courses'));
|
||||
$mform->setType('categorycolor', PARAM_TEXT);
|
||||
$mform->setDefault('categorycolor', '#667eea');
|
||||
$mform->addHelpButton('categorycolor', 'categorycolor', 'block_category_courses');
|
||||
|
||||
$this->add_action_buttons();
|
||||
}
|
||||
}
|
||||
|
||||
$customdata = $DB->get_record('block_catcourse_images', ['categoryid' => $categoryid]);
|
||||
|
||||
$form = new category_image_form(null, ['categoryid' => $categoryid]);
|
||||
|
||||
if ($form->is_cancelled()) {
|
||||
redirect(new moodle_url('/blocks/category_courses/manage_images.php'));
|
||||
} else if ($data = $form->get_data()) {
|
||||
$context = context_system::instance();
|
||||
|
||||
// Handle file upload
|
||||
$imageurl = '';
|
||||
if ($draftitemid = $data->categoryimage) {
|
||||
file_save_draft_area_files($draftitemid, $context->id, 'block_category_courses',
|
||||
'categoryimage', $categoryid, ['subdirs' => false, 'maxfiles' => 1]);
|
||||
|
||||
$fs = get_file_storage();
|
||||
$files = $fs->get_area_files($context->id, 'block_category_courses', 'categoryimage', $categoryid, 'filename', false);
|
||||
if (!empty($files)) {
|
||||
$file = reset($files);
|
||||
$imageurl = moodle_url::make_pluginfile_url(
|
||||
$context->id, 'block_category_courses', 'categoryimage', $categoryid, '/', $file->get_filename()
|
||||
)->out();
|
||||
}
|
||||
}
|
||||
|
||||
// Save to database
|
||||
if ($customdata) {
|
||||
$customdata->bgcolor = $data->categorycolor;
|
||||
if ($imageurl) {
|
||||
$customdata->imageurl = $imageurl;
|
||||
}
|
||||
$customdata->timemodified = time();
|
||||
$DB->update_record('block_catcourse_images', $customdata);
|
||||
} else {
|
||||
$record = new stdClass();
|
||||
$record->categoryid = $categoryid;
|
||||
$record->bgcolor = $data->categorycolor;
|
||||
$record->imageurl = $imageurl;
|
||||
$record->timecreated = time();
|
||||
$record->timemodified = time();
|
||||
$DB->insert_record('block_catcourse_images', $record);
|
||||
}
|
||||
|
||||
redirect(new moodle_url('/blocks/category_courses/manage_images.php'),
|
||||
get_string('categoryupdated', 'block_category_courses'));
|
||||
}
|
||||
|
||||
// Set form data
|
||||
$formdata = new stdClass();
|
||||
$formdata->categoryid = $categoryid;
|
||||
|
||||
// Always prepare file area (required for filemanager)
|
||||
$context = context_system::instance();
|
||||
$draftitemid = file_get_submitted_draft_itemid('categoryimage');
|
||||
file_prepare_draft_area($draftitemid, $context->id, 'block_category_courses', 'categoryimage',
|
||||
$categoryid, ['subdirs' => false, 'maxfiles' => 1]);
|
||||
$formdata->categoryimage = $draftitemid;
|
||||
|
||||
if ($customdata) {
|
||||
$formdata->categorycolor = $customdata->bgcolor;
|
||||
}
|
||||
|
||||
$form->set_data($formdata);
|
||||
|
||||
echo $OUTPUT->header();
|
||||
|
||||
echo html_writer::tag('h2', get_string('editcategoryimage', 'block_category_courses') . ': ' . format_string($category->name));
|
||||
|
||||
// Show current preview
|
||||
if ($customdata) {
|
||||
echo html_writer::start_div('current-preview mb-3');
|
||||
echo html_writer::tag('h4', get_string('currentpreview', 'block_category_courses'));
|
||||
|
||||
$bgcolor = !empty($customdata->bgcolor) ? $customdata->bgcolor : '#667eea';
|
||||
|
||||
echo html_writer::start_div('category-card', ['style' => "background: {$bgcolor}; color: white; width: 280px; height: 168px; border-radius: 12px; overflow: hidden; position: relative; box-shadow: 0 2px 8px rgba(0,0,0,0.1);"]);
|
||||
|
||||
// Check for actual uploaded file
|
||||
$context = context_system::instance();
|
||||
$fs = get_file_storage();
|
||||
$files = $fs->get_area_files($context->id, 'block_category_courses', 'categoryimage', $categoryid, 'filename', false);
|
||||
|
||||
if (!empty($files)) {
|
||||
$file = reset($files);
|
||||
$imageurl = moodle_url::make_pluginfile_url(
|
||||
$context->id,
|
||||
'block_category_courses',
|
||||
'categoryimage',
|
||||
$categoryid,
|
||||
'/',
|
||||
$file->get_filename()
|
||||
)->out();
|
||||
echo html_writer::img($imageurl, $category->name, ['style' => 'width: 100%; height: 100%; object-fit: cover;']);
|
||||
} else {
|
||||
// Show initials
|
||||
$initials = '';
|
||||
$words = explode(' ', $category->name);
|
||||
foreach (array_slice($words, 0, 2) as $word) {
|
||||
$initials .= strtoupper(substr($word, 0, 1));
|
||||
}
|
||||
echo html_writer::div($initials, 'image-initials', ['style' => 'width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; font-size: 2rem; font-weight: 700; text-shadow: 0 1px 3px rgba(0,0,0,0.3);']);
|
||||
}
|
||||
|
||||
echo html_writer::end_div();
|
||||
echo html_writer::end_div();
|
||||
}
|
||||
|
||||
$form->display();
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
182
category_courses/lang/en/block_category_courses.php
Normal file
182
category_courses/lang/en/block_category_courses.php
Normal file
@ -0,0 +1,182 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Language strings for block_category_courses.
|
||||
*
|
||||
* @package block_category_courses
|
||||
* @copyright 2025 Tu Nombre
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['pluginname'] = 'Category Courses';
|
||||
$string['notauth'] = 'You must be logged in to view this content.';
|
||||
$string['errorsetrating'] = 'Error setting rating';
|
||||
$string['rating'] = 'Rating';
|
||||
$string['comment'] = 'Comment';
|
||||
$string['addcomment'] = 'Add comment';
|
||||
$string['submitrating'] = 'Submit rating';
|
||||
$string['averagerating'] = 'Average: {$a}';
|
||||
$string['totalratings'] = '{$a} ratings';
|
||||
$string['nocomments'] = 'No comments yet';
|
||||
$string['ratingadded'] = 'Rating added successfully';
|
||||
$string['ratingupdated'] = 'Rating updated successfully';
|
||||
$string['updatingrating'] = 'Updating your existing rating';
|
||||
$string['ratingerror'] = 'Error saving rating';
|
||||
$string['invalidrating'] = 'Invalid rating value';
|
||||
$string['ratingrequired'] = 'Rating is required';
|
||||
$string['commentoptional'] = 'Comment (optional)';
|
||||
$string['ratingsubmitted'] = 'Your rating has been submitted';
|
||||
$string['ratingdeleted'] = 'Rating deleted successfully';
|
||||
$string['comments'] = 'Comments';
|
||||
$string['viewcomments'] = 'View comments';
|
||||
$string['rateandcomment'] = 'Rate and comment';
|
||||
$string['yourrating'] = 'Your rating';
|
||||
$string['noratings'] = 'No ratings yet';
|
||||
|
||||
// Settings strings
|
||||
$string['categorysettings'] = 'Category Settings';
|
||||
$string['categorysettings_desc'] = 'Configure how categories are displayed';
|
||||
$string['showprogress'] = 'Show Progress';
|
||||
$string['showprogress_desc'] = 'Display progress bars for categories';
|
||||
$string['showdescription'] = 'Show Description';
|
||||
$string['showdescription_desc'] = 'Display category descriptions';
|
||||
$string['showcoursecount'] = 'Show Course Count';
|
||||
$string['showcoursecount_desc'] = 'Display number of courses in each category';
|
||||
$string['showallcategories'] = 'Show All Categories';
|
||||
$string['showallcategories_desc'] = 'Show all visible categories to students, not just those where they are enrolled';
|
||||
$string['coursesettings'] = 'Course Settings';
|
||||
$string['coursesettings_desc'] = 'Configure how courses are displayed';
|
||||
$string['showcourseprogress'] = 'Show Course Progress';
|
||||
$string['showcourseprogress_desc'] = 'Display progress bars for courses';
|
||||
$string['showcoursedescription'] = 'Show Course Description';
|
||||
$string['showcoursedescription_desc'] = 'Display course descriptions';
|
||||
$string['showcourseshortname'] = 'Show Course Short Name';
|
||||
$string['showcourseshortname_desc'] = 'Display course short names';
|
||||
$string['sortorder'] = 'Sort Order';
|
||||
$string['sortorder_desc'] = 'How to sort categories';
|
||||
$string['sortorder_core'] = 'Default (Moodle order)';
|
||||
$string['sortorder_alphabetical'] = 'Alphabetical';
|
||||
$string['sortorder_coursecount'] = 'By course count';
|
||||
$string['sortorder_progress'] = 'By progress';
|
||||
$string['colorsettings'] = 'Color Settings';
|
||||
$string['colorsettings_desc'] = 'Customize colors for the block';
|
||||
$string['buttoncolor'] = 'Button Color';
|
||||
$string['buttoncolor_help'] = 'Color for navigation buttons';
|
||||
$string['progresscolor1'] = 'Progress Color 1';
|
||||
$string['progresscolor1_help'] = 'Start color for progress bars';
|
||||
$string['progresscolor2'] = 'Progress Color 2';
|
||||
$string['progresscolor2_help'] = 'End color for progress bars';
|
||||
$string['manageimages'] = 'Manage Category Images';
|
||||
|
||||
// Template strings
|
||||
$string['navigation'] = 'Navigation';
|
||||
$string['viewcategory'] = 'View category';
|
||||
$string['explore'] = 'Explore';
|
||||
$string['coursescounter'] = 'courses';
|
||||
$string['nocontent'] = 'No content available';
|
||||
$string['enter'] = 'Enter';
|
||||
$string['configured'] = 'Configured';
|
||||
$string['categories'] = 'Categories';
|
||||
$string['customtitle'] = 'Custom title';
|
||||
|
||||
// Image management strings
|
||||
$string['editcategoryimage'] = 'Edit Category Image';
|
||||
$string['categoryimage'] = 'Category Image';
|
||||
$string['categoryimage_help'] = 'Upload an image for this category or provide an image URL';
|
||||
$string['categorycolor'] = 'Background Color';
|
||||
$string['categorycolor_help'] = 'Choose a background color for this category';
|
||||
$string['currentpreview'] = 'Current Preview';
|
||||
$string['categoryupdated'] = 'Category updated successfully';
|
||||
$string['imageurl'] = 'Image URL';
|
||||
$string['imageurl_help'] = 'Provide a direct URL to an image';
|
||||
$string['defaultstatus'] = 'Default Status';
|
||||
|
||||
// Privacy strings
|
||||
$string['privacy:metadata:block_catcourse_ratings'] = 'Information about user ratings for courses';
|
||||
$string['privacy:metadata:block_catcourse_ratings:userid'] = 'The ID of the user who made the rating';
|
||||
$string['privacy:metadata:block_catcourse_ratings:courseid'] = 'The ID of the course being rated';
|
||||
$string['privacy:metadata:block_catcourse_ratings:rating'] = 'The rating value (1-5 stars)';
|
||||
$string['privacy:metadata:block_catcourse_ratings:comment'] = 'Optional comment with the rating';
|
||||
$string['privacy:metadata:block_catcourse_ratings:timemodified'] = 'When the rating was last modified';
|
||||
|
||||
// Additional template strings
|
||||
$string['completed'] = 'Completed';
|
||||
$string['inprogress'] = 'In Progress';
|
||||
$string['notstarted'] = 'Not Started';
|
||||
$string['enrolled'] = 'Enrolled';
|
||||
$string['notenrolled'] = 'Not Enrolled';
|
||||
$string['totalcourses'] = 'total courses';
|
||||
$string['completedcourses'] = 'Completed Courses';
|
||||
$string['progresspercentage'] = 'Progress';
|
||||
$string['viewcourse'] = 'View Course';
|
||||
$string['entercategory'] = 'Enter Category';
|
||||
$string['backtocategories'] = 'Back to Categories';
|
||||
$string['loading'] = 'Loading...';
|
||||
$string['nocoursesavailable'] = 'No courses available in this category';
|
||||
$string['nocategoriesavailable'] = 'No categories available';
|
||||
$string['errorloadingcontent'] = 'Error loading content';
|
||||
$string['tryagain'] = 'Try Again';
|
||||
$string['home'] = 'Home';
|
||||
$string['breadcrumbnavigation'] = 'Breadcrumb Navigation';
|
||||
|
||||
// Course card strings
|
||||
$string['coursesummary'] = 'Course Summary';
|
||||
$string['courseshortname'] = 'Course Short Name';
|
||||
$string['coursefullname'] = 'Course Full Name';
|
||||
$string['courseimage'] = 'Course Image';
|
||||
$string['courserating'] = 'Course Rating';
|
||||
$string['ratethiscourse'] = 'Rate this course';
|
||||
$string['viewallcomments'] = 'View all comments';
|
||||
$string['addyourrating'] = 'Add your rating';
|
||||
$string['updateyourrating'] = 'Update your rating';
|
||||
|
||||
// Error messages
|
||||
$string['errornocategory'] = 'Category not found';
|
||||
$string['errornocourse'] = 'Course not found';
|
||||
$string['errornopermission'] = 'You do not have permission to view this content';
|
||||
$string['errornodata'] = 'No data available';
|
||||
$string['errorajax'] = 'Error loading data via AJAX';
|
||||
$string['errordatabase'] = 'Database error occurred';
|
||||
|
||||
// Success messages
|
||||
$string['successsaved'] = 'Settings saved successfully';
|
||||
$string['successupdated'] = 'Updated successfully';
|
||||
$string['successdeleted'] = 'Deleted successfully';
|
||||
|
||||
// Configuration strings
|
||||
$string['configtitle'] = 'Block Title';
|
||||
$string['configtitle_help'] = 'Custom title for this block instance';
|
||||
$string['configshowprogress'] = 'Show Progress Bars';
|
||||
$string['configshowdescription'] = 'Show Descriptions';
|
||||
$string['configshowcoursecount'] = 'Show Course Count';
|
||||
$string['configsortorder'] = 'Sort Order';
|
||||
|
||||
// Capability strings
|
||||
$string['category_courses:addinstance'] = 'Add a new Category Courses block';
|
||||
$string['category_courses:myaddinstance'] = 'Add a new Category Courses block to Dashboard';
|
||||
$string['category_courses:manage'] = 'Manage Category Courses settings';
|
||||
$string['category_courses:rate'] = 'Rate courses';
|
||||
$string['category_courses:viewratings'] = 'View course ratings';
|
||||
|
||||
// Block configuration
|
||||
$string['blocktitle'] = 'Category Courses';
|
||||
$string['blockstring'] = 'Category Courses Block';
|
||||
$string['descconfig'] = 'Description of the config section';
|
||||
$string['descfoo'] = 'Config description';
|
||||
$string['headerconfig'] = 'Config section header';
|
||||
$string['labelfoo'] = 'Config label';
|
||||
$string['categoryhidden'] = 'Hidden category';
|
||||
182
category_courses/lang/es_mx/block_category_courses.php
Normal file
182
category_courses/lang/es_mx/block_category_courses.php
Normal file
@ -0,0 +1,182 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Cadenas de idioma para block_category_courses (Español México).
|
||||
*
|
||||
* @package block_category_courses
|
||||
* @copyright 2025 Tu Nombre
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['pluginname'] = 'Cursos por Categoría';
|
||||
$string['notauth'] = 'Debes iniciar sesión para ver este contenido.';
|
||||
$string['errorsetrating'] = 'Error al establecer calificación';
|
||||
$string['rating'] = 'Calificación';
|
||||
$string['comment'] = 'Comentario';
|
||||
$string['addcomment'] = 'Agregar comentario';
|
||||
$string['submitrating'] = 'Enviar calificación';
|
||||
$string['averagerating'] = 'Promedio: {$a}';
|
||||
$string['totalratings'] = '{$a} calificaciones';
|
||||
$string['nocomments'] = 'Aún no hay comentarios';
|
||||
$string['ratingadded'] = 'Calificación agregada exitosamente';
|
||||
$string['ratingupdated'] = 'Calificación actualizada exitosamente';
|
||||
$string['updatingrating'] = 'Actualizando tu calificación existente';
|
||||
$string['ratingerror'] = 'Error al guardar calificación';
|
||||
$string['invalidrating'] = 'Valor de calificación inválido';
|
||||
$string['ratingrequired'] = 'La calificación es requerida';
|
||||
$string['commentoptional'] = 'Comentario (opcional)';
|
||||
$string['ratingsubmitted'] = 'Tu calificación ha sido enviada';
|
||||
$string['ratingdeleted'] = 'Calificación eliminada exitosamente';
|
||||
$string['comments'] = 'Comentarios';
|
||||
$string['viewcomments'] = 'Ver comentarios';
|
||||
$string['rateandcomment'] = 'Calificar y comentar';
|
||||
$string['yourrating'] = 'Tu calificación';
|
||||
$string['noratings'] = 'Aún no hay calificaciones';
|
||||
|
||||
// Cadenas de configuración
|
||||
$string['categorysettings'] = 'Configuración de Categorías';
|
||||
$string['categorysettings_desc'] = 'Configura cómo se muestran las categorías';
|
||||
$string['showprogress'] = 'Mostrar Progreso';
|
||||
$string['showprogress_desc'] = 'Mostrar barras de progreso para las categorías';
|
||||
$string['showdescription'] = 'Mostrar Descripción';
|
||||
$string['showdescription_desc'] = 'Mostrar descripciones de las categorías';
|
||||
$string['showcoursecount'] = 'Mostrar Contador de Cursos';
|
||||
$string['showcoursecount_desc'] = 'Mostrar número de cursos en cada categoría';
|
||||
$string['showallcategories'] = 'Mostrar Todas las Categorías';
|
||||
$string['showallcategories_desc'] = 'Mostrar todas las categorías visibles a los estudiantes, no solo aquellas donde están inscritos';
|
||||
$string['coursesettings'] = 'Configuración de Cursos';
|
||||
$string['coursesettings_desc'] = 'Configura cómo se muestran los cursos';
|
||||
$string['showcourseprogress'] = 'Mostrar Progreso del Curso';
|
||||
$string['showcourseprogress_desc'] = 'Mostrar barras de progreso para los cursos';
|
||||
$string['showcoursedescription'] = 'Mostrar Descripción del Curso';
|
||||
$string['showcoursedescription_desc'] = 'Mostrar descripciones de los cursos';
|
||||
$string['showcourseshortname'] = 'Mostrar Nombre Corto del Curso';
|
||||
$string['showcourseshortname_desc'] = 'Mostrar nombres cortos de los cursos';
|
||||
$string['sortorder'] = 'Orden de Clasificación';
|
||||
$string['sortorder_desc'] = 'Cómo ordenar las categorías';
|
||||
$string['sortorder_core'] = 'Predeterminado (orden de Moodle)';
|
||||
$string['sortorder_alphabetical'] = 'Alfabético';
|
||||
$string['sortorder_coursecount'] = 'Por cantidad de cursos';
|
||||
$string['sortorder_progress'] = 'Por progreso';
|
||||
$string['colorsettings'] = 'Configuración de Colores';
|
||||
$string['colorsettings_desc'] = 'Personalizar colores para el bloque';
|
||||
$string['buttoncolor'] = 'Color de Botones';
|
||||
$string['buttoncolor_help'] = 'Color para los botones de navegación';
|
||||
$string['progresscolor1'] = 'Color de Progreso 1';
|
||||
$string['progresscolor1_help'] = 'Color inicial para las barras de progreso';
|
||||
$string['progresscolor2'] = 'Color de Progreso 2';
|
||||
$string['progresscolor2_help'] = 'Color final para las barras de progreso';
|
||||
$string['manageimages'] = 'Administrar Imágenes de Categorías';
|
||||
|
||||
// Cadenas de plantillas
|
||||
$string['navigation'] = 'Navegación';
|
||||
$string['viewcategory'] = 'Ver categoría';
|
||||
$string['explore'] = 'Explorar';
|
||||
$string['coursescounter'] = 'cursos';
|
||||
$string['nocontent'] = 'No hay contenido disponible';
|
||||
$string['enter'] = 'Ingresar';
|
||||
$string['configured'] = 'Configurado';
|
||||
$string['categories'] = 'Categorías';
|
||||
$string['customtitle'] = 'Título personalizado';
|
||||
|
||||
// Cadenas de gestión de imágenes
|
||||
$string['editcategoryimage'] = 'Editar Imagen de Categoría';
|
||||
$string['categoryimage'] = 'Imagen de Categoría';
|
||||
$string['categoryimage_help'] = 'Sube una imagen para esta categoría o proporciona una URL de imagen';
|
||||
$string['categorycolor'] = 'Color de Fondo';
|
||||
$string['categorycolor_help'] = 'Elige un color de fondo para esta categoría';
|
||||
$string['currentpreview'] = 'Vista Previa Actual';
|
||||
$string['categoryupdated'] = 'Categoría actualizada exitosamente';
|
||||
$string['imageurl'] = 'URL de Imagen';
|
||||
$string['imageurl_help'] = 'Proporciona una URL directa a una imagen';
|
||||
$string['defaultstatus'] = 'Estado Predeterminado';
|
||||
|
||||
// Cadenas de privacidad
|
||||
$string['privacy:metadata:block_catcourse_ratings'] = 'Información sobre las calificaciones de usuarios para cursos';
|
||||
$string['privacy:metadata:block_catcourse_ratings:userid'] = 'El ID del usuario que hizo la calificación';
|
||||
$string['privacy:metadata:block_catcourse_ratings:courseid'] = 'El ID del curso siendo calificado';
|
||||
$string['privacy:metadata:block_catcourse_ratings:rating'] = 'El valor de la calificación (1-5 estrellas)';
|
||||
$string['privacy:metadata:block_catcourse_ratings:comment'] = 'Comentario opcional con la calificación';
|
||||
$string['privacy:metadata:block_catcourse_ratings:timemodified'] = 'Cuándo fue modificada la calificación por última vez';
|
||||
|
||||
// Cadenas adicionales de plantillas
|
||||
$string['completed'] = 'Completado';
|
||||
$string['inprogress'] = 'En Progreso';
|
||||
$string['notstarted'] = 'No Iniciado';
|
||||
$string['enrolled'] = 'Inscrito';
|
||||
$string['notenrolled'] = 'No Inscrito';
|
||||
$string['totalcourses'] = 'cursos totales';
|
||||
$string['completedcourses'] = 'Cursos Completados';
|
||||
$string['progresspercentage'] = 'Progreso';
|
||||
$string['viewcourse'] = 'Ver Curso';
|
||||
$string['entercategory'] = 'Ingresar a Categoría';
|
||||
$string['backtocategories'] = 'Volver a Categorías';
|
||||
$string['loading'] = 'Cargando...';
|
||||
$string['nocoursesavailable'] = 'No hay cursos disponibles en esta categoría';
|
||||
$string['nocategoriesavailable'] = 'No hay categorías disponibles';
|
||||
$string['errorloadingcontent'] = 'Error al cargar contenido';
|
||||
$string['tryagain'] = 'Intentar de Nuevo';
|
||||
$string['home'] = 'Inicio';
|
||||
$string['breadcrumbnavigation'] = 'Navegación de Migas de Pan';
|
||||
|
||||
// Cadenas de tarjetas de curso
|
||||
$string['coursesummary'] = 'Resumen del Curso';
|
||||
$string['courseshortname'] = 'Nombre Corto del Curso';
|
||||
$string['coursefullname'] = 'Nombre Completo del Curso';
|
||||
$string['courseimage'] = 'Imagen del Curso';
|
||||
$string['courserating'] = 'Calificación del Curso';
|
||||
$string['ratethiscourse'] = 'Calificar este curso';
|
||||
$string['viewallcomments'] = 'Ver todos los comentarios';
|
||||
$string['addyourrating'] = 'Agregar tu calificación';
|
||||
$string['updateyourrating'] = 'Actualizar tu calificación';
|
||||
|
||||
// Mensajes de error
|
||||
$string['errornocategory'] = 'Categoría no encontrada';
|
||||
$string['errornocourse'] = 'Curso no encontrado';
|
||||
$string['errornopermission'] = 'No tienes permiso para ver este contenido';
|
||||
$string['errornodata'] = 'No hay datos disponibles';
|
||||
$string['errorajax'] = 'Error al cargar datos vía AJAX';
|
||||
$string['errordatabase'] = 'Ocurrió un error de base de datos';
|
||||
|
||||
// Mensajes de éxito
|
||||
$string['successsaved'] = 'Configuración guardada exitosamente';
|
||||
$string['successupdated'] = 'Actualizado exitosamente';
|
||||
$string['successdeleted'] = 'Eliminado exitosamente';
|
||||
|
||||
// Cadenas de configuración
|
||||
$string['configtitle'] = 'Título del Bloque';
|
||||
$string['configtitle_help'] = 'Título personalizado para esta instancia del bloque';
|
||||
$string['configshowprogress'] = 'Mostrar Barras de Progreso';
|
||||
$string['configshowdescription'] = 'Mostrar Descripciones';
|
||||
$string['configshowcoursecount'] = 'Mostrar Contador de Cursos';
|
||||
$string['configsortorder'] = 'Orden de Clasificación';
|
||||
|
||||
// Cadenas de capacidades
|
||||
$string['category_courses:addinstance'] = 'Agregar un nuevo bloque de Cursos por Categoría';
|
||||
$string['category_courses:myaddinstance'] = 'Agregar un nuevo bloque de Cursos por Categoría al Tablero';
|
||||
$string['category_courses:manage'] = 'Administrar configuración de Cursos por Categoría';
|
||||
$string['category_courses:rate'] = 'Calificar cursos';
|
||||
$string['category_courses:viewratings'] = 'Ver calificaciones de cursos';
|
||||
|
||||
// Configuración del bloque
|
||||
$string['blocktitle'] = 'Cursos por Categoría';
|
||||
$string['blockstring'] = 'Bloque de Cursos por Categoría';
|
||||
$string['descconfig'] = 'Descripción de la sección de configuración';
|
||||
$string['descfoo'] = 'Descripción de configuración';
|
||||
$string['headerconfig'] = 'Encabezado de sección de configuración';
|
||||
$string['labelfoo'] = 'Etiqueta de configuración';
|
||||
$string['categoryhidden'] = 'Categoría oculta';
|
||||
182
category_courses/lang/fil/block_category_courses.php
Normal file
182
category_courses/lang/fil/block_category_courses.php
Normal file
@ -0,0 +1,182 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Mga string ng wika para sa block_category_courses (Filipino).
|
||||
*
|
||||
* @package block_category_courses
|
||||
* @copyright 2025 Your Name
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['pluginname'] = 'Mga Kurso ayon sa Kategorya';
|
||||
$string['notauth'] = 'Kailangan mong mag-login upang makita ang nilalaman na ito.';
|
||||
$string['errorsetrating'] = 'Error sa pagtatakda ng rating';
|
||||
$string['rating'] = 'Rating';
|
||||
$string['comment'] = 'Komento';
|
||||
$string['addcomment'] = 'Magdagdag ng komento';
|
||||
$string['submitrating'] = 'Isumite ang rating';
|
||||
$string['averagerating'] = 'Average: {$a}';
|
||||
$string['totalratings'] = '{$a} mga rating';
|
||||
$string['nocomments'] = 'Walang mga komento pa';
|
||||
$string['ratingadded'] = 'Matagumpay na naidagdag ang rating';
|
||||
$string['ratingupdated'] = 'Matagumpay na na-update ang rating';
|
||||
$string['updatingrating'] = 'Ina-update ang inyong kasalukuyang rating';
|
||||
$string['ratingerror'] = 'Error sa pag-save ng rating';
|
||||
$string['invalidrating'] = 'Hindi valid na halaga ng rating';
|
||||
$string['ratingrequired'] = 'Kailangan ang rating';
|
||||
$string['commentoptional'] = 'Komento (opsyonal)';
|
||||
$string['ratingsubmitted'] = 'Naisumite na ang inyong rating';
|
||||
$string['ratingdeleted'] = 'Matagumpay na natanggal ang rating';
|
||||
$string['comments'] = 'Mga Komento';
|
||||
$string['viewcomments'] = 'Tingnan ang mga komento';
|
||||
$string['rateandcomment'] = 'Mag-rate at mag-komento';
|
||||
$string['yourrating'] = 'Inyong rating';
|
||||
$string['noratings'] = 'Walang mga rating pa';
|
||||
|
||||
// Mga string ng settings
|
||||
$string['categorysettings'] = 'Mga Setting ng Kategorya';
|
||||
$string['categorysettings_desc'] = 'I-configure kung paano ipapakita ang mga kategorya';
|
||||
$string['showprogress'] = 'Ipakita ang Progress';
|
||||
$string['showprogress_desc'] = 'Ipakita ang mga progress bar para sa mga kategorya';
|
||||
$string['showdescription'] = 'Ipakita ang Paglalarawan';
|
||||
$string['showdescription_desc'] = 'Ipakita ang mga paglalarawan ng kategorya';
|
||||
$string['showcoursecount'] = 'Ipakita ang Bilang ng Kurso';
|
||||
$string['showcoursecount_desc'] = 'Ipakita ang bilang ng mga kurso sa bawat kategorya';
|
||||
$string['coursesettings'] = 'Mga Setting ng Kurso';
|
||||
$string['coursesettings_desc'] = 'I-configure kung paano ipapakita ang mga kurso';
|
||||
$string['showcourseprogress'] = 'Ipakita ang Progress ng Kurso';
|
||||
$string['showcourseprogress_desc'] = 'Ipakita ang mga progress bar para sa mga kurso';
|
||||
$string['showcoursedescription'] = 'Ipakita ang Paglalarawan ng Kurso';
|
||||
$string['showcoursedescription_desc'] = 'Ipakita ang mga paglalarawan ng kurso';
|
||||
$string['showcourseshortname'] = 'Ipakita ang Maikling Pangalan ng Kurso';
|
||||
$string['showcourseshortname_desc'] = 'Ipakita ang mga maikling pangalan ng kurso';
|
||||
$string['sortorder'] = 'Pagkakaayos';
|
||||
$string['sortorder_desc'] = 'Paano ayusin ang mga kategorya';
|
||||
$string['sortorder_core'] = 'Default (pagkakaayos ng Moodle)';
|
||||
$string['sortorder_alphabetical'] = 'Alphabetical';
|
||||
$string['sortorder_coursecount'] = 'Ayon sa bilang ng kurso';
|
||||
$string['sortorder_progress'] = 'Ayon sa progress';
|
||||
$string['colorsettings'] = 'Mga Setting ng Kulay';
|
||||
$string['colorsettings_desc'] = 'I-customize ang mga kulay para sa block';
|
||||
$string['buttoncolor'] = 'Kulay ng Button';
|
||||
$string['buttoncolor_help'] = 'Kulay para sa mga navigation button';
|
||||
$string['progresscolor1'] = 'Kulay ng Progress 1';
|
||||
$string['progresscolor1_help'] = 'Simula ng kulay para sa mga progress bar';
|
||||
$string['progresscolor2'] = 'Kulay ng Progress 2';
|
||||
$string['progresscolor2_help'] = 'Dulo ng kulay para sa mga progress bar';
|
||||
$string['manageimages'] = 'Pamahalaan ang mga Larawan ng Kategorya';
|
||||
|
||||
// Mga string ng template
|
||||
$string['navigation'] = 'Navigation';
|
||||
$string['viewcategory'] = 'Tingnan ang kategorya';
|
||||
$string['explore'] = 'Tuklasin';
|
||||
$string['coursescounter'] = 'mga kurso';
|
||||
$string['nocontent'] = 'Walang available na nilalaman';
|
||||
$string['enter'] = 'Pumasok';
|
||||
$string['configured'] = 'Na-configure';
|
||||
$string['categories'] = 'Mga Kategorya';
|
||||
$string['customtitle'] = 'Custom na pamagat';
|
||||
|
||||
// Mga string ng pamamahala ng larawan
|
||||
$string['editcategoryimage'] = 'I-edit ang Larawan ng Kategorya';
|
||||
$string['categoryimage'] = 'Larawan ng Kategorya';
|
||||
$string['categoryimage_help'] = 'Mag-upload ng larawan para sa kategoryang ito o magbigay ng URL ng larawan';
|
||||
$string['categorycolor'] = 'Kulay ng Background';
|
||||
$string['categorycolor_help'] = 'Pumili ng kulay ng background para sa kategoryang ito';
|
||||
$string['currentpreview'] = 'Kasalukuyang Preview';
|
||||
$string['categoryupdated'] = 'Matagumpay na na-update ang kategorya';
|
||||
$string['imageurl'] = 'URL ng Larawan';
|
||||
$string['imageurl_help'] = 'Magbigay ng direktang URL sa larawan';
|
||||
$string['defaultstatus'] = 'Default na Status';
|
||||
|
||||
// Mga string ng privacy
|
||||
$string['privacy:metadata:block_catcourse_ratings'] = 'Impormasyon tungkol sa mga rating ng user para sa mga kurso';
|
||||
$string['privacy:metadata:block_catcourse_ratings:userid'] = 'Ang ID ng user na gumawa ng rating';
|
||||
$string['privacy:metadata:block_catcourse_ratings:courseid'] = 'Ang ID ng kursong na-rate';
|
||||
$string['privacy:metadata:block_catcourse_ratings:rating'] = 'Ang halaga ng rating (1-5 bituin)';
|
||||
$string['privacy:metadata:block_catcourse_ratings:comment'] = 'Opsyonal na komento kasama ng rating';
|
||||
$string['privacy:metadata:block_catcourse_ratings:timemodified'] = 'Kailan huling na-modify ang rating';
|
||||
|
||||
// Mga karagdagang string ng template
|
||||
$string['completed'] = 'Tapos na';
|
||||
$string['inprogress'] = 'Ginagawa pa';
|
||||
$string['notstarted'] = 'Hindi pa nagsimula';
|
||||
$string['enrolled'] = 'Naka-enroll';
|
||||
$string['notenrolled'] = 'Hindi naka-enroll';
|
||||
$string['totalcourses'] = 'kabuuang mga kurso';
|
||||
$string['completedcourses'] = 'Mga Tapos na Kurso';
|
||||
$string['progresspercentage'] = 'Progress';
|
||||
$string['viewcourse'] = 'Tingnan ang Kurso';
|
||||
$string['entercategory'] = 'Pumasok sa Kategorya';
|
||||
$string['backtocategories'] = 'Bumalik sa mga Kategorya';
|
||||
$string['loading'] = 'Naglo-load...';
|
||||
$string['nocoursesavailable'] = 'Walang available na kurso sa kategoryang ito';
|
||||
$string['nocategoriesavailable'] = 'Walang available na mga kategorya';
|
||||
$string['errorloadingcontent'] = 'Error sa pag-load ng nilalaman';
|
||||
$string['tryagain'] = 'Subukan Ulit';
|
||||
$string['home'] = 'Home';
|
||||
$string['breadcrumbnavigation'] = 'Breadcrumb Navigation';
|
||||
|
||||
// Mga string ng course card
|
||||
$string['coursesummary'] = 'Buod ng Kurso';
|
||||
$string['courseshortname'] = 'Maikling Pangalan ng Kurso';
|
||||
$string['coursefullname'] = 'Buong Pangalan ng Kurso';
|
||||
$string['courseimage'] = 'Larawan ng Kurso';
|
||||
$string['courserating'] = 'Rating ng Kurso';
|
||||
$string['ratethiscourse'] = 'I-rate ang kursong ito';
|
||||
$string['viewallcomments'] = 'Tingnan ang lahat ng komento';
|
||||
$string['addyourrating'] = 'Idagdag ang inyong rating';
|
||||
$string['updateyourrating'] = 'I-update ang inyong rating';
|
||||
|
||||
// Mga mensahe ng error
|
||||
$string['errornocategory'] = 'Hindi nahanap ang kategorya';
|
||||
$string['errornocourse'] = 'Hindi nahanap ang kurso';
|
||||
$string['errornopermission'] = 'Wala kayong pahintulot na tingnan ang nilalaman na ito';
|
||||
$string['errornodata'] = 'Walang available na data';
|
||||
$string['errorajax'] = 'Error sa pag-load ng data sa pamamagitan ng AJAX';
|
||||
$string['errordatabase'] = 'Naganap ang database error';
|
||||
|
||||
// Mga mensahe ng tagumpay
|
||||
$string['successsaved'] = 'Matagumpay na na-save ang mga setting';
|
||||
$string['successupdated'] = 'Matagumpay na na-update';
|
||||
$string['successdeleted'] = 'Matagumpay na natanggal';
|
||||
|
||||
// Mga string ng configuration
|
||||
$string['configtitle'] = 'Pamagat ng Block';
|
||||
$string['configtitle_help'] = 'Custom na pamagat para sa instance ng block na ito';
|
||||
$string['configshowprogress'] = 'Ipakita ang mga Progress Bar';
|
||||
$string['configshowdescription'] = 'Ipakita ang mga Paglalarawan';
|
||||
$string['configshowcoursecount'] = 'Ipakita ang Bilang ng Kurso';
|
||||
$string['configsortorder'] = 'Pagkakaayos';
|
||||
|
||||
// Mga string ng capability
|
||||
$string['category_courses:addinstance'] = 'Magdagdag ng bagong Category Courses block';
|
||||
$string['category_courses:myaddinstance'] = 'Magdagdag ng Category Courses block sa Dashboard';
|
||||
$string['category_courses:manage'] = 'Pamahalaan ang mga setting ng Category Courses';
|
||||
$string['category_courses:rate'] = 'Mag-rate ng mga kurso';
|
||||
$string['category_courses:viewratings'] = 'Tingnan ang mga rating ng kurso';
|
||||
|
||||
// Configuration ng block
|
||||
$string['blocktitle'] = 'Mga Kurso ayon sa Kategorya';
|
||||
$string['blockstring'] = 'Category Courses Block';
|
||||
$string['descconfig'] = 'Paglalarawan ng configuration section';
|
||||
$string['descfoo'] = 'Paglalarawan ng configuration';
|
||||
$string['headerconfig'] = 'Header ng configuration section';
|
||||
$string['labelfoo'] = 'Label ng configuration';
|
||||
$string['categoryhidden'] = 'Nakatagong kategorya';
|
||||
$string['showallcategories'] = 'Ipakita ang Lahat ng Kategorya';
|
||||
$string['showallcategories_desc'] = 'Payagan ang mga estudyante na makita ang lahat ng nakikitang kategorya, anuman ang status ng enrollment';
|
||||
182
category_courses/lang/id/block_category_courses.php
Normal file
182
category_courses/lang/id/block_category_courses.php
Normal file
@ -0,0 +1,182 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* String bahasa untuk block_category_courses (Bahasa Indonesia).
|
||||
*
|
||||
* @package block_category_courses
|
||||
* @copyright 2025 Your Name
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['pluginname'] = 'Kursus berdasarkan Kategori';
|
||||
$string['notauth'] = 'Anda harus masuk untuk melihat konten ini.';
|
||||
$string['errorsetrating'] = 'Kesalahan saat mengatur penilaian';
|
||||
$string['rating'] = 'Penilaian';
|
||||
$string['comment'] = 'Komentar';
|
||||
$string['addcomment'] = 'Tambah komentar';
|
||||
$string['submitrating'] = 'Kirim penilaian';
|
||||
$string['averagerating'] = 'Rata-rata: {$a}';
|
||||
$string['totalratings'] = '{$a} penilaian';
|
||||
$string['nocomments'] = 'Belum ada komentar';
|
||||
$string['ratingadded'] = 'Penilaian berhasil ditambahkan';
|
||||
$string['ratingupdated'] = 'Penilaian berhasil diperbarui';
|
||||
$string['updatingrating'] = 'Memperbarui penilaian Anda yang ada';
|
||||
$string['ratingerror'] = 'Kesalahan saat menyimpan penilaian';
|
||||
$string['invalidrating'] = 'Nilai penilaian tidak valid';
|
||||
$string['ratingrequired'] = 'Penilaian diperlukan';
|
||||
$string['commentoptional'] = 'Komentar (opsional)';
|
||||
$string['ratingsubmitted'] = 'Penilaian Anda telah dikirim';
|
||||
$string['ratingdeleted'] = 'Penilaian berhasil dihapus';
|
||||
$string['comments'] = 'Komentar';
|
||||
$string['viewcomments'] = 'Lihat komentar';
|
||||
$string['rateandcomment'] = 'Beri nilai dan komentar';
|
||||
$string['yourrating'] = 'Penilaian Anda';
|
||||
$string['noratings'] = 'Belum ada penilaian';
|
||||
|
||||
// String pengaturan
|
||||
$string['categorysettings'] = 'Pengaturan Kategori';
|
||||
$string['categorysettings_desc'] = 'Konfigurasi cara menampilkan kategori';
|
||||
$string['showprogress'] = 'Tampilkan Kemajuan';
|
||||
$string['showprogress_desc'] = 'Tampilkan bilah kemajuan untuk kategori';
|
||||
$string['showdescription'] = 'Tampilkan Deskripsi';
|
||||
$string['showdescription_desc'] = 'Tampilkan deskripsi kategori';
|
||||
$string['showcoursecount'] = 'Tampilkan Jumlah Kursus';
|
||||
$string['showcoursecount_desc'] = 'Tampilkan jumlah kursus di setiap kategori';
|
||||
$string['coursesettings'] = 'Pengaturan Kursus';
|
||||
$string['coursesettings_desc'] = 'Konfigurasi cara menampilkan kursus';
|
||||
$string['showcourseprogress'] = 'Tampilkan Kemajuan Kursus';
|
||||
$string['showcourseprogress_desc'] = 'Tampilkan bilah kemajuan untuk kursus';
|
||||
$string['showcoursedescription'] = 'Tampilkan Deskripsi Kursus';
|
||||
$string['showcoursedescription_desc'] = 'Tampilkan deskripsi kursus';
|
||||
$string['showcourseshortname'] = 'Tampilkan Nama Pendek Kursus';
|
||||
$string['showcourseshortname_desc'] = 'Tampilkan nama pendek kursus';
|
||||
$string['sortorder'] = 'Urutan Sortir';
|
||||
$string['sortorder_desc'] = 'Cara mengurutkan kategori';
|
||||
$string['sortorder_core'] = 'Default (urutan Moodle)';
|
||||
$string['sortorder_alphabetical'] = 'Alfabetis';
|
||||
$string['sortorder_coursecount'] = 'Berdasarkan jumlah kursus';
|
||||
$string['sortorder_progress'] = 'Berdasarkan kemajuan';
|
||||
$string['colorsettings'] = 'Pengaturan Warna';
|
||||
$string['colorsettings_desc'] = 'Sesuaikan warna untuk blok';
|
||||
$string['buttoncolor'] = 'Warna Tombol';
|
||||
$string['buttoncolor_help'] = 'Warna untuk tombol navigasi';
|
||||
$string['progresscolor1'] = 'Warna Kemajuan 1';
|
||||
$string['progresscolor1_help'] = 'Warna awal untuk bilah kemajuan';
|
||||
$string['progresscolor2'] = 'Warna Kemajuan 2';
|
||||
$string['progresscolor2_help'] = 'Warna akhir untuk bilah kemajuan';
|
||||
$string['manageimages'] = 'Kelola Gambar Kategori';
|
||||
|
||||
// String template
|
||||
$string['navigation'] = 'Navigasi';
|
||||
$string['viewcategory'] = 'Lihat kategori';
|
||||
$string['explore'] = 'Jelajahi';
|
||||
$string['coursescounter'] = 'kursus';
|
||||
$string['nocontent'] = 'Tidak ada konten tersedia';
|
||||
$string['enter'] = 'Masuk';
|
||||
$string['configured'] = 'Dikonfigurasi';
|
||||
$string['categories'] = 'Kategori';
|
||||
$string['customtitle'] = 'Judul kustom';
|
||||
|
||||
// String manajemen gambar
|
||||
$string['editcategoryimage'] = 'Edit Gambar Kategori';
|
||||
$string['categoryimage'] = 'Gambar Kategori';
|
||||
$string['categoryimage_help'] = 'Unggah gambar untuk kategori ini atau berikan URL gambar';
|
||||
$string['categorycolor'] = 'Warna Latar Belakang';
|
||||
$string['categorycolor_help'] = 'Pilih warna latar belakang untuk kategori ini';
|
||||
$string['currentpreview'] = 'Pratinjau Saat Ini';
|
||||
$string['categoryupdated'] = 'Kategori berhasil diperbarui';
|
||||
$string['imageurl'] = 'URL Gambar';
|
||||
$string['imageurl_help'] = 'Berikan URL langsung ke gambar';
|
||||
$string['defaultstatus'] = 'Status Default';
|
||||
|
||||
// String privasi
|
||||
$string['privacy:metadata:block_catcourse_ratings'] = 'Informasi tentang penilaian pengguna untuk kursus';
|
||||
$string['privacy:metadata:block_catcourse_ratings:userid'] = 'ID pengguna yang memberikan penilaian';
|
||||
$string['privacy:metadata:block_catcourse_ratings:courseid'] = 'ID kursus yang dinilai';
|
||||
$string['privacy:metadata:block_catcourse_ratings:rating'] = 'Nilai penilaian (1-5 bintang)';
|
||||
$string['privacy:metadata:block_catcourse_ratings:comment'] = 'Komentar opsional dengan penilaian';
|
||||
$string['privacy:metadata:block_catcourse_ratings:timemodified'] = 'Kapan penilaian terakhir dimodifikasi';
|
||||
|
||||
// String template tambahan
|
||||
$string['completed'] = 'Selesai';
|
||||
$string['inprogress'] = 'Sedang Berlangsung';
|
||||
$string['notstarted'] = 'Belum Dimulai';
|
||||
$string['enrolled'] = 'Terdaftar';
|
||||
$string['notenrolled'] = 'Tidak Terdaftar';
|
||||
$string['totalcourses'] = 'total kursus';
|
||||
$string['completedcourses'] = 'Kursus Selesai';
|
||||
$string['progresspercentage'] = 'Kemajuan';
|
||||
$string['viewcourse'] = 'Lihat Kursus';
|
||||
$string['entercategory'] = 'Masuk Kategori';
|
||||
$string['backtocategories'] = 'Kembali ke Kategori';
|
||||
$string['loading'] = 'Memuat...';
|
||||
$string['nocoursesavailable'] = 'Tidak ada kursus tersedia di kategori ini';
|
||||
$string['nocategoriesavailable'] = 'Tidak ada kategori tersedia';
|
||||
$string['errorloadingcontent'] = 'Kesalahan memuat konten';
|
||||
$string['tryagain'] = 'Coba Lagi';
|
||||
$string['home'] = 'Beranda';
|
||||
$string['breadcrumbnavigation'] = 'Navigasi Breadcrumb';
|
||||
|
||||
// String kartu kursus
|
||||
$string['coursesummary'] = 'Ringkasan Kursus';
|
||||
$string['courseshortname'] = 'Nama Pendek Kursus';
|
||||
$string['coursefullname'] = 'Nama Lengkap Kursus';
|
||||
$string['courseimage'] = 'Gambar Kursus';
|
||||
$string['courserating'] = 'Penilaian Kursus';
|
||||
$string['ratethiscourse'] = 'Nilai kursus ini';
|
||||
$string['viewallcomments'] = 'Lihat semua komentar';
|
||||
$string['addyourrating'] = 'Tambahkan penilaian Anda';
|
||||
$string['updateyourrating'] = 'Perbarui penilaian Anda';
|
||||
|
||||
// Pesan kesalahan
|
||||
$string['errornocategory'] = 'Kategori tidak ditemukan';
|
||||
$string['errornocourse'] = 'Kursus tidak ditemukan';
|
||||
$string['errornopermission'] = 'Anda tidak memiliki izin untuk melihat konten ini';
|
||||
$string['errornodata'] = 'Tidak ada data tersedia';
|
||||
$string['errorajax'] = 'Kesalahan memuat data melalui AJAX';
|
||||
$string['errordatabase'] = 'Terjadi kesalahan database';
|
||||
|
||||
// Pesan sukses
|
||||
$string['successsaved'] = 'Pengaturan berhasil disimpan';
|
||||
$string['successupdated'] = 'Berhasil diperbarui';
|
||||
$string['successdeleted'] = 'Berhasil dihapus';
|
||||
|
||||
// String konfigurasi
|
||||
$string['configtitle'] = 'Judul Blok';
|
||||
$string['configtitle_help'] = 'Judul kustom untuk instance blok ini';
|
||||
$string['configshowprogress'] = 'Tampilkan Bilah Kemajuan';
|
||||
$string['configshowdescription'] = 'Tampilkan Deskripsi';
|
||||
$string['configshowcoursecount'] = 'Tampilkan Jumlah Kursus';
|
||||
$string['configsortorder'] = 'Urutan Sortir';
|
||||
|
||||
// String kemampuan
|
||||
$string['category_courses:addinstance'] = 'Tambahkan blok Kursus Kategori baru';
|
||||
$string['category_courses:myaddinstance'] = 'Tambahkan blok Kursus Kategori ke Dashboard';
|
||||
$string['category_courses:manage'] = 'Kelola pengaturan Kursus Kategori';
|
||||
$string['category_courses:rate'] = 'Nilai kursus';
|
||||
$string['category_courses:viewratings'] = 'Lihat penilaian kursus';
|
||||
|
||||
// Konfigurasi blok
|
||||
$string['blocktitle'] = 'Kursus berdasarkan Kategori';
|
||||
$string['blockstring'] = 'Blok Kursus Kategori';
|
||||
$string['descconfig'] = 'Deskripsi bagian konfigurasi';
|
||||
$string['descfoo'] = 'Deskripsi konfigurasi';
|
||||
$string['headerconfig'] = 'Header bagian konfigurasi';
|
||||
$string['labelfoo'] = 'Label konfigurasi';
|
||||
$string['categoryhidden'] = 'Kategori tersembunyi';
|
||||
$string['showallcategories'] = 'Tampilkan Semua Kategori';
|
||||
$string['showallcategories_desc'] = 'Izinkan siswa melihat semua kategori yang terlihat, terlepas dari status pendaftaran';
|
||||
182
category_courses/lang/vi/block_category_courses.php
Normal file
182
category_courses/lang/vi/block_category_courses.php
Normal file
@ -0,0 +1,182 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Chuỗi ngôn ngữ cho block_category_courses (Tiếng Việt).
|
||||
*
|
||||
* @package block_category_courses
|
||||
* @copyright 2025 Your Name
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['pluginname'] = 'Khóa học theo Danh mục';
|
||||
$string['notauth'] = 'Bạn phải đăng nhập để xem nội dung này.';
|
||||
$string['errorsetrating'] = 'Lỗi khi đặt đánh giá';
|
||||
$string['rating'] = 'Đánh giá';
|
||||
$string['comment'] = 'Bình luận';
|
||||
$string['addcomment'] = 'Thêm bình luận';
|
||||
$string['submitrating'] = 'Gửi đánh giá';
|
||||
$string['averagerating'] = 'Trung bình: {$a}';
|
||||
$string['totalratings'] = '{$a} đánh giá';
|
||||
$string['nocomments'] = 'Chưa có bình luận';
|
||||
$string['ratingadded'] = 'Đã thêm đánh giá thành công';
|
||||
$string['ratingupdated'] = 'Đã cập nhật đánh giá thành công';
|
||||
$string['updatingrating'] = 'Đang cập nhật đánh giá hiện tại của bạn';
|
||||
$string['ratingerror'] = 'Lỗi khi lưu đánh giá';
|
||||
$string['invalidrating'] = 'Giá trị đánh giá không hợp lệ';
|
||||
$string['ratingrequired'] = 'Đánh giá là bắt buộc';
|
||||
$string['commentoptional'] = 'Bình luận (tùy chọn)';
|
||||
$string['ratingsubmitted'] = 'Đánh giá của bạn đã được gửi';
|
||||
$string['ratingdeleted'] = 'Đã xóa đánh giá thành công';
|
||||
$string['comments'] = 'Bình luận';
|
||||
$string['viewcomments'] = 'Xem bình luận';
|
||||
$string['rateandcomment'] = 'Đánh giá và bình luận';
|
||||
$string['yourrating'] = 'Đánh giá của bạn';
|
||||
$string['noratings'] = 'Chưa có đánh giá';
|
||||
|
||||
// Chuỗi cài đặt
|
||||
$string['categorysettings'] = 'Cài đặt Danh mục';
|
||||
$string['categorysettings_desc'] = 'Cấu hình cách hiển thị danh mục';
|
||||
$string['showprogress'] = 'Hiển thị Tiến độ';
|
||||
$string['showprogress_desc'] = 'Hiển thị thanh tiến độ cho danh mục';
|
||||
$string['showdescription'] = 'Hiển thị Mô tả';
|
||||
$string['showdescription_desc'] = 'Hiển thị mô tả danh mục';
|
||||
$string['showcoursecount'] = 'Hiển thị Số lượng Khóa học';
|
||||
$string['showcoursecount_desc'] = 'Hiển thị số lượng khóa học trong mỗi danh mục';
|
||||
$string['coursesettings'] = 'Cài đặt Khóa học';
|
||||
$string['coursesettings_desc'] = 'Cấu hình cách hiển thị khóa học';
|
||||
$string['showcourseprogress'] = 'Hiển thị Tiến độ Khóa học';
|
||||
$string['showcourseprogress_desc'] = 'Hiển thị thanh tiến độ cho khóa học';
|
||||
$string['showcoursedescription'] = 'Hiển thị Mô tả Khóa học';
|
||||
$string['showcoursedescription_desc'] = 'Hiển thị mô tả khóa học';
|
||||
$string['showcourseshortname'] = 'Hiển thị Tên ngắn Khóa học';
|
||||
$string['showcourseshortname_desc'] = 'Hiển thị tên ngắn khóa học';
|
||||
$string['sortorder'] = 'Thứ tự Sắp xếp';
|
||||
$string['sortorder_desc'] = 'Cách sắp xếp danh mục';
|
||||
$string['sortorder_core'] = 'Mặc định (thứ tự Moodle)';
|
||||
$string['sortorder_alphabetical'] = 'Theo bảng chữ cái';
|
||||
$string['sortorder_coursecount'] = 'Theo số lượng khóa học';
|
||||
$string['sortorder_progress'] = 'Theo tiến độ';
|
||||
$string['colorsettings'] = 'Cài đặt Màu sắc';
|
||||
$string['colorsettings_desc'] = 'Tùy chỉnh màu sắc cho khối';
|
||||
$string['buttoncolor'] = 'Màu Nút';
|
||||
$string['buttoncolor_help'] = 'Màu cho các nút điều hướng';
|
||||
$string['progresscolor1'] = 'Màu Tiến độ 1';
|
||||
$string['progresscolor1_help'] = 'Màu bắt đầu cho thanh tiến độ';
|
||||
$string['progresscolor2'] = 'Màu Tiến độ 2';
|
||||
$string['progresscolor2_help'] = 'Màu kết thúc cho thanh tiến độ';
|
||||
$string['manageimages'] = 'Quản lý Hình ảnh Danh mục';
|
||||
|
||||
// Chuỗi template
|
||||
$string['navigation'] = 'Điều hướng';
|
||||
$string['viewcategory'] = 'Xem danh mục';
|
||||
$string['explore'] = 'Khám phá';
|
||||
$string['coursescounter'] = 'khóa học';
|
||||
$string['nocontent'] = 'Không có nội dung';
|
||||
$string['enter'] = 'Vào';
|
||||
$string['configured'] = 'Đã cấu hình';
|
||||
$string['categories'] = 'Danh mục';
|
||||
$string['customtitle'] = 'Tiêu đề tùy chỉnh';
|
||||
|
||||
// Chuỗi quản lý hình ảnh
|
||||
$string['editcategoryimage'] = 'Chỉnh sửa Hình ảnh Danh mục';
|
||||
$string['categoryimage'] = 'Hình ảnh Danh mục';
|
||||
$string['categoryimage_help'] = 'Tải lên hình ảnh cho danh mục này hoặc cung cấp URL hình ảnh';
|
||||
$string['categorycolor'] = 'Màu Nền';
|
||||
$string['categorycolor_help'] = 'Chọn màu nền cho danh mục này';
|
||||
$string['currentpreview'] = 'Xem trước Hiện tại';
|
||||
$string['categoryupdated'] = 'Danh mục đã được cập nhật thành công';
|
||||
$string['imageurl'] = 'URL Hình ảnh';
|
||||
$string['imageurl_help'] = 'Cung cấp URL trực tiếp đến hình ảnh';
|
||||
$string['defaultstatus'] = 'Trạng thái Mặc định';
|
||||
|
||||
// Chuỗi bảo mật
|
||||
$string['privacy:metadata:block_catcourse_ratings'] = 'Thông tin về đánh giá của người dùng cho khóa học';
|
||||
$string['privacy:metadata:block_catcourse_ratings:userid'] = 'ID của người dùng đã đánh giá';
|
||||
$string['privacy:metadata:block_catcourse_ratings:courseid'] = 'ID của khóa học được đánh giá';
|
||||
$string['privacy:metadata:block_catcourse_ratings:rating'] = 'Giá trị đánh giá (1-5 sao)';
|
||||
$string['privacy:metadata:block_catcourse_ratings:comment'] = 'Bình luận tùy chọn với đánh giá';
|
||||
$string['privacy:metadata:block_catcourse_ratings:timemodified'] = 'Thời gian đánh giá được sửa đổi lần cuối';
|
||||
|
||||
// Chuỗi template bổ sung
|
||||
$string['completed'] = 'Hoàn thành';
|
||||
$string['inprogress'] = 'Đang tiến hành';
|
||||
$string['notstarted'] = 'Chưa bắt đầu';
|
||||
$string['enrolled'] = 'Đã đăng ký';
|
||||
$string['notenrolled'] = 'Chưa đăng ký';
|
||||
$string['totalcourses'] = 'tổng khóa học';
|
||||
$string['completedcourses'] = 'Khóa học Hoàn thành';
|
||||
$string['progresspercentage'] = 'Tiến độ';
|
||||
$string['viewcourse'] = 'Xem Khóa học';
|
||||
$string['entercategory'] = 'Vào Danh mục';
|
||||
$string['backtocategories'] = 'Quay lại Danh mục';
|
||||
$string['loading'] = 'Đang tải...';
|
||||
$string['nocoursesavailable'] = 'Không có khóa học trong danh mục này';
|
||||
$string['nocategoriesavailable'] = 'Không có danh mục';
|
||||
$string['errorloadingcontent'] = 'Lỗi khi tải nội dung';
|
||||
$string['tryagain'] = 'Thử lại';
|
||||
$string['home'] = 'Trang chủ';
|
||||
$string['breadcrumbnavigation'] = 'Điều hướng Breadcrumb';
|
||||
|
||||
// Chuỗi thẻ khóa học
|
||||
$string['coursesummary'] = 'Tóm tắt Khóa học';
|
||||
$string['courseshortname'] = 'Tên ngắn Khóa học';
|
||||
$string['coursefullname'] = 'Tên đầy đủ Khóa học';
|
||||
$string['courseimage'] = 'Hình ảnh Khóa học';
|
||||
$string['courserating'] = 'Đánh giá Khóa học';
|
||||
$string['ratethiscourse'] = 'Đánh giá khóa học này';
|
||||
$string['viewallcomments'] = 'Xem tất cả bình luận';
|
||||
$string['addyourrating'] = 'Thêm đánh giá của bạn';
|
||||
$string['updateyourrating'] = 'Cập nhật đánh giá của bạn';
|
||||
|
||||
// Thông báo lỗi
|
||||
$string['errornocategory'] = 'Không tìm thấy danh mục';
|
||||
$string['errornocourse'] = 'Không tìm thấy khóa học';
|
||||
$string['errornopermission'] = 'Bạn không có quyền xem nội dung này';
|
||||
$string['errornodata'] = 'Không có dữ liệu';
|
||||
$string['errorajax'] = 'Lỗi khi tải dữ liệu qua AJAX';
|
||||
$string['errordatabase'] = 'Đã xảy ra lỗi cơ sở dữ liệu';
|
||||
|
||||
// Thông báo thành công
|
||||
$string['successsaved'] = 'Đã lưu cài đặt thành công';
|
||||
$string['successupdated'] = 'Đã cập nhật thành công';
|
||||
$string['successdeleted'] = 'Đã xóa thành công';
|
||||
|
||||
// Chuỗi cấu hình
|
||||
$string['configtitle'] = 'Tiêu đề Khối';
|
||||
$string['configtitle_help'] = 'Tiêu đề tùy chỉnh cho phiên bản khối này';
|
||||
$string['configshowprogress'] = 'Hiển thị Thanh Tiến độ';
|
||||
$string['configshowdescription'] = 'Hiển thị Mô tả';
|
||||
$string['configshowcoursecount'] = 'Hiển thị Số lượng Khóa học';
|
||||
$string['configsortorder'] = 'Thứ tự Sắp xếp';
|
||||
|
||||
// Chuỗi khả năng
|
||||
$string['category_courses:addinstance'] = 'Thêm khối Khóa học theo Danh mục mới';
|
||||
$string['category_courses:myaddinstance'] = 'Thêm khối Khóa học theo Danh mục vào Bảng điều khiển';
|
||||
$string['category_courses:manage'] = 'Quản lý cài đặt Khóa học theo Danh mục';
|
||||
$string['category_courses:rate'] = 'Đánh giá khóa học';
|
||||
$string['category_courses:viewratings'] = 'Xem đánh giá khóa học';
|
||||
|
||||
// Cấu hình khối
|
||||
$string['blocktitle'] = 'Khóa học theo Danh mục';
|
||||
$string['blockstring'] = 'Khối Khóa học theo Danh mục';
|
||||
$string['descconfig'] = 'Mô tả phần cấu hình';
|
||||
$string['descfoo'] = 'Mô tả cấu hình';
|
||||
$string['headerconfig'] = 'Tiêu đề phần cấu hình';
|
||||
$string['labelfoo'] = 'Nhãn cấu hình';
|
||||
$string['categoryhidden'] = 'Danh mục ẩn';
|
||||
$string['showallcategories'] = 'Hiển thị Tất cả Danh mục';
|
||||
$string['showallcategories_desc'] = 'Cho phép học sinh xem tất cả danh mục có thể nhìn thấy, bất kể trạng thái đăng ký';
|
||||
40
category_courses/lib.php
Normal file
40
category_courses/lib.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Serves files from the block_category_courses file areas
|
||||
*/
|
||||
function block_category_courses_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = array()) {
|
||||
global $DB;
|
||||
|
||||
// Check context level
|
||||
if ($context->contextlevel != CONTEXT_SYSTEM) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if user is logged in
|
||||
require_login();
|
||||
|
||||
// Check filearea
|
||||
if ($filearea !== 'categoryimage') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get file details
|
||||
$itemid = (int)array_shift($args);
|
||||
$filename = array_pop($args);
|
||||
$filepath = $args ? '/'.implode('/', $args).'/' : '/';
|
||||
|
||||
// Get file from storage
|
||||
$fs = get_file_storage();
|
||||
$file = $fs->get_file($context->id, 'block_category_courses', $filearea, $itemid, $filepath, $filename);
|
||||
|
||||
if (!$file || $file->is_directory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send the file
|
||||
send_stored_file($file, 86400, 0, $forcedownload, $options);
|
||||
return true;
|
||||
}
|
||||
|
||||
156
category_courses/manage_images.php
Normal file
156
category_courses/manage_images.php
Normal file
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
require_once('../../config.php');
|
||||
require_once($CFG->libdir.'/adminlib.php');
|
||||
|
||||
require_login();
|
||||
require_capability('block/category_courses:manage', context_system::instance());
|
||||
|
||||
$categoryid = optional_param('categoryid', 0, PARAM_INT);
|
||||
$action = optional_param('action', 'list', PARAM_ALPHA);
|
||||
|
||||
$PAGE->set_url('/blocks/category_courses/manage_images.php');
|
||||
$PAGE->set_context(context_system::instance());
|
||||
$PAGE->set_title(get_string('manageimages', 'block_category_courses'));
|
||||
$PAGE->set_heading(get_string('manageimages', 'block_category_courses'));
|
||||
|
||||
admin_externalpage_setup('block_category_courses_images');
|
||||
|
||||
// Load colorpicker JavaScript for any forms on this page
|
||||
$PAGE->requires->js_call_amd('block_category_courses/colorpicker', 'init');
|
||||
|
||||
if ($action === 'edit' && $categoryid > 0) {
|
||||
// Redirect to edit form
|
||||
redirect(new moodle_url('/blocks/category_courses/edit_image.php', ['categoryid' => $categoryid]));
|
||||
}
|
||||
|
||||
echo $OUTPUT->header();
|
||||
|
||||
// Helper function to check if category is effectively hidden (inherited visibility)
|
||||
function is_category_or_parent_hidden($category) {
|
||||
// Check if this category itself is hidden
|
||||
if (!$category->visible) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If it's a root category and visible, it's not hidden
|
||||
if ($category->parent == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check parent categories recursively
|
||||
try {
|
||||
$parent = core_course_category::get($category->parent, IGNORE_MISSING);
|
||||
if ($parent) {
|
||||
// If any parent is hidden, this category is effectively hidden
|
||||
return is_category_or_parent_hidden($parent);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Parent not found, assume visible
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build accordion structure
|
||||
function render_category_accordion($parentid = 0) {
|
||||
global $DB;
|
||||
$categories = core_course_category::get_all();
|
||||
$output = '';
|
||||
|
||||
foreach ($categories as $category) {
|
||||
if ($category->parent == $parentid) {
|
||||
$customdata = $DB->get_record('block_catcourse_images', ['categoryid' => $category->id]);
|
||||
$status = (!empty($customdata->imageurl) || !empty($customdata->bgcolor)) ?
|
||||
get_string('configured', 'block_category_courses') :
|
||||
get_string('defaultstatus', 'block_category_courses');
|
||||
$statusclass = $status === get_string('configured', 'block_category_courses') ? 'badge-success' : 'badge-secondary';
|
||||
|
||||
// Check if category or any parent is hidden (recursive)
|
||||
$ishidden = is_category_or_parent_hidden($category);
|
||||
$visibilitystatus = $ishidden ? ' (Oculta)' : '';
|
||||
$visibilityclass = $ishidden ? 'text-muted' : '';
|
||||
|
||||
$editurl = new moodle_url('/blocks/category_courses/edit_image.php', ['categoryid' => $category->id]);
|
||||
|
||||
// Check if has children
|
||||
$haschildren = false;
|
||||
foreach ($categories as $child) {
|
||||
if ($child->parent == $category->id) {
|
||||
$haschildren = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$output .= html_writer::start_div('card mb-2');
|
||||
$output .= html_writer::start_div('card-header d-flex justify-content-between align-items-center');
|
||||
|
||||
if ($haschildren) {
|
||||
$output .= html_writer::start_tag('button', [
|
||||
'class' => 'btn btn-link text-left p-0 collapsed ' . $visibilityclass,
|
||||
'type' => 'button',
|
||||
'data-toggle' => 'collapse',
|
||||
'data-target' => '#collapse' . $category->id,
|
||||
'aria-expanded' => 'false'
|
||||
]);
|
||||
$output .= html_writer::tag('i', '', ['class' => 'fa fa-chevron-right mr-2']);
|
||||
$output .= format_string($category->name) . $visibilitystatus;
|
||||
$output .= html_writer::end_tag('button');
|
||||
} else {
|
||||
$output .= html_writer::tag('span', format_string($category->name) . $visibilitystatus, ['class' => $visibilityclass]);
|
||||
}
|
||||
|
||||
$output .= html_writer::start_div('d-flex align-items-center');
|
||||
$output .= html_writer::tag('span', $status, ['class' => "badge $statusclass mr-2"]);
|
||||
$output .= html_writer::link($editurl, get_string('edit'), ['class' => 'btn btn-sm btn-primary']);
|
||||
$output .= html_writer::end_div();
|
||||
|
||||
$output .= html_writer::end_div(); // card-header
|
||||
|
||||
if ($haschildren) {
|
||||
$output .= html_writer::start_div('collapse', ['id' => 'collapse' . $category->id]);
|
||||
$output .= html_writer::start_div('card-body');
|
||||
$output .= render_category_accordion($category->id);
|
||||
$output .= html_writer::end_div();
|
||||
$output .= html_writer::end_div();
|
||||
}
|
||||
|
||||
$output .= html_writer::end_div(); // card
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
echo html_writer::start_tag('div', ['class' => 'category-images-manager']);
|
||||
|
||||
echo html_writer::tag('h2', get_string('manageimages', 'block_category_courses'));
|
||||
|
||||
echo render_category_accordion();
|
||||
|
||||
// Add JavaScript for accordion functionality
|
||||
$PAGE->requires->js_init_code('
|
||||
require(["jquery"], function($) {
|
||||
$(document).ready(function() {
|
||||
$(".card-header button[data-toggle=collapse]").click(function() {
|
||||
var $button = $(this);
|
||||
var target = $button.attr("data-target");
|
||||
var $target = $(target);
|
||||
var icon = $button.find("i");
|
||||
|
||||
$target.toggle();
|
||||
|
||||
if ($target.is(":visible")) {
|
||||
$button.removeClass("collapsed");
|
||||
icon.removeClass("fa-chevron-right").addClass("fa-chevron-down");
|
||||
} else {
|
||||
$button.addClass("collapsed");
|
||||
icon.removeClass("fa-chevron-down").addClass("fa-chevron-right");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
');
|
||||
|
||||
echo html_writer::end_tag('div');
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
97
category_courses/settings.php
Normal file
97
category_courses/settings.php
Normal file
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
if ($ADMIN->fulltree) {
|
||||
// Category display settings
|
||||
$settings->add(new admin_setting_heading('block_category_courses/categoryheading',
|
||||
get_string('categorysettings', 'block_category_courses'),
|
||||
get_string('categorysettings_desc', 'block_category_courses')));
|
||||
|
||||
$settings->add(new admin_setting_configcheckbox('block_category_courses/showprogress',
|
||||
get_string('showprogress', 'block_category_courses'),
|
||||
get_string('showprogress_desc', 'block_category_courses'),
|
||||
1));
|
||||
|
||||
$settings->add(new admin_setting_configcheckbox('block_category_courses/showdescription',
|
||||
get_string('showdescription', 'block_category_courses'),
|
||||
get_string('showdescription_desc', 'block_category_courses'),
|
||||
1));
|
||||
|
||||
$settings->add(new admin_setting_configcheckbox('block_category_courses/showcoursecount',
|
||||
get_string('showcoursecount', 'block_category_courses'),
|
||||
get_string('showcoursecount_desc', 'block_category_courses'),
|
||||
1));
|
||||
|
||||
$settings->add(new admin_setting_configcheckbox('block_category_courses/showallcategories',
|
||||
get_string('showallcategories', 'block_category_courses'),
|
||||
get_string('showallcategories_desc', 'block_category_courses'),
|
||||
0));
|
||||
|
||||
// Course display settings
|
||||
$settings->add(new admin_setting_heading('block_category_courses/courseheading',
|
||||
get_string('coursesettings', 'block_category_courses'),
|
||||
get_string('coursesettings_desc', 'block_category_courses')));
|
||||
|
||||
$settings->add(new admin_setting_configcheckbox('block_category_courses/showcourseprogress',
|
||||
get_string('showcourseprogress', 'block_category_courses'),
|
||||
get_string('showcourseprogress_desc', 'block_category_courses'),
|
||||
1));
|
||||
|
||||
$settings->add(new admin_setting_configcheckbox('block_category_courses/showcoursedescription',
|
||||
get_string('showcoursedescription', 'block_category_courses'),
|
||||
get_string('showcoursedescription_desc', 'block_category_courses'),
|
||||
1));
|
||||
|
||||
$settings->add(new admin_setting_configcheckbox('block_category_courses/showcourseshortname',
|
||||
get_string('showcourseshortname', 'block_category_courses'),
|
||||
get_string('showcourseshortname_desc', 'block_category_courses'),
|
||||
1));
|
||||
|
||||
// Sort order setting
|
||||
$settings->add(new admin_setting_configselect('block_category_courses/sortorder',
|
||||
get_string('sortorder', 'block_category_courses'),
|
||||
get_string('sortorder_desc', 'block_category_courses'),
|
||||
'core',
|
||||
[
|
||||
'core' => get_string('sortorder_core', 'block_category_courses'),
|
||||
'alphabetical' => get_string('sortorder_alphabetical', 'block_category_courses'),
|
||||
'coursecount' => get_string('sortorder_coursecount', 'block_category_courses'),
|
||||
'progress' => get_string('sortorder_progress', 'block_category_courses')
|
||||
]));
|
||||
|
||||
// Color settings
|
||||
$settings->add(new admin_setting_heading('block_category_courses/colorheading',
|
||||
get_string('colorsettings', 'block_category_courses'),
|
||||
get_string('colorsettings_desc', 'block_category_courses')));
|
||||
|
||||
$settings->add(new admin_setting_configtext('block_category_courses/buttoncolor',
|
||||
get_string('buttoncolor', 'block_category_courses'),
|
||||
get_string('buttoncolor_help', 'block_category_courses'),
|
||||
'',
|
||||
PARAM_TEXT));
|
||||
|
||||
$settings->add(new admin_setting_configtext('block_category_courses/progresscolor1',
|
||||
get_string('progresscolor1', 'block_category_courses'),
|
||||
get_string('progresscolor1_help', 'block_category_courses'),
|
||||
'#4285f4',
|
||||
PARAM_TEXT));
|
||||
|
||||
$settings->add(new admin_setting_configtext('block_category_courses/progresscolor2',
|
||||
get_string('progresscolor2', 'block_category_courses'),
|
||||
get_string('progresscolor2_help', 'block_category_courses'),
|
||||
'#34a853',
|
||||
PARAM_TEXT));
|
||||
|
||||
// Include color picker JavaScript
|
||||
global $PAGE;
|
||||
if ($PAGE) {
|
||||
$PAGE->requires->js_call_amd('block_category_courses/colorpicker', 'init');
|
||||
}
|
||||
}
|
||||
|
||||
// Category image management - separate external page
|
||||
if ($hassiteconfig) {
|
||||
$ADMIN->add('blocksettings', new admin_externalpage('block_category_courses_images',
|
||||
get_string('manageimages', 'block_category_courses'),
|
||||
new moodle_url('/blocks/category_courses/manage_images.php')));
|
||||
}
|
||||
983
category_courses/styles.css
Normal file
983
category_courses/styles.css
Normal file
@ -0,0 +1,983 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
/* Mejoras de contraste para progress elements */
|
||||
.category-card .progress-info {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.category-card .progress-label {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.category-card .progress-bar {
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.category-card .course-info .course-count {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Contraste automático para fondos oscuros */
|
||||
.category-card[data-dark-bg="true"] .progress-label,
|
||||
.category-card[data-dark-bg="true"] .course-count {
|
||||
color: #ffffff !important;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.category-card[data-dark-bg="true"] .progress-bar {
|
||||
background-color: rgba(255, 255, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
/* Contraste automático para fondos claros */
|
||||
.category-card[data-dark-bg="false"] .progress-label,
|
||||
.category-card[data-dark-bg="false"] .course-count {
|
||||
color: #333333 !important;
|
||||
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.category-card[data-dark-bg="false"] .progress-bar {
|
||||
background-color: rgba(0, 0, 0, 0.15) !important;
|
||||
}
|
||||
|
||||
/* Mejoras de contraste para custom progress container */
|
||||
.custom-progress-container {
|
||||
padding: 0.5rem;
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.custom-progress-container span,
|
||||
.custom-progress-container i {
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.card.block_category_courses {
|
||||
border: none !important;
|
||||
border-color: transparent;
|
||||
margin: 0;
|
||||
padding: 4em 0 2em 0;
|
||||
}
|
||||
|
||||
.card.block_category_courses .card-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.category-cards-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 1.5rem;
|
||||
padding: 1rem 0;
|
||||
width: 100%;
|
||||
margin-top: 1.5em;
|
||||
justify-items: stretch;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.category-card {
|
||||
position: relative;
|
||||
background: var(--white);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--gray-300);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.category-card:hover,
|
||||
.category-card:focus {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.category-card:focus-visible {
|
||||
outline: 2px solid var(--primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.category-card .category-title,
|
||||
.category-card .category-description,
|
||||
.category-card .progress-percentage {
|
||||
color: inherit;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.category-card .course-count-chip {
|
||||
background: rgba(0, 0, 0, 0);
|
||||
color: inherit;
|
||||
backdrop-filter: blur(10px);
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.card-image {
|
||||
height: 168px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.category-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
aspect-ratio: 16/9;
|
||||
}
|
||||
|
||||
.image-initials {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: var(--white);
|
||||
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 1.25rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-footer-button {
|
||||
padding: 0.75rem 1.25rem 1.25rem;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.card-footer-button .btn {
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.card-footer-button .btn i {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.category-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: var(--dark);
|
||||
margin: 0 0 0.75rem 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.category-description {
|
||||
font-size: 0.875rem;
|
||||
color: var(--gray-600);
|
||||
margin: 0 0 1rem 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.course-count-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background: var(--gray-200);
|
||||
color: var(--gray-800);
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.progress,
|
||||
.rui-progress {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.card-link {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.category-card.card-clicked {
|
||||
transform: scale(0.98);
|
||||
transition: transform 0.1s ease;
|
||||
}
|
||||
|
||||
.category-card.loading .card-content>* {
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: loading 1.5s infinite;
|
||||
border-radius: 4px;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.category-card.loading .card-image {
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: loading 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.category-cards-container {
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 350px));
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 320px) {
|
||||
.category-cards-container {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.card-image {
|
||||
height: 168px;
|
||||
}
|
||||
}
|
||||
|
||||
#page-blocks-category_courses-view_courses .category-courses-grid,
|
||||
.category-courses-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 1.5rem;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.hierarchy-navigation {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.navigation-content {
|
||||
transition: opacity 0.15s ease;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.navigation-content.loading {
|
||||
pointer-events: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.navigation-content.loading::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin: -16px 0 0 -16px;
|
||||
border: 3px solid var(--gray-300);
|
||||
border-top: 3px solid var(--primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.hierarchy-navigation.navigating .category-card {
|
||||
pointer-events: none;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.hierarchy-navigation .breadcrumb-nav {
|
||||
box-shadow: 0 8px 14px -2px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
padding: 0.75rem 1.25rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
border-radius: 10px;
|
||||
background-color: var(--gray-100);
|
||||
}
|
||||
|
||||
.hierarchy-navigation .breadcrumb {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.hierarchy-navigation .breadcrumb-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.hierarchy-navigation .breadcrumb-link {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--primary);
|
||||
text-decoration: none;
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.hierarchy-navigation .breadcrumb-link:hover,
|
||||
.hierarchy-navigation .breadcrumb-link:focus {
|
||||
color: var(--primary-color-700);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.hierarchy-navigation .breadcrumb-separator {
|
||||
color: var(--gray-600);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.current-page {
|
||||
color: var(--gray-700);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.level-content {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.level-categories+.level-courses {
|
||||
border-top: 2px solid var(--gray-300);
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.no-content {
|
||||
text-align: center;
|
||||
padding: 3rem 1rem;
|
||||
color: var(--gray-600);
|
||||
}
|
||||
|
||||
.no-content p {
|
||||
margin: 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.course-card {
|
||||
background: var(--white);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid var(--gray-300);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.course-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
body.theme-dark .course-card {
|
||||
background: #2d3748 !important;
|
||||
border-color: #4a5568 !important;
|
||||
color: #e2e8f0 !important;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important;
|
||||
}
|
||||
|
||||
body.theme-dark .course-card:hover {
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4) !important;
|
||||
}
|
||||
|
||||
body.theme-dark .course-title {
|
||||
color: #f7fafc !important;
|
||||
}
|
||||
|
||||
body.theme-dark .course-shortname,
|
||||
body.theme-dark .course-summary {
|
||||
color: #a0aec0 !important;
|
||||
}
|
||||
|
||||
body.theme-dark .progress-text {
|
||||
color: #cbd5e0 !important;
|
||||
}
|
||||
|
||||
.course-card:hover .course-rating-section {
|
||||
background: var(--gray-100);
|
||||
}
|
||||
|
||||
body.theme-dark .course-card:hover .course-rating-section {
|
||||
background: #4a5568 !important;
|
||||
}
|
||||
|
||||
.course-link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.course-image {
|
||||
height: 168px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.course-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* Mejoras de contraste para progress elements */
|
||||
.category-card .progress-info {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.category-card .progress-label {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.category-card .progress-bar {
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.category-card .course-info .course-count {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Contraste automático para fondos oscuros */
|
||||
.category-card[data-dark-bg="true"] .progress-label,
|
||||
.category-card[data-dark-bg="true"] .course-count {
|
||||
color: #ffffff !important;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.category-card[data-dark-bg="true"] .progress-bar {
|
||||
background-color: rgba(255, 255, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
/* Contraste automático para fondos claros */
|
||||
.category-card[data-dark-bg="false"] .progress-label,
|
||||
.category-card[data-dark-bg="false"] .course-count {
|
||||
color: #333333 !important;
|
||||
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.category-card[data-dark-bg="false"] .progress-bar {
|
||||
background-color: rgba(0, 0, 0, 0.15) !important;
|
||||
}
|
||||
|
||||
/* Mejoras de contraste para custom progress container */
|
||||
.custom-progress-container {
|
||||
padding: 0.5rem;
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.custom-progress-container span,
|
||||
.custom-progress-container i {
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.enrollment-badge {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
background: var(--success);
|
||||
color: var(--white);
|
||||
border-radius: 50%;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.course-content {
|
||||
padding: 1.25rem;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.course-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: var(--dark);
|
||||
margin: 0 0 0.5rem 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.course-shortname {
|
||||
font-size: 0.875rem;
|
||||
color: var(--gray-600);
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
|
||||
.course-summary {
|
||||
font-size: 0.875rem;
|
||||
color: var(--gray-700);
|
||||
margin-bottom: 1rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.progress-section {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: var(--gray-300);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: var(--primary);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 0.75rem;
|
||||
color: var(--gray-600);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.hidden-course-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
background: var(--danger);
|
||||
color: var(--white);
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.hidden-course-badge i {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.visibility-overlay {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
left: 0.5rem;
|
||||
background: color-mix(in srgb, var(--danger) 90%, transparent);
|
||||
color: var(--white);
|
||||
border-radius: 50%;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.category-card.navigating {
|
||||
transform: scale(0.98);
|
||||
opacity: 0.8;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.hierarchy-navigation .breadcrumb-nav {
|
||||
padding: 0.5rem 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.hierarchy-navigation .breadcrumb {
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.hierarchy-navigation .breadcrumb-separator {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
.color-picker-wrapper {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
input[type='color'].color-picker-input {
|
||||
width: 40px;
|
||||
height: 30px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
background: none;
|
||||
}
|
||||
|
||||
input[type='color'].color-picker-input::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
input[type='color'].color-picker-input::-webkit-color-swatch {
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
input[type='color'].color-picker-input::-moz-color-swatch {
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.fgroup .color-picker-wrapper {
|
||||
margin-left: 0;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.fgroup .felement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.fgroup .felement input[name='categorycolor'],
|
||||
.fgroup .felement input[name*='buttoncolor'] {
|
||||
width: 100px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Block configuration form specific styles */
|
||||
.block-config-form .fitem .felement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.block-config-form input[name*='buttoncolor']+.color-picker-wrapper {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Ensure color picker appears in block edit form */
|
||||
#page-blocks-edit .fitem .felement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
#page-blocks-edit input[name*='buttoncolor'] {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.category-navigate-btn {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/* Premium badge styles */
|
||||
.course-badges {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: .5rem;
|
||||
margin: .75rem 0;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.badge-premium {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
color: inherit;
|
||||
font-weight: 500;
|
||||
padding: 8px 14px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.85rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.badge-premium.badge-total {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.badge-premium.badge-progress {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.badge-premium:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.badge-premium i {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Rating System Styles */
|
||||
.course-rating-section {
|
||||
padding: 0.75rem 1.25rem;
|
||||
border-top: 1px solid var(--gray-200);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: var(--gray-50);
|
||||
}
|
||||
|
||||
.rating-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.rating-stars {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.rating-star {
|
||||
color: #ddd;
|
||||
transition: color 0.2s ease;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.rating-star:hover,
|
||||
.rating-star.hover {
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
.rating-star.selected {
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
.rating-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--gray-600);
|
||||
}
|
||||
|
||||
.average-rating {
|
||||
font-weight: 600;
|
||||
color: var(--dark);
|
||||
}
|
||||
|
||||
.total-ratings {
|
||||
color: var(--gray-500);
|
||||
}
|
||||
|
||||
.comments-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--gray-600);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.comments-btn:hover {
|
||||
background: var(--gray-200);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.comments-count {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Prevent rating section from being clickable as course link */
|
||||
.course-rating-section {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.course-link {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Modal rating styles */
|
||||
.rating-stars-form {
|
||||
font-size: 1.5rem;
|
||||
color: #ddd;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.rating-star-form {
|
||||
transition: color 0.2s;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.rating-star-form:hover,
|
||||
.rating-star-form.selected {
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
.rating-display {
|
||||
color: #ffc107;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.comment-item {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.comments-modal-content {
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Force dark mode styles for Moodle dark theme */
|
||||
.theme-boost-union-dark .comment-item,
|
||||
[data-theme="dark"] .comment-item,
|
||||
body.theme-dark .comment-item {
|
||||
background-color: #2d3748 !important;
|
||||
border-color: #4a5568 !important;
|
||||
color: #e2e8f0 !important;
|
||||
}
|
||||
|
||||
.theme-boost-union-dark .comment-item strong,
|
||||
[data-theme="dark"] .comment-item strong,
|
||||
body.theme-dark .comment-item strong {
|
||||
color: #f7fafc !important;
|
||||
}
|
||||
|
||||
.theme-boost-union-dark .comment-item .text-muted,
|
||||
[data-theme="dark"] .comment-item .text-muted,
|
||||
body.theme-dark .comment-item .text-muted {
|
||||
color: #a0aec0 !important;
|
||||
}
|
||||
|
||||
.theme-boost-union-dark .comments-modal-content,
|
||||
[data-theme="dark"] .comments-modal-content,
|
||||
body.theme-dark .comments-modal-content {
|
||||
background-color: transparent !important;
|
||||
color: #e2e8f0 !important;
|
||||
}
|
||||
|
||||
.theme-boost-union-dark .comments-modal-content h6,
|
||||
[data-theme="dark"] .comments-modal-content h6,
|
||||
body.theme-dark .comments-modal-content h6 {
|
||||
color: #f7fafc !important;
|
||||
}
|
||||
|
||||
.theme-boost-union-dark .comments-modal-content .form-label,
|
||||
[data-theme="dark"] .comments-modal-content .form-label,
|
||||
body.theme-dark .comments-modal-content .form-label {
|
||||
color: #e2e8f0 !important;
|
||||
}
|
||||
|
||||
.theme-boost-union-dark .comments-modal-content .form-control,
|
||||
[data-theme="dark"] .comments-modal-content .form-control,
|
||||
body.theme-dark .comments-modal-content .form-control {
|
||||
background-color: #2d3748 !important;
|
||||
border-color: #4a5568 !important;
|
||||
color: #e2e8f0 !important;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.theme-boost-union-dark .comments-modal-content .form-control::placeholder,
|
||||
[data-theme="dark"] .comments-modal-content .form-control::placeholder,
|
||||
body.theme-dark .comments-modal-content .form-control::placeholder {
|
||||
color: #a0aec0 !important;
|
||||
}
|
||||
|
||||
.theme-boost-union-dark .comments-modal-content .form-text,
|
||||
[data-theme="dark"] .comments-modal-content .form-text,
|
||||
body.theme-dark .comments-modal-content .form-text {
|
||||
color: #a0aec0 !important;
|
||||
}
|
||||
|
||||
/* Hidden category styles */
|
||||
.category-card.category-hidden {
|
||||
filter: grayscale(100%);
|
||||
opacity: 0.6;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.category-card.category-hidden::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
44
category_courses/templates/breadcrumbs.mustache
Normal file
44
category_courses/templates/breadcrumbs.mustache
Normal file
@ -0,0 +1,44 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template block_category_courses/breadcrumbs
|
||||
|
||||
Breadcrumbs para navegación jerárquica.
|
||||
|
||||
Context variables required for this template:
|
||||
* name - Nombre del breadcrumb
|
||||
* url - URL del breadcrumb
|
||||
* categoryid - ID de la categoría
|
||||
* active - Si es el breadcrumb activo
|
||||
}}
|
||||
|
||||
<nav class="breadcrumb-nav" aria-label="{{#str}}navigation, block_category_courses{{/str}}">
|
||||
<ol class="breadcrumb">
|
||||
{{#breadcrumbs}}
|
||||
<li class="breadcrumb-item {{#active}}active{{/active}}">
|
||||
{{#active}}
|
||||
<span class="current-page">{{name}}</span>
|
||||
{{/active}}
|
||||
{{^active}}
|
||||
<button type="button" class="breadcrumb-link" data-categoryid="{{categoryid}}" data-action="navigate-breadcrumb">
|
||||
{{name}}
|
||||
</button>
|
||||
{{/active}}
|
||||
</li>
|
||||
{{/breadcrumbs}}
|
||||
</ol>
|
||||
</nav>
|
||||
76
category_courses/templates/category_card.mustache
Normal file
76
category_courses/templates/category_card.mustache
Normal file
@ -0,0 +1,76 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template block_category_courses/category_card
|
||||
|
||||
Category card template with original beautiful design.
|
||||
|
||||
Context variables required for this template:
|
||||
* id - Category ID
|
||||
* name - Category name
|
||||
* description - Category description
|
||||
* image - Category image data
|
||||
* color - Background color
|
||||
* textcolor - Text color
|
||||
* total_courses - Total courses
|
||||
* completed_courses - Completed courses
|
||||
* progress_percentage - Progress percentage
|
||||
* hasprogress - Has progress data
|
||||
}}
|
||||
|
||||
<div class="category-card" data-categoryid="{{id}}" style="background: {{color}}; color: {{textcolor}};">
|
||||
<div class="category-header">
|
||||
<div class="category-image">
|
||||
{{#image.type}}
|
||||
<div class="category-initials" style="background-color: rgba(255,255,255,0.2);">
|
||||
{{image.text}}
|
||||
</div>
|
||||
{{/image.type}}
|
||||
{{^image.type}}
|
||||
<img src="{{image}}" alt="{{name}}" class="category-img">
|
||||
{{/image.type}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="category-body">
|
||||
<h3 class="category-title">{{name}}</h3>
|
||||
|
||||
{{#description}}
|
||||
<p class="category-description">{{description}}</p>
|
||||
{{/description}}
|
||||
|
||||
<div class="course-info">
|
||||
<span class="course-count">{{completed_courses}} / {{total_courses}} cursos</span>
|
||||
</div>
|
||||
|
||||
{{#hasprogress}}
|
||||
<div class="progress-info">
|
||||
<span class="progress-label">{{progress_percentage}}% completado</span>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: {{progress_percentage}}%; background: linear-gradient(90deg, {{#config.progresscolor1}}{{config.progresscolor1}}{{/config.progresscolor1}}{{^config.progresscolor1}}#4285f4{{/config.progresscolor1}}, {{#config.progresscolor2}}{{config.progresscolor2}}{{/config.progresscolor2}}{{^config.progresscolor2}}#34a853{{/config.progresscolor2}});"></div>
|
||||
</div>
|
||||
</div>
|
||||
{{/hasprogress}}
|
||||
</div>
|
||||
|
||||
<div class="category-footer">
|
||||
<button class="btn-ingresar" type="button" style="{{#config.buttoncolor}}background-color: {{config.buttoncolor}}; border-color: {{config.buttoncolor}};{{/config.buttoncolor}}{{^config.buttoncolor}}background-color: var(--primary); border-color: var(--primary);{{/config.buttoncolor}} color: white;">
|
||||
<i class="fa fa-folder-open" aria-hidden="true"></i>
|
||||
{{#str}}enter, block_category_courses{{/str}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
106
category_courses/templates/category_card_hierarchical.mustache
Normal file
106
category_courses/templates/category_card_hierarchical.mustache
Normal file
@ -0,0 +1,106 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template block_category_courses/category_card_hierarchical
|
||||
|
||||
Tarjeta de categoría para navegación jerárquica - mantiene diseño actual.
|
||||
|
||||
Context variables required for this template:
|
||||
* id - Category ID
|
||||
* name - Category name
|
||||
* description - Category description
|
||||
* image - Category image data
|
||||
* color - Background color
|
||||
* textcolor - Text color
|
||||
* total_courses - Total courses
|
||||
* completed_courses - Completed courses
|
||||
* progress_percentage - Progress percentage
|
||||
* hasprogress - Has progress data
|
||||
* config - Display configuration
|
||||
}}
|
||||
|
||||
<div class="category-card{{#ishidden}} category-hidden{{/ishidden}}"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="{{#str}}viewcategory, block_category_courses{{/str}}: {{name}}"
|
||||
data-category-id="{{id}}"
|
||||
data-action="navigate-category"
|
||||
style="background: {{color}}; color: {{textcolor}};">
|
||||
|
||||
<div class="card-image">
|
||||
{{#image.type}}
|
||||
{{#image.text}}
|
||||
<div class="image-initials" aria-hidden="true">{{image.text}}</div>
|
||||
{{/image.text}}
|
||||
{{/image.type}}
|
||||
{{#image.url}}
|
||||
<img src="{{image.url}}" alt="{{name}}" class="category-image" />
|
||||
{{/image.url}}
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<h3 class="category-title">
|
||||
{{name}}
|
||||
{{#ishidden}}
|
||||
<span class="badge badge-secondary ml-2" title="{{#str}}categoryhidden, block_category_courses{{/str}}">
|
||||
<i class="fa fa-eye-slash" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{#str}}categoryhidden, block_category_courses{{/str}}</span>
|
||||
</span>
|
||||
{{/ishidden}}
|
||||
</h3>
|
||||
|
||||
{{#config.showdescription}}
|
||||
{{#description}}
|
||||
<p class="category-description">{{description}}</p>
|
||||
{{/description}}
|
||||
{{/config.showdescription}}
|
||||
|
||||
{{#config.showcoursecount}}
|
||||
<div class="course-badges">
|
||||
{{#is_admin}}
|
||||
<span class="badge-premium badge-total">
|
||||
<i class="fa fa-graduation-cap" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{admin_total_courses}} {{#str}}totalcourses, block_category_courses{{/str}}</span>
|
||||
<span aria-hidden="true">{{admin_total_courses}} {{#str}}totalcourses, block_category_courses{{/str}}</span>
|
||||
</span>
|
||||
{{/is_admin}}
|
||||
{{#hasprogress}}
|
||||
<span class="badge-premium badge-progress">
|
||||
<i class="fa fa-check-circle" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{#str}}coursescounter, block_category_courses, {"completed": {{completed_courses}}, "total": {{total_courses}}}{{/str}}</span>
|
||||
<span aria-hidden="true">{{completed_courses}} / {{total_courses}} {{#str}}completed, block_category_courses{{/str}}</span>
|
||||
</span>
|
||||
{{/hasprogress}}
|
||||
</div>
|
||||
{{/config.showcoursecount}}
|
||||
|
||||
{{#config.showprogress}}
|
||||
{{#hasprogress}}
|
||||
<div class="progress-container">
|
||||
{{> block_category_courses/progress_bar}}
|
||||
</div>
|
||||
{{/hasprogress}}
|
||||
{{/config.showprogress}}
|
||||
</div>
|
||||
|
||||
<div class="card-footer-button">
|
||||
<button type="button" class="btn btn-sm category-navigate-btn" data-category-id="{{id}}" style="{{#config.buttoncolor}}background-color: {{config.buttoncolor}}; border-color: {{config.buttoncolor}};{{/config.buttoncolor}}{{^config.buttoncolor}}background-color: var(--primary); border-color: var(--primary);{{/config.buttoncolor}} color: white;">
|
||||
<i class="fa fa-folder-open" aria-hidden="true"></i> {{#str}}explore, block_category_courses{{/str}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
85
category_courses/templates/comments_modal.mustache
Normal file
85
category_courses/templates/comments_modal.mustache
Normal file
@ -0,0 +1,85 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template block_category_courses/comments_modal
|
||||
|
||||
Comments modal template.
|
||||
|
||||
Context variables required for this template:
|
||||
* courseid - Course ID
|
||||
* comments - Array of comments
|
||||
}}
|
||||
|
||||
<div class="comments-modal-content">
|
||||
<form>
|
||||
<input type="hidden" name="courseid" value="{{courseid}}">
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{#str}}rating, block_category_courses{{/str}}</label>
|
||||
<div class="rating-stars-form">
|
||||
<span class="rating-star-form" data-rating="1">★</span>
|
||||
<span class="rating-star-form" data-rating="2">★</span>
|
||||
<span class="rating-star-form" data-rating="3">★</span>
|
||||
<span class="rating-star-form" data-rating="4">★</span>
|
||||
<span class="rating-star-form" data-rating="5">★</span>
|
||||
</div>
|
||||
<input type="hidden" name="rating" value="0">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="comment" class="form-label">{{#str}}comment, block_category_courses{{/str}}</label>
|
||||
<textarea class="form-control" name="comment" rows="3" placeholder="{{#str}}addcomment, block_category_courses{{/str}}"></textarea>
|
||||
{{#userRating}}
|
||||
<small class="form-text text-muted">{{#str}}updatingrating, block_category_courses{{/str}}</small>
|
||||
{{/userRating}}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="comments-list">
|
||||
<h6>{{#str}}comments, block_category_courses{{/str}}</h6>
|
||||
|
||||
{{#comments}}
|
||||
<div class="comment-item mb-3 p-3 border rounded">
|
||||
<div class="d-flex align-items-start">
|
||||
<div class="me-3">
|
||||
{{{user_picture}}}
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<strong>{{user_fullname}}</strong>
|
||||
<div class="rating-display" data-rating="{{rating}}">
|
||||
<span class="star-filled">★</span>
|
||||
<span class="star-filled">★</span>
|
||||
<span class="star-filled">★</span>
|
||||
<span class="star-filled">★</span>
|
||||
<span class="star-filled">★</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mb-1">{{comment}}</p>
|
||||
<small class="text-muted">{{#userdate}}{{timemodified}}, {{#str}}strftimedatetimeshort, langconfig{{/str}}{{/userdate}}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/comments}}
|
||||
|
||||
{{^comments}}
|
||||
<p class="text-muted text-center py-3">{{#str}}nocomments, block_category_courses{{/str}}</p>
|
||||
{{/comments}}
|
||||
</div>
|
||||
</div>
|
||||
101
category_courses/templates/course_card.mustache
Normal file
101
category_courses/templates/course_card.mustache
Normal file
@ -0,0 +1,101 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template block_category_courses/course_card
|
||||
|
||||
Tarjeta de curso con progreso y enlace directo.
|
||||
|
||||
Context variables required for this template:
|
||||
* id - ID del curso
|
||||
* fullname - Nombre completo del curso
|
||||
* shortname - Nombre corto del curso
|
||||
* summary - Resumen del curso
|
||||
* viewurl - URL para ver el curso
|
||||
* courseimage - Imagen del curso
|
||||
* visible - Si el curso es visible
|
||||
* isenrolled - Si el usuario está inscrito
|
||||
* hasprogress - Si tiene progreso
|
||||
* progress - Porcentaje de progreso
|
||||
}}
|
||||
|
||||
<div class="course-card {{^visible}}course-hidden{{/visible}}" data-courseid="{{id}}">
|
||||
<a href="{{viewurl}}" class="course-link text-decoration-none" aria-label="{{#str}}course{{/str}}: {{fullname}}">
|
||||
<div class="card-inner">
|
||||
<div class="course-image">
|
||||
<img src="{{courseimage}}" alt="{{fullname}}" class="course-img" loading="lazy">
|
||||
{{#isenrolled}}
|
||||
<div class="enrollment-badge">
|
||||
<i class="fa fa-check-circle" aria-hidden="true"></i>
|
||||
</div>
|
||||
{{/isenrolled}}
|
||||
{{^visible}}
|
||||
<div class="visibility-overlay">
|
||||
<i class="fa fa-eye-slash" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{#str}}hiddenfromstudents, moodle{{/str}}</span>
|
||||
</div>
|
||||
{{/visible}}
|
||||
</div>
|
||||
<div class="course-content">
|
||||
<h4 class="course-title">{{fullname}}</h4>
|
||||
{{#config.showcourseshortname}}
|
||||
<p class="course-shortname">{{shortname}}</p>
|
||||
{{/config.showcourseshortname}}
|
||||
{{^visible}}
|
||||
<div class="hidden-course-badge">
|
||||
<i class="fa fa-eye-slash" aria-hidden="true"></i>
|
||||
{{#str}}hiddenfromstudents, moodle{{/str}}
|
||||
</div>
|
||||
{{/visible}}
|
||||
{{#config.showcoursedescription}}
|
||||
{{#summary}}
|
||||
<div class="course-summary">{{summary}}</div>
|
||||
{{/summary}}
|
||||
{{/config.showcoursedescription}}
|
||||
</div>
|
||||
</div>
|
||||
{{#config.showcourseprogress}}
|
||||
{{$progress}}
|
||||
{{#hasprogress}}
|
||||
<div class="card-footer w-100">
|
||||
{{> block_category_courses/progress_bar}}
|
||||
</div>
|
||||
{{/hasprogress}}
|
||||
{{/progress}}
|
||||
{{/config.showcourseprogress}}
|
||||
</a>
|
||||
|
||||
<!-- Rating System -->
|
||||
<div class="course-rating-section">
|
||||
<div class="rating-container">
|
||||
<div class="rating-stars" data-courseid="{{id}}" data-user-rating="{{user_rating}}">
|
||||
<span class="rating-star" data-rating="1">★</span>
|
||||
<span class="rating-star" data-rating="2">★</span>
|
||||
<span class="rating-star" data-rating="3">★</span>
|
||||
<span class="rating-star" data-rating="4">★</span>
|
||||
<span class="rating-star" data-rating="5">★</span>
|
||||
</div>
|
||||
<div class="rating-info">
|
||||
<span class="average-rating">{{average_rating}}</span>
|
||||
<span class="total-ratings">({{total_ratings}})</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="comments-btn" type="button" aria-label="{{#str}}comments, block_category_courses{{/str}}">
|
||||
<i class="fa fa-comment" aria-hidden="true"></i>
|
||||
<span class="comments-count">{{total_comments}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
44
category_courses/templates/main.mustache
Normal file
44
category_courses/templates/main.mustache
Normal file
@ -0,0 +1,44 @@
|
||||
{{! Navegación jerárquica de categorías }}
|
||||
<div class="hierarchy-navigation" data-current-category="{{currentcategoryid}}">
|
||||
|
||||
{{! Breadcrumbs para navegación }}
|
||||
{{#breadcrumbs}}
|
||||
{{> block_category_courses/breadcrumbs}}
|
||||
{{/breadcrumbs}}
|
||||
|
||||
{{! Contenedor principal con niveles }}
|
||||
<div class="navigation-content">
|
||||
|
||||
{{! Nivel actual - Categorías }}
|
||||
{{#hascategories}}
|
||||
<div class="level-content level-categories" data-level="{{currentcategoryid}}">
|
||||
<div class="category-cards-container">
|
||||
{{#categories}}
|
||||
{{> block_category_courses/category_card_hierarchical}}
|
||||
{{/categories}}
|
||||
</div>
|
||||
</div>
|
||||
{{/hascategories}}
|
||||
|
||||
{{! Nivel actual - Cursos }}
|
||||
{{#hascourses}}
|
||||
<div class="level-content level-courses" data-level="{{currentcategoryid}}">
|
||||
<div class="category-courses-grid">
|
||||
{{#courses}}
|
||||
{{> block_category_courses/course_card}}
|
||||
{{/courses}}
|
||||
</div>
|
||||
</div>
|
||||
{{/hascourses}}
|
||||
|
||||
{{! Mensaje cuando no hay contenido }}
|
||||
{{^hascategories}}
|
||||
{{^hascourses}}
|
||||
<div class="no-content">
|
||||
<p>{{#str}}nocontent, block_category_courses{{/str}}</p>
|
||||
</div>
|
||||
{{/hascourses}}
|
||||
{{/hascategories}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
25
category_courses/templates/progress_bar.mustache
Normal file
25
category_courses/templates/progress_bar.mustache
Normal file
@ -0,0 +1,25 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
@template block_category_courses/progress_bar
|
||||
|
||||
Custom progress bar with configurable gradient colors.
|
||||
|
||||
Context variables required for this template:
|
||||
* progress - Progress percentage (0-100)
|
||||
* config - Configuration object with progresscolor1 and progresscolor2
|
||||
}}
|
||||
|
||||
<div class="custom-progress-container" style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
|
||||
<i class="fa fa-check-circle" style="color: {{#config.progresscolor2}}{{config.progresscolor2}}{{/config.progresscolor2}}{{^config.progresscolor2}}#34a853{{/config.progresscolor2}}; font-size: 16px;"></i>
|
||||
<span>{{progress}}% {{#str}}completed, block_category_courses{{/str}}</span>
|
||||
</div>
|
||||
<div class="progress" style="height: 8px; background-color: rgba(0,0,0,0.1); border-radius: 4px; overflow: hidden;">
|
||||
<div class="progress-bar"
|
||||
role="progressbar"
|
||||
style="width: {{progress}}%; background: linear-gradient(90deg, {{#config.progresscolor1}}{{config.progresscolor1}}{{/config.progresscolor1}}{{^config.progresscolor1}}#4285f4{{/config.progresscolor1}}, {{#config.progresscolor2}}{{config.progresscolor2}}{{/config.progresscolor2}}{{^config.progresscolor2}}#34a853{{/config.progresscolor2}}); transition: width 0.3s ease;"
|
||||
aria-valuenow="{{progress}}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100">
|
||||
</div>
|
||||
</div>
|
||||
78
category_courses/templates/view_courses.mustache
Normal file
78
category_courses/templates/view_courses.mustache
Normal file
@ -0,0 +1,78 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template block_myoverview/view-cards
|
||||
|
||||
This template renders the cards view for the myoverview block.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"courses": [
|
||||
{
|
||||
"name": "Assignment due 1",
|
||||
"viewurl": "https://moodlesite/course/view.php?id=2",
|
||||
"courseimage": "https://moodlesite/pluginfile/123/course/overviewfiles/123.jpg",
|
||||
"fullname": "Course 3 for \"Statistical and Computational Tools\" ",
|
||||
"hasprogress": true,
|
||||
"progress": 10,
|
||||
"coursecategory": "Category 1",
|
||||
"visible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
|
||||
{{#hascourses}}
|
||||
{{< core_course/coursecards }}
|
||||
{{$classes}}category-courses-grid{{/classes}}
|
||||
|
||||
{{$progress}}
|
||||
{{#hasprogress}}
|
||||
<div class="card-footer dashboard-card-footer border-0 w-100">
|
||||
{{> block_myoverview/progress-bar}}
|
||||
</div>
|
||||
{{/hasprogress}}
|
||||
{{/progress}}
|
||||
{{$coursename}}
|
||||
<span class="multiline" title="{{fullname}}">
|
||||
<span class="sr-only">{{{fullname}}}</span>
|
||||
<span aria-hidden="true">
|
||||
{{#shortentext}}55, {{{fullname}}} {{/shortentext}}
|
||||
</span>
|
||||
</span>
|
||||
{{/coursename}}
|
||||
{{$coursecategory}}
|
||||
{{#showcoursecategory}}
|
||||
<span class="sr-only">
|
||||
{{#str}}aria:coursecategory, core_course{{/str}}
|
||||
</span>
|
||||
<span class="categoryname text-truncate">
|
||||
{{{coursecategory}}}
|
||||
</span>
|
||||
{{/showcoursecategory}}
|
||||
{{/coursecategory}}
|
||||
{{$divider}}
|
||||
{{#showcoursecategory}}
|
||||
<div class="pl-1 pr-1">|</div>
|
||||
{{/showcoursecategory}}
|
||||
{{/divider}}
|
||||
{{/ core_course/coursecards }}
|
||||
{{/hascourses}}
|
||||
|
||||
{{^hascourses}}
|
||||
<div class="alert alert-info">{{message}}</div>
|
||||
{{/hascourses}}
|
||||
31
category_courses/version.php
Normal file
31
category_courses/version.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Version information for block_category_courses.
|
||||
*
|
||||
* @package block_category_courses
|
||||
* @copyright 2025 Your Name
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->component = 'block_category_courses';
|
||||
$plugin->version = 2025073217;
|
||||
$plugin->requires = 2024100700; // Moodle 4.5+
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
$plugin->release = '4.5.3';
|
||||
124
category_courses/view_courses.php
Normal file
124
category_courses/view_courses.php
Normal file
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
require_once('../../config.php');
|
||||
require_login();
|
||||
|
||||
$categoryid = required_param('categoryid', PARAM_INT);
|
||||
|
||||
$category = core_course_category::get($categoryid, IGNORE_MISSING);
|
||||
if (!$category) {
|
||||
throw new moodle_exception('invalidcategoryid');
|
||||
}
|
||||
|
||||
$context = context_coursecat::instance($categoryid);
|
||||
$PAGE->set_context($context);
|
||||
$PAGE->set_url(new moodle_url('/blocks/category_courses/view_courses.php', ['categoryid' => $categoryid]));
|
||||
$PAGE->set_pagelayout('standard');
|
||||
|
||||
$catname = format_string($category->get_formatted_name());
|
||||
$PAGE->set_title($catname . ' - ' . get_string('mycourses', 'moodle'));
|
||||
$PAGE->set_heading($catname);
|
||||
|
||||
echo $OUTPUT->header();
|
||||
|
||||
// Preparar datos para el template
|
||||
$templatedata = [];
|
||||
|
||||
// Obtener cursos inscritos del usuario para verificar progreso
|
||||
$fields = 'id, fullname, shortname, summary, category, startdate, enddate, visible';
|
||||
$mycourses = enrol_get_my_courses($fields, 'sortorder');
|
||||
$enrolledcourses = [];
|
||||
foreach ($mycourses as $course) {
|
||||
$enrolledcourses[$course->id] = $course;
|
||||
}
|
||||
|
||||
// Si es administrador, obtener TODOS los cursos de la categoría
|
||||
if (is_siteadmin()) {
|
||||
$courses = $category->get_courses(['recursive' => false, 'coursecontacts' => false]);
|
||||
$filtered = [];
|
||||
foreach ($courses as $course) {
|
||||
// Verificar si está inscrito para mostrar progreso
|
||||
$isenrolled = isset($enrolledcourses[$course->id]);
|
||||
$hasprogress = false;
|
||||
$progress = 0;
|
||||
|
||||
if ($isenrolled && completion_info::is_enabled_for_site()) {
|
||||
$completion = new completion_info($course);
|
||||
if ($completion->is_enabled()) {
|
||||
$percentage = \core_completion\progress::get_course_progress_percentage($course, $USER->id);
|
||||
if (!is_null($percentage)) {
|
||||
$hasprogress = true;
|
||||
$progress = floor($percentage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$courseimage = \core_course\external\course_summary_exporter::get_course_image($course);
|
||||
if (!$courseimage) {
|
||||
$courseimage = $OUTPUT->get_generated_image_for_id($course->id);
|
||||
}
|
||||
|
||||
$filtered[] = [
|
||||
'id' => $course->id,
|
||||
'fullname' => format_string($course->fullname),
|
||||
'shortname' => format_string($course->shortname),
|
||||
'summary' => format_text($course->summary, FORMAT_HTML),
|
||||
'viewurl' => (new moodle_url('/course/view.php', ['id' => $course->id]))->out(false),
|
||||
'courseimage' => $courseimage,
|
||||
'visible' => $course->visible,
|
||||
'coursecategory' => format_string($category->name),
|
||||
'showcoursecategory' => true,
|
||||
'hasprogress' => $hasprogress,
|
||||
'progress' => $progress
|
||||
];
|
||||
}
|
||||
$templatedata['message'] = get_string('nocoursesincat', 'block_category_courses');
|
||||
} else {
|
||||
// Usuario normal: solo cursos donde está inscrito con progreso
|
||||
$filtered = [];
|
||||
foreach ($mycourses as $course) {
|
||||
if ((int)$course->category === (int)$categoryid) {
|
||||
// Calcular progreso
|
||||
$hasprogress = false;
|
||||
$progress = 0;
|
||||
|
||||
if (completion_info::is_enabled_for_site()) {
|
||||
$completion = new completion_info($course);
|
||||
if ($completion->is_enabled()) {
|
||||
$percentage = \core_completion\progress::get_course_progress_percentage($course, $USER->id);
|
||||
if (!is_null($percentage)) {
|
||||
$hasprogress = true;
|
||||
$progress = floor($percentage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$courseimage = \core_course\external\course_summary_exporter::get_course_image($course);
|
||||
if (!$courseimage) {
|
||||
$courseimage = $OUTPUT->get_generated_image_for_id($course->id);
|
||||
}
|
||||
|
||||
$filtered[] = [
|
||||
'id' => $course->id,
|
||||
'fullname' => format_string($course->fullname),
|
||||
'shortname' => format_string($course->shortname),
|
||||
'summary' => format_text($course->summary, FORMAT_HTML),
|
||||
'viewurl' => (new moodle_url('/course/view.php', ['id' => $course->id]))->out(false),
|
||||
'courseimage' => $courseimage,
|
||||
'visible' => $course->visible,
|
||||
'coursecategory' => format_string($category->name),
|
||||
'showcoursecategory' => true,
|
||||
'hasprogress' => $hasprogress,
|
||||
'progress' => $progress
|
||||
];
|
||||
}
|
||||
}
|
||||
$templatedata['message'] = get_string('notenrolledincat', 'block_category_courses');
|
||||
}
|
||||
|
||||
$templatedata['courses'] = $filtered;
|
||||
$templatedata['hascourses'] = !empty($filtered);
|
||||
|
||||
// Renderizar template
|
||||
echo $OUTPUT->render_from_template('block_category_courses/view_courses', $templatedata);
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
Loading…
x
Reference in New Issue
Block a user