Skip to content

Commit 75ecde6

Browse files
committed
Feature: Add first version of CSS snippets feature, credits go to all members of the MoodleMootDACH dev camp team 22.
1 parent 3ac8485 commit 75ecde6

21 files changed

+1099
-3
lines changed

CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Changes
66

77
### Unreleased
88

9+
* 2024-09-04 - Feature: Add first version of CSS snippets feature, credits go to all members of the MoodleMootDACH dev camp team 22.
910
* 2024-08-24 - Upgrade: Update Bootstrap classes for Moodle 4.4.
1011
* 2024-08-11 - Updated Moodle Plugin CI to latest upstream recommendations
1112
* 2024-07-24 - Test: Fix broken Behat scenario 'Suppress 'Chat to course participants' link', resolves #696

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,10 @@ By default, on the course management page, Moodle requires you to either open th
613613

614614
Boost Union's flavours offer a possibility to override particular Moodle look & feel settings in particular contexts. On this page, you can create and manage flavours.
615615

616+
### Settings page "CSS Snippets"
617+
618+
Boost Union's CSS snipppets offer a possibility to add small (or slightly larger) amounts of CSS to the Moodle site. This can be particularly handy for fixing small visual glitches in Moodle core or for adding eye candy to your Moodle site.
619+
616620
### Settings page "Smart menus"
617621

618622
Smart menus allow site administrators to create customizable menus that can be placed in different locations on the site, such as the site main menu, bottom mobile menu, and user menu. The menus can be configured to display different types of content, including links to other pages or resources, category links, or user profile links. On this page, you can create and manage smart menus.

classes/snippets.php

