load_settings(); } /** * Load settings from database. */ private function load_settings() { $this->settings = get_option( 'maple_icons_settings', array( 'active_set' => '', 'downloaded_sets' => array(), ) ); } /** * Save settings to database. * * @return bool True on success. */ private function save_settings() { return update_option( 'maple_icons_settings', $this->settings ); } /** * Get all downloaded sets. * * @return array Array of downloaded set data. */ public function get_downloaded_sets() { return isset( $this->settings['downloaded_sets'] ) ? $this->settings['downloaded_sets'] : array(); } /** * Get the active set slug. * * @return string|null Active set slug or null if none. */ public function get_active_set() { $active = isset( $this->settings['active_set'] ) ? $this->settings['active_set'] : ''; return ! empty( $active ) ? $active : null; } /** * Set the active icon set. * * @param string $slug Icon set slug. * @return bool|WP_Error True on success, error on failure. */ public function set_active( $slug ) { // Allow empty string to deactivate. if ( empty( $slug ) ) { $this->settings['active_set'] = ''; return $this->save_settings(); } // Validate slug. if ( ! MI_Icon_Sets::is_valid_slug( $slug ) ) { return new WP_Error( 'invalid_set', __( 'Invalid icon set.', 'maple-icons' ) ); } // Check if downloaded. if ( ! $this->is_downloaded( $slug ) ) { return new WP_Error( 'not_downloaded', __( 'Icon set is not downloaded.', 'maple-icons' ) ); } $this->settings['active_set'] = $slug; return $this->save_settings(); } /** * Check if a set is downloaded. * * @param string $slug Icon set slug. * @return bool True if downloaded. */ public function is_downloaded( $slug ) { $downloaded = $this->get_downloaded_sets(); return isset( $downloaded[ $slug ] ); } /** * Mark a set as downloaded. * * @param string $slug Icon set slug. * @param int $icon_count Number of icons downloaded. * @return bool True on success. */ public function mark_downloaded( $slug, $icon_count ) { if ( ! MI_Icon_Sets::is_valid_slug( $slug ) ) { return false; } $set_config = MI_Icon_Sets::get( $slug ); $this->settings['downloaded_sets'][ $slug ] = array( 'version' => $set_config['version'], 'downloaded_at' => current_time( 'mysql' ), 'icon_count' => $icon_count, ); return $this->save_settings(); } /** * Remove a set from downloaded list. * * @param string $slug Icon set slug. * @return bool True on success. */ public function unmark_downloaded( $slug ) { if ( isset( $this->settings['downloaded_sets'][ $slug ] ) ) { unset( $this->settings['downloaded_sets'][ $slug ] ); } // If this was the active set, clear it. if ( $this->settings['active_set'] === $slug ) { $this->settings['active_set'] = ''; } return $this->save_settings(); } /** * Get all icons for a set and style. * * @param string $slug Icon set slug. * @param string $style Style slug. * @return array Array of icon data. */ public function get_icons_for_set( $slug, $style ) { if ( ! MI_Icon_Sets::is_valid_slug( $slug ) ) { return array(); } if ( ! MI_Icon_Sets::is_valid_style( $slug, $style ) ) { return array(); } // Load manifest. $manifest = MI_Icon_Sets::load_manifest( $slug ); if ( is_wp_error( $manifest ) ) { return array(); } $icons = isset( $manifest['icons'] ) ? $manifest['icons'] : array(); $result = array(); foreach ( $icons as $icon ) { $icon_styles = isset( $icon['styles'] ) ? $icon['styles'] : array_keys( MI_Icon_Sets::get( $slug )['styles'] ); // Only include if this icon has the requested style. if ( in_array( $style, $icon_styles, true ) ) { $result[] = array( 'name' => $icon['name'], 'tags' => isset( $icon['tags'] ) ? $icon['tags'] : array(), 'category' => isset( $icon['category'] ) ? $icon['category'] : '', ); } } return $result; } /** * Search icons in the active set. * * @param string $query Search query. * @param string $style Optional style filter. * @param int $limit Maximum results. * @param int $offset Offset for pagination. * @return array Search results. */ public function search_icons( $query = '', $style = '', $limit = MI_SEARCH_LIMIT, $offset = 0 ) { $active_set = $this->get_active_set(); if ( ! $active_set ) { return array( 'icons' => array(), 'total' => 0, ); } $set_config = MI_Icon_Sets::get( $active_set ); if ( ! $set_config ) { return array( 'icons' => array(), 'total' => 0, ); } // Default to first style if not specified. if ( empty( $style ) ) { $style = $set_config['default_style']; } // Validate style. if ( ! MI_Icon_Sets::is_valid_style( $active_set, $style ) ) { $style = $set_config['default_style']; } // Load manifest. $manifest = MI_Icon_Sets::load_manifest( $active_set ); if ( is_wp_error( $manifest ) ) { return array( 'icons' => array(), 'total' => 0, ); } $icons = isset( $manifest['icons'] ) ? $manifest['icons'] : array(); $query = strtolower( trim( $query ) ); $results = array(); foreach ( $icons as $icon ) { $icon_styles = isset( $icon['styles'] ) ? $icon['styles'] : array_keys( $set_config['styles'] ); // Skip if this icon doesn't have the requested style. if ( ! in_array( $style, $icon_styles, true ) ) { continue; } // If no query, include all. if ( empty( $query ) ) { $results[] = $icon; continue; } // Search in name. if ( strpos( strtolower( $icon['name'] ), $query ) !== false ) { $results[] = $icon; continue; } // Search in tags. if ( isset( $icon['tags'] ) && is_array( $icon['tags'] ) ) { foreach ( $icon['tags'] as $tag ) { if ( strpos( strtolower( $tag ), $query ) !== false ) { $results[] = $icon; break; } } } } $total = count( $results ); // Apply offset and limit. $results = array_slice( $results, $offset, $limit ); // Format results. $formatted = array(); foreach ( $results as $icon ) { $formatted[] = array( 'name' => $icon['name'], 'tags' => isset( $icon['tags'] ) ? $icon['tags'] : array(), 'category' => isset( $icon['category'] ) ? $icon['category'] : '', 'set' => $active_set, 'style' => $style, ); } return array( 'icons' => $formatted, 'total' => $total, 'set' => $active_set, 'style' => $style, 'styles' => MI_Icon_Sets::get_style_labels( $active_set ), ); } /** * Get SVG content for a specific icon. * * @param string $slug Icon set slug. * @param string $style Style slug. * @param string $name Icon name. * @return string|WP_Error SVG content or error. */ public function get_icon_svg( $slug, $style, $name ) { // Validate inputs. if ( ! MI_Icon_Sets::is_valid_slug( $slug ) ) { return new WP_Error( 'invalid_set', __( 'Invalid icon set.', 'maple-icons' ) ); } if ( ! MI_Icon_Sets::is_valid_style( $slug, $style ) ) { return new WP_Error( 'invalid_style', __( 'Invalid icon style.', 'maple-icons' ) ); } // Validate icon name. if ( ! preg_match( '/^[a-z0-9\-_]+$/i', $name ) ) { return new WP_Error( 'invalid_name', __( 'Invalid icon name.', 'maple-icons' ) ); } $local_path = MI_Icon_Sets::get_local_path( $slug, $style, $name ); // Validate path is within icons directory (prevent path traversal). $real_path = realpath( $local_path ); $allowed_base = realpath( MI_ICONS_DIR ); if ( false === $real_path ) { return new WP_Error( 'not_found', __( 'Icon not found.', 'maple-icons' ) ); } if ( false === $allowed_base || strpos( $real_path, $allowed_base ) !== 0 ) { return new WP_Error( 'invalid_path', __( 'Invalid icon path.', 'maple-icons' ) ); } if ( ! file_exists( $real_path ) ) { return new WP_Error( 'not_found', __( 'Icon not found.', 'maple-icons' ) ); } $svg = file_get_contents( $real_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents if ( false === $svg ) { return new WP_Error( 'read_error', __( 'Could not read icon file.', 'maple-icons' ) ); } return $svg; } /** * Delete a downloaded set. * * @param string $slug Icon set slug. * @return bool|WP_Error True on success, error on failure. */ public function delete_set( $slug ) { if ( ! MI_Icon_Sets::is_valid_slug( $slug ) ) { return new WP_Error( 'invalid_set', __( 'Invalid icon set.', 'maple-icons' ) ); } if ( ! $this->is_downloaded( $slug ) ) { return new WP_Error( 'not_downloaded', __( 'Icon set is not downloaded.', 'maple-icons' ) ); } // Delete files. $downloader = new MI_Downloader(); $deleted = $downloader->delete_set( $slug ); if ( ! $deleted ) { return new WP_Error( 'delete_error', __( 'Could not delete icon files.', 'maple-icons' ) ); } // Update settings. $this->unmark_downloaded( $slug ); return true; } /** * Get info about a downloaded set. * * @param string $slug Icon set slug. * @return array|null Set info or null if not downloaded. */ public function get_downloaded_info( $slug ) { $downloaded = $this->get_downloaded_sets(); return isset( $downloaded[ $slug ] ) ? $downloaded[ $slug ] : null; } /** * Refresh settings from database. */ public function refresh() { $this->load_settings(); } }