416 lines
12 KiB
PHP
416 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* Icon Registry class - Manages downloaded sets and provides icon search/retrieval.
|
|
*
|
|
* @package MapleIcons
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Class MI_Icon_Registry
|
|
*
|
|
* Manages downloaded icon sets and provides search/retrieval functionality.
|
|
*/
|
|
class MI_Icon_Registry {
|
|
|
|
/**
|
|
* Singleton instance.
|
|
*
|
|
* @var MI_Icon_Registry|null
|
|
*/
|
|
private static $instance = null;
|
|
|
|
/**
|
|
* Cached settings.
|
|
*
|
|
* @var array|null
|
|
*/
|
|
private $settings = null;
|
|
|
|
/**
|
|
* Get singleton instance.
|
|
*
|
|
* @return MI_Icon_Registry
|
|
*/
|
|
public static function get_instance() {
|
|
if ( null === self::$instance ) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Constructor.
|
|
*/
|
|
private function __construct() {
|
|
$this->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();
|
|
}
|
|
}
|