+298
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
<?php
2+
// This file is part of Moodle - http://moodle.org/
3+
//
4+
// Moodle is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// Moodle is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16+
17+
namespace theme_boost_union;
18+
19+
use moodle_recordset;
20+
21+
/**
22+
* Class snippets
23+
*
24+
* @package theme_boost_union
25+
* @copyright 2024 University of Graz
26+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27+
*/
28+
class snippets {
29+
/**
30+
* Constant for how many a Kilobyte are in bytes.
31+
*
32+
* @var int
33+
*/
34+
const KB_IN_BYTES = 1024;
35+
36+
/**
37+
* List of all available Snippet Meta File headers.
38+
*
39+
* @var array
40+
*/
41+
const SNIPPET_HEADERS = [
42+
'Snippet Title',
43+
'Goal',
44+
'Description',
45+
'Scope',
46+
];
47+
48+
/**
49+
* Base path CSS snippets that are shipped with boost_union.
50+
* @var string
51+
*/
52+
const BUILTIN_SNIPPETS_BASE_PATH = '/theme/boost_union/snippets/builtin/';
53+
54+
/**
55+
* Gets the snippet file based on the meta information.
56+
*
57+
* @param mixed $path
58+
* @param mixed $source
59+
*
60+
* @return string|null
61+
*/
62+
public static function get_snippet_file($path, $source) {
63+
global $CFG;
64+
// Get the snippet file based on the different sources.
65+
if ('theme_boost_union' === $source) {
66+
// Builtin CSS SNippets.
67+
$file = $CFG->dirroot . self::BUILTIN_SNIPPETS_BASE_PATH . $path;
68+
} else {
69+
// Other snippet sources.
70+
return null;
71+
}
72+
return is_readable($file) ? $file : null;
73+
}
74+
75+
/**
76+
* Loads the Snippets SCSS content.
77+
*
78+
* @param mixed $path
79+
* @param mixed $source
80+
*
81+
* @return boolean|string
82+
*/
83+
public static function get_snippet_scss($path, $source) {
84+
// Get the snippets file, based on the source.
85+
$file = self::get_snippet_file($path, $source);
86+
87+
if (is_null($file)) {
88+
return '';
89+
}
90+
91+
$scss = file_get_contents( $file, false, null, 0);
92+
return $scss;
93+
}
94+
95+
/**
96+
* Get a snippet defined in the code based on path.
97+
*
98+
* @param string $path
99+
* @param string $source
100+
*
101+
* @return mixed
102+
*/
103+
public static function get_snippet_meta($path, $source) {
104+
global $CFG;
105+
106+
// Get the snippets file, based on the source.
107+
$file = self::get_snippet_file($path, $source);
108+
109+
// If the file does not exist or is not readable, we can not proceed.
110+
if (is_null($file)) {
111+
return null;
112+
}
113+
114+
// Extract the meta from the SCSS files top level multiline comment in WordPress style.
115+
$headers = self::get_snippet_meta_from_file($file);
116+
117+
// The title is the only required meta-key that actually must be set.
118+
if (!array_key_exists('Snippet Title', $headers)) {
119+
return null;
120+
}
121+
122+
// Create an object containing the information.
123+
$snippet = new \stdClass();
124+
$snippet->title = $headers['Snippet Title'];
125+
$snippet->description = $headers['Description'];
126+
$snippet->scope = $headers['Scope'];
127+
$snippet->goal = $headers['Goal'];
128+
$snippet->source = 'theme_boost_union';
129+
130+
return $snippet;
131+
}
132+
133+
/**
134+
* Combine snippets meta data from the snippets file with the database record.
135+
*
136+
* This is currently used for create the view for the settings table.
137+
*
138+
* @param moodle_recordset $snippetrecordset
139+
*
140+
* @return array
141+
*/
142+
public static function compose_snippets_data($snippetrecordset) {
143+
$snippets = [];
144+
145+
foreach ($snippetrecordset as $snippetrecord) {
146+
// Get the meta information from the SCSS files top multiline comment.
147+
$snippet = self::get_snippet_meta($snippetrecord->path, $snippetrecord->source);
148+
// If snippets meta is not found, it will no be added to the returned snippet array.
149+
if ($snippet) {
150+
// Merge the two objects.
151+
$snippets[] = (object) array_merge((array) $snippetrecord, (array) $snippet);
152+
}
153+
}
154+
155+
return $snippets;
156+
}
157+
158+
/**
159+
* Checks which snippets are active and returns their css.
160+
*
161+
* @return string
162+
*/
163+
public static function get_enabled_snippet_scss() {
164+
global $DB;
165+
166+
// Compose SQL base query.
167+
$sql = "SELECT *
168+
FROM {theme_boost_union_snippets} s
169+
WHERE enabled = '1'
170+
ORDER BY sortorder";
171+
172+
// Get records.
173+
$data = $DB->get_recordset_sql($sql);
174+
175+
$scss = '';
176+
177+
foreach ($data as $snippet) {
178+
$scss .= self::get_snippet_scss($snippet->path, $snippet->source);
179+
}
180+
181+
$data->close();
182+
183+
return $scss;
184+
}
185+
186+
/**
187+
* Strips close comment and close php tags from file headers.
188+
*
189+
* @param string $str Header comment to clean up.
190+
*
191+
* @return string
192+
*/
193+
private static function cleanup_header_comment($str) {
194+
return trim(preg_replace('/\s*(?:\*\/|\?>).*/', '', $str));
195+
}
196+
197+
/**
198+
* Retrieves metadata from a file.
199+
*
200+
* Searches for metadata in the first 8 KB of a file, such as a plugin or theme.
201+
* Each piece of metadata must be on its own line. Fields can not span multiple
202+
* lines, the value will get cut at the end of the first line.
203+
*
204+
* If the file data is not within that first 8 KB, then the author should correct
205+
* the snippet.
206+
*
207+
* @param string $file Absolute path to the file.
208+
*
209+
* @copyright forked from https://developer.wordpress.org/reference/functions/get_file_data/
210+
* @return string[] Array of file header values keyed by header name.
211+
*/
212+
public static function get_snippet_meta_from_file($file) {
213+
// Pull only the first 8 KB of the file in.
214+
$filedata = file_get_contents( $file, false, null, 0, 8 * self::KB_IN_BYTES );
215+
216+
if ( false === $filedata ) {
217+
$filedata = '';
218+
}
219+
220+
// Make sure we catch CR-only line endings.
221+
$filedata = str_replace( "\r", "\n", $filedata );
222+
223+
$headers = [];
224+
225+
foreach (self::SNIPPET_HEADERS as $regex) {
226+
if (preg_match('/^(?:[ \t]*)?[ \t\/*#@]*' . preg_quote($regex, '/') . ':(.*)$/mi', $filedata, $match)
227+
&& $match[1]) {
228+
$headers[$regex] = self::cleanup_header_comment($match[1]);
229+
} else {
230+
$headers[$regex] = '';
231+
}
232+
}
233+
234+
return $headers;
235+
}
236+
237+
/**
238+
* Retrieve all builtin CSS snippets via the actual scss files.
239+
*
240+
* @return string[]
241+
*/
242+
private static function get_builtin_snippet_paths() {
243+
global $CFG;
244+
// Get an array of all .scss files in the directory.
245+
$files = glob($CFG->dirroot . self::BUILTIN_SNIPPETS_BASE_PATH . '*.scss');
246+
247+
// Get the basenames.
248+
$basenames = array_map(fn($file) => basename($file), $files);
249+
250+
return $basenames;
251+
}
252+
253+
/**
254+
* Make sure builtin snippets are in the database.
255+
*
256+
* @return void
257+
*/
258+
public static function add_builtin_snippets() {
259+
global $DB;
260+
261+
// Get builtin snippets that are present on disk.
262+
$paths = self::get_builtin_snippet_paths();
263+
264+
// Get builtin snippets which are known in the database.
265+
$snippets = $DB->get_records(
266+
'theme_boost_union_snippets',
267+
['source' => 'theme_boost_union'],
268+
'sortorder DESC',
269+
'id,path,sortorder',
270+
0,
271+
0,
272+
);
273+
274+
// Get the highest sortorder present.
275+
$sortorder = empty($snippets) ? 0 : intval(reset($snippets)->sortorder) + 1;
276+
277+
// Prepare an array with all the present builtin snippet paths.
278+
$presentpaths = array_map(function($snippet) {
279+
return $snippet->path;
280+
}, $snippets);
281+
282+
foreach ($paths as $path) {
283+
if (!in_array($path, $presentpaths)) {
284+
$DB->insert_record(
285+
'theme_boost_union_snippets',
286+
[
287+
'path' => $path,
288+
'source' => 'theme_boost_union',
289+
'sortorder' => $sortorder,
290+
]
291+
);
292+
// We add each record with incrementing sortorder.
293+
$sortorder += 1;
294+
}
295+
}
296+
}
297+
298+
}

0 commit comments

Comments
 (0)