esc_html__( 'What is a loop?', 'elementor-pro' ), 'content' => esc_html__( 'A Loop is a layout you can customize to display recurring dynamic content - like listings, posts, portfolios, products, , etc.', 'elementor-pro' ), 'tip' => esc_html__( 'Start by creating a master item. All the other instances in the grid will match this design. Then go back to the widget in the editor panel and assign both a template and a source of content. Your grid should populate automatically.', 'elementor-pro' ), 'docs' => 'https://go.elementor.com/app-theme-builder-loop', 'video_url' => 'https://www.youtube.com/embed/zMvY9XaE1YY', ]; } protected static function get_site_editor_thumbnail_url() { return ELEMENTOR_PRO_MODULES_URL . 'loop-builder/assets/images/loop-item.svg'; } public static function get_properties() { $properties = parent::get_properties(); $properties['support_conditions'] = false; return $properties; } public function save( $data ) { if ( isset( $data['settings']['source'] ) ) { update_post_meta( $this->get_main_id(), '_elementor_source', $data['settings']['source'] ); } parent::save( $data ); } private function get_data_id() { if ( Taxonomy_Loop_Provider::is_loop_taxonomy() ) { return $this->get_data_id_from_taxonomy_loop_query(); } return get_the_ID(); } public function get_container_attributes() { $attributes = Document::get_container_attributes(); $post_id = $this->get_data_id(); $attributes['class'] .= ' e-loop-item'; $attributes['class'] .= ' e-loop-item-' . $post_id; $attributes['class'] .= ' ' . esc_attr( implode( ' ', get_post_class( [], $post_id ) ) ); $attributes['data-custom-edit-handle'] = true; return $attributes; } public function get_initial_config() { $config = parent::get_initial_config(); $loop_builder_module = new LoopBuilderModule(); if ( 'post' === $loop_builder_module->get_source_type_from_post_meta( $this->get_main_id() ) ) { foreach ( static::RECOMMENDED_POSTS_WIDGET_NAMES as $recommended_posts_widget_name ) { $config['panel']['widgets_settings'][ $recommended_posts_widget_name ] = [ 'categories' => [ 'recommended' ], 'show_in_panel' => true, ]; } } $config['panel']['widgets_settings']['container'] = [ 'categories' => [ 'layout' ], ]; foreach ( static::WIDGETS_TO_HIDE as $widget_to_hide ) { $config['panel']['widgets_settings'][ $widget_to_hide ] = [ 'show_in_panel' => false, ]; } $config['container_attributes'] = $this->get_container_attributes(); return $config; } public static function get_site_editor_config() { $config = parent::get_site_editor_config(); $config['show_instances'] = false; return $config; } public function get_location_label() { return ''; } public function get_css_wrapper_selector() { return '.e-loop-item-' . $this->get_main_id(); } public static function get_preview_as_options() { $post_types = Utils::get_public_post_types(); $post_types_options = []; foreach ( $post_types as $post_type => $label ) { $post_types_options[ self::SINGLE_PREFIX . $post_type ] = get_post_type_object( $post_type )->labels->singular_name; } return [ 'single' => [ 'label' => esc_html__( 'Single', 'elementor-pro' ), 'options' => $post_types_options, ], ]; } protected function get_remote_library_config() { $config = parent::get_remote_library_config(); $config['type'] = self::DOCUMENT_TYPE; $config['default_route'] = 'templates/loop-items'; return $config; } /** * Get Edit Url * * Disable the Library modal for non-container (section) users. * * @return string */ public function get_edit_url() { $url = parent::get_edit_url(); if ( ! Plugin::elementor()->experiments->is_feature_active( 'container' ) ) { $url = str_replace( '#library', '', $url ); } return $url; } protected static function get_editor_panel_categories() { $new_categories = [ 'recommended' => [ 'title' => esc_html__( 'Recommended', 'elementor-pro' ), ], 'layout' => [ 'title' => esc_html__( 'Layout', 'elementor-pro' ), 'hideIfEmpty' => true, ], ]; return static::insert_categories_after_favorites( $new_categories ); } protected function register_controls() { parent::register_controls(); $this->remove_control( 'content_wrapper_html_tag' ); $this->update_preview_control(); $this->inject_width_control(); $this->add_query_section(); Plugin::elementor()->controls_manager->add_custom_css_controls( $this ); } /** * Get Wrapper Tags * * We remove the `content_wrapper_html_tag` control in this document and default to using a `div`. * The setting no longer exists when printing the document element, so we need to override this method so that * the extended document class defaults to using a `div` when printing the element. * * @since 3.8.0 * * @return false */ public function get_wrapper_tags() { return false; } /** * Print elements with wrapper. * * Overwrite method from theme-document.php to render some custom markup if a variable * $elements_data['empty_loop_template'] is set. This variable is set via a filter hook * 'elementor/frontend/builder_content_data' in the loop builder module. * * @since 3.8.0 * * @param $elements_data * * @return void */ public function print_elements_with_wrapper( $elements_data = null ) { if ( isset( $elements_data['empty_loop_template'] ) ) { $this->print_empty_loop_template_markup( $elements_data['empty_loop_template_id'] ); } else { parent::print_elements_with_wrapper( $elements_data ); } } private function enqueue_loop_css() { if ( $this->is_autosave() ) { $css_file = Loop_Preview::create( $this->post->ID ); } else { $css_file = Loop_CSS::create( $this->post->ID ); } $css_file->print_all_css( $this->post->ID ); } /** * Get content. * * Override the parent method to retrieve the content with CSS in the Editor. * * @since 3.8.0 */ public function get_content( $with_css = false ) { $edit_mode = Plugin::elementor()->editor->is_edit_mode(); add_filter( 'elementor/frontend/builder_content/before_print_css', [ $this, 'prevent_inline_css_printing' ] ); $this->enqueue_loop_css(); Plugin::elementor()->editor->set_edit_mode( false ); $content = parent::get_content(); remove_filter( 'elementor/frontend/builder_content/before_print_css', [ $this, 'prevent_inline_css_printing' ] ); Plugin::elementor()->editor->set_edit_mode( $edit_mode ); return $content; } /** * Runs on the 'elementor/frontend/builder_content/before_print_css' hook. * * @return false */ public function prevent_inline_css_printing() { return false; } /** * Print empty loop template markup. * * This function is used to render markup in the editor when a loop template is empty/blank. * Currently, nothing will be rendered in the editor if the template is empty. * This markup is needed in the DOM for us to be able to switch to this document in place. * * @since 3.8.0 * * @param int $post_id The post ID of the document. * * @return void */ protected function print_empty_loop_template_markup( $post_id ) { ?>