array_fill_keys( array( 'class', 'viewbox', 'width', 'height', 'fill', 'aria-hidden', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin' ), true ),
'path' => array_fill_keys( array( 'd', 'fill', 'opacity' ), true ),
'rect' => array_fill_keys( array( 'x', 'y', 'width', 'height', 'rx' ), true ),
'circle' => array_fill_keys( array( 'cx', 'cy', 'r' ), true ),
'span' => array( 'class' => true, 'aria-hidden' => true ),
'strong' => array(), 'small' => array(),
'img' => array_fill_keys( array( 'src', 'alt', 'width', 'height', 'loading', 'decoding', 'referrerpolicy', 'data-lai-avatar' ), true ),
);
}
/** A fixed, scalable Logged.ai monogram. */
public static function mark(): string {
return ' ';
}
public static function brand(): string {
return 'Logged. ai ';
}
public static function screen(): bool {
global $plugin_page, $pagenow;
return 'admin.php' === $pagenow && is_string( $plugin_page ) && ( 'logged-ai' === $plugin_page || str_starts_with( $plugin_page, 'logged-ai-' ) );
}
public static function boot(): void {
add_filter( 'admin_body_class', static fn( string $classes ): string => $classes . ( self::screen() ? ' lai-app' : '' ) );
add_action( 'admin_enqueue_scripts', static function (): void {
if ( self::screen() ) {
wp_enqueue_style( 'logged-ai-studio', plugins_url( 'assets/studio.css', LOGGED_AI_FILE ), array(), self::VERSION );
wp_enqueue_script( 'logged-ai-studio', plugins_url( 'assets/studio.js', LOGGED_AI_FILE ), array( 'wp-i18n' ), self::VERSION, true );
wp_set_script_translations( 'logged-ai-studio', 'logged-ai', plugin_dir_path( LOGGED_AI_FILE ) . 'languages' );
wp_enqueue_style( 'logged-ai-activity', plugins_url( 'assets/activity.css', LOGGED_AI_FILE ), array( 'logged-ai-studio' ), self::VERSION );
wp_enqueue_script( 'logged-ai-activity', plugins_url( 'assets/activity.js', LOGGED_AI_FILE ), array( 'logged-ai-studio', 'wp-i18n' ), self::VERSION, true );
wp_set_script_translations( 'logged-ai-activity', 'logged-ai', plugin_dir_path( LOGGED_AI_FILE ) . 'languages' );
wp_enqueue_style( 'logged-ai-settings-ui', plugins_url( 'assets/settings.css', LOGGED_AI_FILE ), array( 'logged-ai-studio' ), self::VERSION );
wp_enqueue_script( 'logged-ai-settings-ui', plugins_url( 'assets/settings.js', LOGGED_AI_FILE ), array( 'logged-ai-studio', 'wp-i18n' ), self::VERSION, true );
wp_set_script_translations( 'logged-ai-settings-ui', 'logged-ai', plugin_dir_path( LOGGED_AI_FILE ) . 'languages' );
wp_enqueue_style( 'logged-ai-value', plugins_url( 'assets/value.css', LOGGED_AI_FILE ), array( 'logged-ai-studio' ), self::VERSION );
wp_enqueue_style( 'logged-ai-sidebar', plugins_url( 'assets/sidebar.css', LOGGED_AI_FILE ), array( 'logged-ai-value' ), self::VERSION );
wp_enqueue_style( 'logged-ai-discovery-navigation', plugins_url( 'assets/discovery-navigation.css', LOGGED_AI_FILE ), array( 'logged-ai-sidebar' ), self::VERSION );
wp_enqueue_script( 'logged-ai-value', plugins_url( 'assets/value.js', LOGGED_AI_FILE ), array( 'logged-ai-studio', 'wp-i18n' ), self::VERSION, true );
wp_set_script_translations( 'logged-ai-value', 'logged-ai', plugin_dir_path( LOGGED_AI_FILE ) . 'languages' );
if ( 'logged-ai-setup' === ( $GLOBALS['plugin_page'] ?? '' ) ) {
wp_enqueue_style( 'logged-ai-setup', plugins_url( 'assets/setup.css', LOGGED_AI_FILE ), array( 'logged-ai-studio' ), self::VERSION );
wp_enqueue_script( 'logged-ai-setup', plugins_url( 'assets/setup.js', LOGGED_AI_FILE ), array( 'logged-ai-studio', 'wp-i18n' ), self::VERSION, true );
wp_set_script_translations( 'logged-ai-setup', 'logged-ai', plugin_dir_path( LOGGED_AI_FILE ) . 'languages' );
}
}
}, 999 );
}
/** Fixed decorative vector paths; never accepts arbitrary markup. */
public static function icon( string $name ): string {
$paths = array(
'plans' => ' ',
'setup' => ' ',
'overview' => ' ',
'activity' => ' ',
'requests' => ' ',
'incidents' => ' ',
'response' => ' ',
'alerts' => ' ',
'reports' => ' ',
'settings' => ' ',
'arrow' => ' ',
'moon' => ' ',
'lock' => ' ',
);
return '' . ( $paths[ $name ] ?? $paths['activity'] ) . ' ';
}
public static function label( string $code ): string {
$labels = array(
'content.post_created' => __( 'Content created', 'logged-ai' ),
'content.post_deleted' => __( 'Content deleted', 'logged-ai' ),
'content.post_trashed' => __( 'Content moved to trash', 'logged-ai' ),
'identity.user_created' => __( 'User account created', 'logged-ai' ),
'identity.user_deleted' => __( 'User account deleted', 'logged-ai' ),
'configuration.plugin_operation_failed' => __( 'Plugin operation failed', 'logged-ai' ),
'identity.roles_changed' => __( 'User permissions changed', 'logged-ai' ),
'identity.password_changed' => __( 'Password changed', 'logged-ai' ),
'identity.application_password_created' => __( 'Application password created', 'logged-ai' ),
'identity.login' => __( 'User signed in', 'logged-ai' ),
'identity.logout' => __( 'User signed out', 'logged-ai' ),
'identity.account_auth_failed_burst' => __( 'Repeated sign-in failures', 'logged-ai' ),
'identity.authentication_failed_burst' => __( 'Sign-in failure burst', 'logged-ai' ),
'identity.public_registration_completed' => __( 'Public registration completed', 'logged-ai' ),
'configuration.option_changed' => __( 'Site configuration changed', 'logged-ai' ),
'configuration.plugin_activated' => __( 'Plugin activated', 'logged-ai' ),
'configuration.plugin_updated' => __( 'Plugin updated', 'logged-ai' ),
'configuration.plugin_deactivated' => __( 'Plugin deactivated', 'logged-ai' ),
'configuration.theme_changed' => __( 'Theme changed', 'logged-ai' ),
'content.post_updated' => __( 'Content updated', 'logged-ai' ),
'audit.export_cancelled' => __( 'Export cancelled', 'logged-ai' ),
'audit.export_completed' => __( 'Export completed', 'logged-ai' ),
'audit.export_downloaded' => __( 'Export downloaded', 'logged-ai' ),
'audit.export_requested' => __( 'Export requested', 'logged-ai' ),
'audit.network_export_cancelled' => __( 'Network export cancelled', 'logged-ai' ),
'audit.network_export_completed' => __( 'Network export completed', 'logged-ai' ),
'audit.network_export_downloaded' => __( 'Network export downloaded', 'logged-ai' ),
'audit.network_export_requested' => __( 'Network export requested', 'logged-ai' ),
'commerce.coupon_changed' => __( 'Coupon changed', 'logged-ai' ),
'commerce.coupon_created' => __( 'Coupon created', 'logged-ai' ),
'commerce.order_changed' => __( 'Order changed', 'logged-ai' ),
'commerce.order_created' => __( 'Order created', 'logged-ai' ),
'commerce.order_deleted' => __( 'Order deleted', 'logged-ai' ),
'commerce.payment_setting_changed' => __( 'Payment setting changed', 'logged-ai' ),
'commerce.product_changed' => __( 'Product changed', 'logged-ai' ),
'commerce.product_created' => __( 'Product created', 'logged-ai' ),
'commerce.refund_changed' => __( 'Refund changed', 'logged-ai' ),
'commerce.refund_deleted' => __( 'Refund deleted', 'logged-ai' ),
'commerce.refund_stored' => __( 'Refund stored', 'logged-ai' ),
'commerce.stock_changed' => __( 'Stock changed', 'logged-ai' ),
'commerce.store_setting_changed' => __( 'Store setting changed', 'logged-ai' ),
'configuration.alerts_changed' => __( 'Alerts changed', 'logged-ai' ),
'configuration.automatic_suppressed' => __( 'Automatic suppressed', 'logged-ai' ),
'configuration.collection_changed' => __( 'Collection changed', 'logged-ai' ),
'configuration.core_operation_failed' => __( 'Core operation failed', 'logged-ai' ),
'configuration.core_updated' => __( 'Core updated', 'logged-ai' ),
'configuration.diagnostic_export_started' => __( 'Diagnostic export started', 'logged-ai' ),
'configuration.navigation_locations_changed' => __( 'Navigation locations changed', 'logged-ai' ),
'configuration.network_alerts_changed' => __( 'Network alerts changed', 'logged-ai' ),
'configuration.network_plugin_activated' => __( 'Network plugin activated', 'logged-ai' ),
'configuration.network_plugin_deactivated' => __( 'Network plugin deactivated', 'logged-ai' ),
'configuration.network_privacy_changed' => __( 'Network privacy changed', 'logged-ai' ),
'configuration.observer_test_started' => __( 'Observer test started', 'logged-ai' ),
'configuration.plugin_deleted' => __( 'Plugin deleted', 'logged-ai' ),
'configuration.plugin_installed' => __( 'Plugin installed', 'logged-ai' ),
'configuration.privacy_changed' => __( 'Privacy changed', 'logged-ai' ),
'configuration.request_source_changed' => __( 'Request source changed', 'logged-ai' ),
'configuration.role_capability_changed' => __( 'Role capability changed', 'logged-ai' ),
'configuration.role_definition_added' => __( 'Role definition added', 'logged-ai' ),
'configuration.role_definition_removed' => __( 'Role definition removed', 'logged-ai' ),
'configuration.sidebars_changed' => __( 'Sidebars changed', 'logged-ai' ),
'configuration.theme_deleted' => __( 'Theme deleted', 'logged-ai' ),
'configuration.theme_installed' => __( 'Theme installed', 'logged-ai' ),
'configuration.theme_operation_failed' => __( 'Theme operation failed', 'logged-ai' ),
'configuration.theme_updated' => __( 'Theme updated', 'logged-ai' ),
'configuration.uninstall_choice_changed' => __( 'Uninstall choice changed', 'logged-ai' ),
'configuration.widget_changed' => __( 'Widget changed', 'logged-ai' ),
'content.comment_created' => __( 'Comment created', 'logged-ai' ),
'content.comment_deleted' => __( 'Comment deleted', 'logged-ai' ),
'content.comment_status_changed' => __( 'Comment status changed', 'logged-ai' ),
'content.comment_updated' => __( 'Comment updated', 'logged-ai' ),
'content.navigation_item_configuration_changed' => __( 'Navigation item configuration changed', 'logged-ai' ),
'content.post_changed' => __( 'Post changed', 'logged-ai' ),
'content.term_created' => __( 'Term created', 'logged-ai' ),
'content.term_deleted' => __( 'Term deleted', 'logged-ai' ),
'content.term_updated' => __( 'Term updated', 'logged-ai' ),
'content.terms_changed' => __( 'Terms changed', 'logged-ai' ),
'identity.application_password_deleted' => __( 'Application password deleted', 'logged-ai' ),
'identity.capability_changed' => __( 'Capability changed', 'logged-ai' ),
'identity.password_rehashed' => __( 'Password rehashed', 'logged-ai' ),
'identity.password_reset_completed' => __( 'Password reset completed', 'logged-ai' ),
'identity.password_set' => __( 'Password set', 'logged-ai' ),
'identity.profile_changed' => __( 'Profile changed', 'logged-ai' ),
'identity.reset_mail_result' => __( 'Reset mail result', 'logged-ai' ),
'identity.reset_request_accepted' => __( 'Reset request accepted', 'logged-ai' ),
'identity.reset_requested' => __( 'Reset requested', 'logged-ai' ),
'identity.site_membership_added' => __( 'Site membership added', 'logged-ai' ),
'identity.site_membership_removed' => __( 'Site membership removed', 'logged-ai' ),
'identity.super_admin_granted' => __( 'Super admin granted', 'logged-ai' ),
'identity.super_admin_revoked' => __( 'Super admin revoked', 'logged-ai' ),
'network.option_changed' => __( 'Option changed', 'logged-ai' ),
'network.site_archived' => __( 'Site archived', 'logged-ai' ),
'network.site_changed' => __( 'Site changed', 'logged-ai' ),
'network.site_created' => __( 'Site created', 'logged-ai' ),
'network.site_deleted' => __( 'Site deleted', 'logged-ai' ),
'network.site_restored' => __( 'Site restored', 'logged-ai' ),
'privacy.identity_erased' => __( 'Identity erased', 'logged-ai' ),
'privacy.network_identity_erased' => __( 'Network identity erased', 'logged-ai' ),
'privacy.network_retention_pruned' => __( 'Network retention pruned', 'logged-ai' ),
'privacy.retention_pruned' => __( 'Retention pruned', 'logged-ai' ),
);
return $labels[ $code ] ?? ucfirst( str_replace( array( '.', '_' ), ' ', preg_replace( '/^[^.]+\./', '', $code ) ) );
}
public static function experience(): void {
echo ( '' . esc_html__( 'Display detail', 'logged-ai' ) . ' ' . esc_html__( 'Readable stories and guided controls.', 'logged-ai' ) . ' ' . esc_html__( 'Changes what you see, not collection, alerts, response or permissions.', 'logged-ai' ) . '
' . esc_html__( 'Essentials', 'logged-ai' ) . ' ' . esc_html__( 'Understand', 'logged-ai' ) . ' ' . esc_html__( 'Operator', 'logged-ai' ) . ' ' . esc_html__( 'Work & filter', 'logged-ai' ) . ' ' . esc_html__( 'Expert', 'logged-ai' ) . ' ' . esc_html__( 'Inspect everything', 'logged-ai' ) . '
' );
}
public static function time_label( TimeDisplay $time, mixed $value ): string {
$text = $time->text( $value );
$date = \DateTimeImmutable::createFromFormat( '!Y-m-d H:i:s', substr( $text, 0, 19 ) );
return false === $date ? $text : wp_date( _x( 'M j · H:i', 'compact activity date format', 'logged-ai' ), $date->getTimestamp(), $date->getTimezone() );
}
private static function guide( string $group ): void {
$help = array(
'activity' => array( __( 'Start with a change you recognise.', 'logged-ai' ), __( 'Open an event to see the actor, the affected object and the captured result. Use filters when you know what you are looking for.', 'logged-ai' ) ),
'requests' => array( __( 'A finding is a reason to look closer.', 'logged-ai' ), __( 'Review the observed pattern and source context before choosing a response. Collection can miss cache hits and upstream traffic.', 'logged-ai' ) ),
'incidents' => array( __( 'Follow the evidence, one event at a time.', 'logged-ai' ), __( 'Open a case, review its retained members and record a decision. A connection between events does not establish why they happened.', 'logged-ai' ) ),
'response' => array( __( 'Preview first. Decide second.', 'logged-ai' ), __( 'Check what a policy matches, its expiry and the recovery controls before confirming it. Automatic response begins switched off.', 'logged-ai' ) ),
'alerts' => array( __( 'Only ask for the notifications you need.', 'logged-ai' ), __( 'Choose useful events and check the destination. Delivery acceptance does not guarantee someone received or read the message.', 'logged-ai' ) ),
'reports' => array( __( 'Start with a useful view.', 'logged-ai' ), __( 'Filter your evidence before exporting it. Pro adds saved searches, local summaries and scheduled reviews.', 'logged-ai' ) ),
'settings' => array( __( 'Make the workspace fit your site.', 'logged-ai' ), __( 'Review collection, privacy and retention first. Display detail changes presentation only; collection, alerts, response and permissions stay the same.', 'logged-ai' ) ),
);
if ( ! isset( $help[ $group ] ) ) { return; }
echo '' . wp_kses( self::icon( $group ), Design::allowed_markup() ) . '' . esc_html( $help[ $group ][0] ) . ' ' . esc_html( $help[ $group ][1] ) . '
';
}
public static function upgrade_card(): void {
echo ( '' . esc_html__( 'LOGGED.AI / PRO', 'logged-ai' ) . ' ' ) . wp_kses( self::mark(), Design::allowed_markup() ) . ( '
' . esc_html__( 'Less repeated work.', 'logged-ai' ) . ' ' . esc_html__( 'Clearer handovers.', 'logged-ai' ) . ' ' . esc_html__( 'Schedule private reports, configure team alerts and keep investigation notes with the evidence.', 'logged-ai' ) . '
' . esc_html__( 'See Pro in action', 'logged-ai' ) . ' ' ) . wp_kses( self::icon( 'arrow' ), Design::allowed_markup() ) . ( ' ' . esc_html__( 'For regular reviews, teams and client work.', 'logged-ai' ) . ' ' );
}
public static function upgrade_dialog( bool $pro = false, ?string $workspace_url = null, array $destinations = array() ): void {
if ( $pro && ! $destinations && $workspace_url ) { $destinations['investigate'] = array('url'=>$workspace_url,'label'=>__( 'Open Pro workspace', 'logged-ai' )); }
DesignPro::render( $pro, $destinations, true );
}
public static function preference_scope(): string {
return substr( hash( 'sha256', admin_url() . '|' . get_current_blog_id() . '|' . get_current_user_id() ), 0, 24 );
}
/**
* Render registered destinations without altering route authorization.
*
* @param array $groups Permission-filtered navigation groups.
* @param string $current Current group key.
* @param string $base Current site or network admin URL.
*/
private static function navigation_links( array $groups, string $current, string $base ): void {
foreach ( $groups as $key => $group ) {
$slug = array_key_first( $group['pages'] );
$label = 'settings' === $key ? __( 'Settings', 'logged-ai' ) : $group['label'];
$icon = 'plugins' === $key ? 'overview' : $key;
echo '' . wp_kses( self::icon( $icon ), self::allowed_markup() ) . '' . esc_html( $label ) . ' ' . ( $key === $current ? ' ' : '' ) . ' ';
}
}
/** $groups contains only registered routes allowed by Navigation::available(). */
public static function shell( array $groups, bool $network, string $page ): void {
$current = null;
$pro = defined( 'LOGGED_AI_PRO_FILE' );
foreach ( $groups as $key => $group ) {
if ( isset( $group['pages'][ $page ] ) ) { $current = $key; }
if ( isset( $group['pages']['logged-ai-investigations'] ) || isset( $group['pages']['logged-ai-network-priority'] ) ) { $pro = true; }
}
if ( null === $current ) { return; }
$base = $network ? Scope::network_admin_url( 'admin.php' ) : admin_url( 'admin.php' );
echo '
' . esc_html__( 'Workspace', 'logged-ai' ) . ' ' . '/ ' ) . esc_html( $groups[ $current ]['label'] ) . '
';
if ( $pro ) { echo '' . esc_html__( 'PRO WORKSPACE', 'logged-ai' ) . ' '; }
echo '' . wp_kses( self::icon( 'lock' ), Design::allowed_markup() ) . ( ' ' . esc_html__( 'Local storage', 'logged-ai' ) . ' ' ) . wp_kses( self::icon( 'moon' ), Design::allowed_markup() ) . '
';
if ( count( $groups[ $current ]['pages'] ) > 1 ) {
echo ( '' );
foreach ( $groups[ $current ]['pages'] as $slug => $item ) {
echo '' . esc_html( $item['label'] ) . ' ';
}
echo ' ';
}
if ( ! $network ) { DesignPro::continuation(); }
if ( ! in_array( $page, array( 'logged-ai-overview', 'logged-ai-setup', 'logged-ai-plans', 'logged-ai-commercial', 'logged-ai-commercial-updates', 'logged-ai-more-plugins' ), true ) ) { self::experience(); }
if ( 'logged-ai-plans' !== $page ) {
echo '' . esc_html__( 'Explore Logged.ai Pro workflows', 'logged-ai' ) . '
' );
self::upgrade_dialog( $pro, null, DesignPro::destinations( $groups, $base ) );
}
}
}