| @@ -3,8 +3,9 @@ | ||
| 3 | 3 | namespace ABlocks\import; |
| 4 | 4 | |
| 5 | 5 | use ABlocks\Admin\Settings\Base; |
| 6 | 6 | use ABlocks\Helper; |
| 7 | +use ABlocks\Classes\GlobalClasses; | |
| 7 | 8 | use ABlocks\import\XmlParsers\WXR_Parser; |
| 8 | 9 | use WP_Error; |
| 9 | 10 | |
| 10 | 11 | if ( ! defined( 'ABSPATH' ) ) { |
| @@ -1253,10 +1254,98 @@ | ||
| 1253 | 1254 | update_option( 'ablocks_frontend_dashboard_sub_pages', wp_json_encode( $settings['frontend_dashboard_sub_pages'] ) ); |
| 1254 | 1255 | unset( $settings['frontend_dashboard_sub_pages'] ); |
| 1255 | 1256 | } |
| 1256 | 1257 | |
| 1258 | + if ( isset( $settings['global_classes'] ) ) { | |
| 1259 | + $this->import_global_classes( $settings['global_classes'] ); | |
| 1260 | + unset( $settings['global_classes'] ); | |
| 1261 | + } | |
| 1262 | + | |
| 1257 | 1263 | Base::save_settings( $settings ); |
| 1258 | 1264 | } |
| 1265 | + } | |
| 1266 | + | |
| 1267 | + /** | |
| 1268 | + * Sanitise one imported class record. | |
| 1269 | + * | |
| 1270 | + * The file is untrusted input, so a record gets the same treatment | |
| 1271 | + * GlobalClasses::upsert() gives one saved from the editor: the id through | |
| 1272 | + * `sanitize_html_class` so it cannot escape the selector it is written | |
| 1273 | + * into, and the legacy raw `css` declaration string stripped of braces and | |
| 1274 | + * angle brackets so it cannot close its own rule and open another. | |
| 1275 | + * | |
| 1276 | + * @param array $class One record from the imported library. | |
| 1277 | + * @return array The sanitised record. | |
| 1278 | + */ | |
| 1279 | + private function sanitize_global_class( $class ) { | |
| 1280 | + $clean = [ | |
| 1281 | + 'id' => sanitize_html_class( $class['id'] ), | |
| 1282 | + 'label' => isset( $class['label'] ) ? sanitize_text_field( $class['label'] ) : '', | |
| 1283 | + ]; | |
| 1284 | + | |
| 1285 | + if ( isset( $class['styles'] ) && is_array( $class['styles'] ) ) { | |
| 1286 | + // Structured buckets are compiled through the styles schema, which | |
| 1287 | + // only emits properties it knows, so they need no extra stripping. | |
| 1288 | + $clean['styles'] = $class['styles']; | |
| 1289 | + } elseif ( isset( $class['css'] ) ) { | |
| 1290 | + $clean['css'] = trim( preg_replace( '/[{}<>]/', '', (string) $class['css'] ) ); | |
| 1291 | + } | |
| 1292 | + | |
| 1293 | + return $clean; | |
| 1294 | + } | |
| 1295 | + | |
| 1296 | + /** | |
| 1297 | + * Merge the imported global-class library into this site's. | |
| 1298 | + * | |
| 1299 | + * A class already on this site wins: overwriting it would silently restyle | |
| 1300 | + * blocks that are already published here, which is a worse surprise than an | |
| 1301 | + * imported block keeping the local definition of a class it names. Skipped | |
| 1302 | + * ids are reported so the collision is visible rather than guessed at. | |
| 1303 | + * | |
| 1304 | + * @param array $incoming Class records from the imported file. | |
| 1305 | + */ | |
| 1306 | + private function import_global_classes( $incoming ) { | |
| 1307 | + if ( ! is_array( $incoming ) || empty( $incoming ) ) { | |
| 1308 | + return; | |
| 1309 | + } | |
| 1310 | + | |
| 1311 | + $existing = GlobalClasses::get_all(); | |
| 1312 | + $known = []; | |
| 1313 | + foreach ( $existing as $class ) { | |
| 1314 | + if ( ! empty( $class['id'] ) ) { | |
| 1315 | + $known[ $class['id'] ] = true; | |
| 1316 | + } | |
| 1317 | + } | |
| 1318 | + | |
| 1319 | + $added = []; | |
| 1320 | + $skipped = []; | |
| 1321 | + foreach ( $incoming as $class ) { | |
| 1322 | + if ( empty( $class['id'] ) || '' === sanitize_html_class( $class['id'] ) ) { | |
| 1323 | + continue; | |
| 1324 | + } | |
| 1325 | + if ( isset( $known[ $class['id'] ] ) ) { | |
| 1326 | + $skipped[] = $class['id']; | |
| 1327 | + continue; | |
| 1328 | + } | |
| 1329 | + $existing[] = $this->sanitize_global_class( $class ); | |
| 1330 | + $added[] = $class['id']; | |
| 1331 | + } | |
| 1332 | + | |
| 1333 | + if ( ! empty( $added ) ) { | |
| 1334 | + update_option( 'ablocks_global_classes', wp_json_encode( array_values( $existing ) ), false ); | |
| 1335 | + GlobalClasses::bump_revision(); | |
| 1336 | + } | |
| 1337 | + | |
| 1338 | + Helper::emit_sse_message( [ | |
| 1339 | + 'action' => 'log', | |
| 1340 | + 'level' => 'info', | |
| 1341 | + 'message' => sprintf( | |
| 1342 | + /* translators: 1: number of imported global classes, 2: number skipped because the id already existed. */ | |
| 1343 | + __( 'Global classes: %1$d imported, %2$d skipped (id already in use).', 'ablocks' ), | |
| 1344 | + count( $added ), | |
| 1345 | + count( $skipped ) | |
| 1346 | + ), | |
| 1347 | + ] ); | |
| 1259 | 1348 | } |
| 1260 | 1349 | |
| 1261 | 1350 | /** |
| 1262 | 1351 | * Parse a WXR file |