Skip to content

Commit 21c26b3

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 21c26b3

18 files changed

+1094
-1
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 snippets 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

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

0 commit comments

Comments
 (0)