register( new AKLA_Elementor_Widget() ); } } /** * Search keywords, the same as the block's. * * @return string[] Keywords. */ public static function keywords() { return array( 'activities', 'kids', 'children', 'courses', 'classes', 'camps', 'schedule' ); } /** * Saved configurations for the widget's Configuration control. * * @return array Configuration id => name, led by an empty choice. */ public static function profile_options() { $options = array( '' => __( 'Choose a saved configuration', 'actikidz-local-activities' ) ); foreach ( AKLA_Admin::profile_choices() as $choice ) { $options[ (string) $choice['value'] ] = (string) $choice['label']; } return $options; } /** * Map widget settings to the renderer's overrides, validated exactly like block previews. * * @param mixed $settings Widget settings for display. * @return array Overrides: limit, layout, heading_level and style, each only when valid. */ public static function overrides( $settings ) { $settings = is_array( $settings ) ? $settings : array(); return AKLA_Admin::preview_overrides( array( 'limit' => $settings['limit'] ?? '', 'layout' => $settings['layout'] ?? '', 'headingLevel' => $settings['heading_level'] ?? 3, 'style' => 'yes' === ( $settings['compact'] ?? '' ) ? 'compact' : '', ) ); } /** * Derive a stable widget id for one widget instance. * * Elementor element ids are unique within a document and survive saves, so the same placement * always reports the same id, while different placements never share one. * * @param mixed $document_id Elementor document (post) id. * @param mixed $element_id Elementor element id. * @return string UUID shaped as version 4. */ public static function widget_id( $document_id, $element_id ) { $hash = md5( 'akla-elementor|' . absint( $document_id ) . '|' . (string) $element_id ); return sprintf( '%s-%s-4%s-%s%s-%s', substr( $hash, 0, 8 ), substr( $hash, 8, 4 ), substr( $hash, 13, 3 ), dechex( 8 | ( hexdec( $hash[16] ) & 3 ) ), substr( $hash, 17, 3 ), substr( $hash, 20, 12 ) ); } /** * The document that owns the widget: the template for theme-builder parts, otherwise the post. * * @return int Document id, or 0 when unknown. */ public static function document_id() { $plugin = self::elementor(); if ( is_object( $plugin ) && isset( $plugin->documents ) && is_object( $plugin->documents ) && method_exists( $plugin->documents, 'get_current' ) ) { $document = $plugin->documents->get_current(); if ( is_object( $document ) && method_exists( $document, 'get_main_id' ) ) { return absint( $document->get_main_id() ); } } return absint( get_the_ID() ); } /** * Editor renders never count as live placements. * * @return string PREVIEW inside the Elementor editor or any other preview context, otherwise LIVE. */ public static function purpose() { return self::editing() ? 'PREVIEW' : AKLA_Renderer::context_purpose(); } /** * Render one widget instance. * * @param mixed $settings Widget settings for display. * @param mixed $element_id Elementor element id. * @return string Escaped HTML from the renderer, or an escaped editor-only hint when it is empty. */ public static function render_widget( $settings, $element_id ) { $settings = is_array( $settings ) ? $settings : array(); $profile_id = isset( $settings['profile'] ) && is_string( $settings['profile'] ) ? $settings['profile'] : ''; $html = (string) AKLA_Renderer::render( $profile_id, self::widget_id( self::document_id(), $element_id ), self::purpose(), self::overrides( $settings ) ); if ( '' !== $html || ! self::editing() ) { return $html; } // Visitors see nothing for an unusable widget; editors see why, so the widget does not look broken. if ( '' === $profile_id ) { $hint = __( 'Choose a saved configuration to show local activities on this page.', 'actikidz-local-activities' ); } elseif ( ! AKLA_Client::connected() ) { $hint = __( 'Connect this site to ActiKidz first.', 'actikidz-local-activities' ); } else { $hint = __( 'This configuration is no longer available. Choose another one.', 'actikidz-local-activities' ); } return '

' . esc_html( $hint ) . '

'; } /** * Whether the current request renders inside the Elementor editor or its preview frame. * * @return bool True while editing. */ private static function editing() { $plugin = self::elementor(); if ( ! is_object( $plugin ) ) { return false; } if ( isset( $plugin->editor ) && is_object( $plugin->editor ) && method_exists( $plugin->editor, 'is_edit_mode' ) && $plugin->editor->is_edit_mode() ) { return true; } return isset( $plugin->preview ) && is_object( $plugin->preview ) && method_exists( $plugin->preview, 'is_preview_mode' ) && $plugin->preview->is_preview_mode(); } /** * The Elementor plugin instance, when Elementor is active. * * @return object|null Instance or null. */ private static function elementor() { if ( ! class_exists( 'Elementor\Plugin' ) || ! property_exists( 'Elementor\Plugin', 'instance' ) ) { return null; } $plugin = \Elementor\Plugin::$instance; return is_object( $plugin ) ? $plugin : null; } }