> 백엔드 개발 > PHP 튜토리얼 > Joomla 아트의 스마트 검색 분석 플러그인 만들기 I.

Joomla 아트의 스마트 검색 분석 플러그인 만들기 I.

Barbara Streisand
풀어 주다: 2024-12-04 22:29:11
원래의
367명이 탐색했습니다.

이전 기사에서는 Joomla 스마트 검색 구성 요소의 기능에 대해 알아보고 CRON을 사용한 예약된 인덱싱 매개변수 및 구성에 대해 이야기했습니다. 자체 플러그인용 코드 생성을 시작해 보겠습니다.

리소스 목록

기술적인 부분을 시작하기 전에 주요 주제를 직접적으로 다루는 몇 가지 기사를 언급하겠습니다. Joomla 4 / Joomla 5의 최신 아키텍처를 위한 플러그인 생성 및/또는 업데이트를 일반적으로 다루는 기사도 있습니다. 다음으로 독자가 해당 기사를 읽었으며 일반적으로 작동하는 플러그인을 만드는 방법에 대한 아이디어를 가지고 있다고 가정합니다. Joomla의 경우:

  • 스마트 검색 플러그인 만들기 - 공식 Joomla 문서. Joomla 3에 대한 것이지만 대부분의 조항은 Joomla 4 / Joomla 5에 적용됩니다
  • 스마트 검색 플러그인 개발 2012년 Joomla Community Magazine 기사.
  • Joomla 개발을 다룬 Nicholas Dionysopoulos의 Joomla Extensions Development 책! Joomla 버전 4 및 5의 확장 기능.
  • 새로운 문서 포털 manual.joomla.org의 데이터베이스 섹션 - Joomla 4 및 Joomla 5용. ## 기술적인 부분입니다. Joomla 5 스마트 검색 플러그인 개발 스마트 검색 구성 요소는 데이터 공급자 플러그인과 함께 작동합니다. 주요 작업은 데이터를 선택하고 인덱싱을 위해 구성 요소에 제공하는 것입니다. 그러나 시간이 지나면서 재색인 작업도 플러그인의 책임 영역으로 넘어갔습니다. 이 기사에서는 관리자 패널에서 콘텐츠 인덱싱을 수동으로 실행한다고 가정합니다. CLI의 작업은 시각적으로 다르지만 본질은 동일합니다.

숙련된 개발자를 위해 검색 플러그인은 JoomlaComponentFinderAdministratorIndexerAdapter 클래스를 확장하며 클래스 파일은 administrator/comComponents/com_finder/src/Indexer/Adapter.php에 있습니다. 그렇다면 그들은 스스로 알아낼 것입니다. 또한 샘플로 plugins/finder 폴더에서 기사, 카테고리, 연락처, 태그 등에 대한 Joomla 핵심 스마트 검색 플러그인을 연구할 수 있습니다. 저는 JoomShopping(Joomla 전자상거래 구성 요소) 및 SW JProjects(업데이트 서버가 포함된 자체 Joomla 확장 디렉터리 구성 요소) 구성 요소에 대한 스마트 검색 플러그인 작업을 수행했으므로 클래스 이름과 일부 뉘앙스가 여기에 연결됩니다. JoomShopping의 예를 사용하여 대부분을 보여 드리겠습니다. 다국어 문제에 대한 해결책은 SW JProjects의 사례를 바탕으로 합니다.

스마트 검색 플러그인의 파일 구조

Joomshopping용 스마트 검색 플러그인의 파일 구조는 일반적인 것과 다르지 않습니다.

The anatomy of smart search in Joomla art Creating a plugin I.
Joomla 5 스마트 검색 플러그인 파일 구조

파일 서비스/provider.php

provider.php 파일을 사용하면 Joomla DI 컨테이너에 플러그인을 등록할 수 있고 MVCFactory를 사용하여 외부에서 플러그인 메소드에 액세스할 수 있습니다.

<?php

/**
 * @package     Joomla.Plugin
 * @subpackage  Finder.Wtjoomshoppingfinder
 *
 * @copyright   (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

\defined('_JEXEC') or die;

use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Database\DatabaseInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Joomla\Plugin\Finder\Wtjoomshoppingfinder\Extension\Wtjoomshoppingfinder;

return new class () implements ServiceProviderInterface {
    /**
     * Registers the service provider with a DI container.
     *
     * @param   Container  $container  The DI container.
     *
     * @return  void
     *
     * @since   4.3.0
     */
    public function register(Container $container)
    {
        $container->set(
            PluginInterface::class,
            function (Container $container) {
                $plugin     = new Wtjoomshoppingfinder(
                    $container->get(DispatcherInterface::class),
                    (array) PluginHelper::getPlugin('finder', 'wtjoomshoppingfinder')
                );
                $plugin->setApplication(Factory::getApplication());

                // Our plugin uses DatabaseTrait, so the setDatabase() method appeared 
                // If it is not present, then we use only setApplication().
                $plugin->setDatabase($container->get(DatabaseInterface::class));

                return $plugin;
            }
        );
    }
};
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

플러그인 클래스 파일

플러그인의 주요 작업 코드가 포함된 파일입니다. src/Extension 폴더에 위치해야 합니다. 내 경우에는 JoomlaPluginFinderWtjoomshoppingfinderExtensionWtjoomshoppingfinder 플러그인 클래스가 plugins/finder/wtjoomshoppingfinder/src/Extension/Wtjoomshoppingfinder.php 파일에 있습니다. 플러그인의 네임스페이스는 JoomlaPluginFinderWtjoomshoppingfinderExtension입니다.

작업에 필요한 최소한의 클래스 속성 및 메서드 집합이 있습니다(부모 Adapter 클래스를 포함하여 액세스됨).

클래스의 최소 필수 속성

  • $extension - 콘텐츠 유형을 정의하는 구성 요소의 이름입니다. 예를 들어 com_content입니다. 제 경우에는 com_jshopping입니다.
  • $context - 플러그인의 고유 식별자로, 플러그인에 액세스할 인덱싱 컨텍스트를 설정합니다. 실제로 이는 플러그인 클래스(요소)의 이름입니다. 저희 경우는 Wtjoomshoppingfinder입니다.
  • $layout - 검색 결과 요소의 출력 레이아웃 이름입니다. 이 레이아웃은 검색 결과를 표시할 때 사용됩니다. 예를 들어, $layout 매개변수가 기사로 설정된 경우 이 유형의 검색 결과를 표시해야 할 때 기본 보기 모드는 default_article.php라는 레이아웃 파일을 검색합니다. 해당 파일을 찾을 수 없으면 default_result.php라는 이름의 레이아웃 파일이 대신 사용됩니다. HTML 레이아웃이 포함된 출력 레이아웃은 comComponents/com_finder/tmpl/search에 있습니다. 그러나 레이아웃을 html 템플릿 폴더인 templates/YOUR_TEMPLATE/html/com_finder/search에 재정의하여 배치해야 합니다. 제 경우에는 레이아웃 제품 이름을 지정했고 파일 이름은 default_product.php입니다. The anatomy of smart search in Joomla art Creating a plugin I.
  • $table - 데이터를 가져오기 위해 액세스하는 데이터베이스의 테이블 이름입니다(예: #__content). 제 경우에는 JoomShopping 상품이 담긴 메인 테이블이 #__jshopping_products 입니다.
  • $state_field - 색인된 요소의 게시 여부를 담당하는 데이터베이스 테이블의 필드 이름입니다. 기본적으로 이 필드를 상태라고 합니다. 하지만 JoomShopping의 경우 이 필드를 product_publish라고 합니다.
<?php

/**
 * @package     Joomla.Plugin
 * @subpackage  Finder.Wtjoomshoppingfinder
 *
 * @copyright   (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

\defined('_JEXEC') or die;

use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Database\DatabaseInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Joomla\Plugin\Finder\Wtjoomshoppingfinder\Extension\Wtjoomshoppingfinder;

return new class () implements ServiceProviderInterface {
    /**
     * Registers the service provider with a DI container.
     *
     * @param   Container  $container  The DI container.
     *
     * @return  void
     *
     * @since   4.3.0
     */
    public function register(Container $container)
    {
        $container->set(
            PluginInterface::class,
            function (Container $container) {
                $plugin     = new Wtjoomshoppingfinder(
                    $container->get(DispatcherInterface::class),
                    (array) PluginHelper::getPlugin('finder', 'wtjoomshoppingfinder')
                );
                $plugin->setApplication(Factory::getApplication());

                // Our plugin uses DatabaseTrait, so the setDatabase() method appeared 
                // If it is not present, then we use only setApplication().
                $plugin->setDatabase($container->get(DatabaseInterface::class));

                return $plugin;
            }
        );
    }
};
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

클래스에 필요한 최소 메소드

  • setup() : bool - 플러그인 사전 구성, 라이브러리 연결 등을 위한 메소드입니다. 이 메소드는 onBeforeIndex 이벤트에서 재인덱싱(reindex() 메소드) 중에 호출됩니다. 메소드는 true를 반환해야 합니다. 그렇지 않으면 색인 생성이 중단됩니다.
  • index() : void - 인덱싱 자체를 시작하는 방법입니다. 원시 SQL 쿼리 데이터에서 원하는 구조의 개체를 수집한 다음 인덱싱을 위해 JoomlaComponentFinderAdministratorIndexerIndexer 클래스에 전달됩니다. 메소드는 색인화된 각 요소에 대해 실행됩니다. 메소드 인수는 $item입니다. 데이터베이스에 대한 쿼리 결과이며 JoomlaComponentFinderAdministratorIndexerResult 클래스로 형식이 지정됩니다.
  • getListQuery() : JoomlaDatabaseDatabaseQuery - 색인된 항목 목록을 가져오는 메서드입니다…

... 문서와 대부분의 기사에서 이에 대해 이야기하고 있음에도 불구하고 getListQuery() 메서드는 실제로 필수는 아니기 때문에 여기서 세부 사항을 살펴보기 시작합니다.

The anatomy of smart search in Joomla art Creating a plugin I.
"복잡한 계획" 주제에 관한 모든 사진이 여기에 적합합니다.

세부 사항을 자세히 살펴보세요. 색인화된 요소의 데이터 구조입니다.

어떤 정보나 아이디어가 때때로 우리가 인지하고 깨닫기도 전에 원을 그리며 스쳐가는 경우가 얼마나 많은지 놀랍습니다! 1년이 넘도록 우리 눈앞에 있는 많은 것들이 여전히 인식되지 못하고, 수년간의 경험을 거쳐야 우리의 관심이 집중됩니다.

Joomla와 관련하여 어떤 이유로든 Joomla의 구성 요소가 Joomla의 일종의 공통 아키텍처 특성을 가정한다는 비전이 즉시 나타나지 않습니다(이것은 분명한 사실이지만). 데이터베이스 테이블 구조 수준에 포함됩니다. Joomla 콘텐츠 테이블의 일부 필드를 살펴보겠습니다. 특정 열 이름은 우리에게 그다지 중요하지 않으며(항상 SELECT 이름을 제목으로 쿼리할 수 있음), 하나의 색인화된 요소에 대한 데이터 구조는 얼마인지 예약하겠습니다.

  • id - 자동 증가
  • 자산 ID - 기사, 제품, 메뉴, 모듈, 플러그인 및 기타 모든 항목과 같은 사이트의 각 요소에 대해 그룹 및 사용자의 액세스 권한이 저장되는 #__assets 테이블 항목의 ID입니다. Joomla는 ACL(액세스 제어 목록) 패턴을 사용합니다.
  • title - 요소 제목
  • 언어 - 요소 언어
  • 소개 텍스트 - 요소에 대한 소개 텍스트 또는 간략한 시각적 설명
  • fulltext - 상품의 전체 텍스트, 제품에 대한 전체 설명 등
  • state - 게시 상태를 담당하는 논리 플래그: 항목이 게시되었는지 여부.
  • catid - 항목 카테고리의 ID입니다. Joomla에는 다른 CMS처럼 "사이트 페이지"만 있는 것이 아닙니다. 일부 카테고리에 속해야 하는 콘텐츠 개체(기사, 연락처, 제품 등)가 있습니다.
  • 생성- 항목이 생성된 날짜입니다.
  • 접속 - 접속권한 그룹ID (비인가 사이트 이용자(게스트), 전체, 등록 등)
  • Metakey - 요소의 메타 키워드입니다. 예, 2009년부터는 Google에서 사용하지 않습니다. 그러나 Joomla에서는 지정된 키워드를 사용하여 실제로 유사한 기사를 검색하기 위해 유사한 기사 모듈에서 이 필드가 사용되기 때문에 역사적으로 남아 있습니다.
  • Metadesc - 요소 메타 설명
  • 게시_업 및 게시_다운 - 요소 게시 및 게시 취소 시작 날짜입니다. 이는 옵션에 가깝지만 많은 구성 요소에서 발견됩니다.

#__content(Joomla 기사), #__contact_details(연락처 구성 요소), #__tags(Joomla 태그), #__categories(Joomla 카테고리 구성 요소) 테이블을 비교하면 나열된 데이터 유형을 거의 모든 곳에서 찾을 수 있습니다.

스마트 검색 플러그인이 생성되는 구성 요소가 "Joomla 방식"을 따르고 해당 아키텍처를 상속하는 경우 플러그인 클래스에서 최소한의 메소드로 작업할 수 있습니다. 개발자가 쉬운 길을 찾지 않고 자신의 길을 가기로 결정했다면 Adapter 클래스의 거의 모든 메서드를 재정의하는 어려운 길을 가야 할 것입니다.

getListQuery() 메소드

이 메서드는 3가지 경우에 호출됩니다.

  1. Adapter 클래스의 getContentCount() 메서드는 색인이 생성된 항목 수(총 기사 수, 총 제품 수 등)를 가져오는 것입니다. The anatomy of smart search in Joomla art Creating a plugin I. Joomla 스마트 검색 색인 생성 프로세스 디버그 모드에서 인덱싱된 항목 수를 확인할 수 있습니다.
  2. Adapter 클래스의 getItem($id) 메소드는 해당 ID로 특정 색인 요소를 가져오는 것입니다. getItem() 메서드는 다시 색인을 생성하는 동안 reindex($id) 메서드에서 호출됩니다.
  3. Adapter 클래스의 getItems($offset, $limit, $query = null) 메소드는 색인된 요소의 목록을 가져오는 메소드입니다. 오프셋과 제한은 구성 요소 설정("번들"에 포함되어야 하는 색인 ​​요소 수)에 따라 설정됩니다. The anatomy of smart search in Joomla art Creating a plugin I. Joomla 5 스마트 검색 설정 인덱서 배치 크기

Joomla 핵심 플러그인의 구현 예를 살펴보겠습니다.

<?php

/**
 * @package     Joomla.Plugin
 * @subpackage  Finder.Wtjoomshoppingfinder
 *
 * @copyright   (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

\defined('_JEXEC') or die;

use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Database\DatabaseInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Joomla\Plugin\Finder\Wtjoomshoppingfinder\Extension\Wtjoomshoppingfinder;

return new class () implements ServiceProviderInterface {
    /**
     * Registers the service provider with a DI container.
     *
     * @param   Container  $container  The DI container.
     *
     * @return  void
     *
     * @since   4.3.0
     */
    public function register(Container $container)
    {
        $container->set(
            PluginInterface::class,
            function (Container $container) {
                $plugin     = new Wtjoomshoppingfinder(
                    $container->get(DispatcherInterface::class),
                    (array) PluginHelper::getPlugin('finder', 'wtjoomshoppingfinder')
                );
                $plugin->setApplication(Factory::getApplication());

                // Our plugin uses DatabaseTrait, so the setDatabase() method appeared 
                // If it is not present, then we use only setApplication().
                $plugin->setDatabase($container->get(DatabaseInterface::class));

                return $plugin;
            }
        );
    }
};
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

getListQuery() 메서드는 쿼리 생성자의 개체인 DatabaseQuery 개체를 반환합니다. 여기서 테이블 이름과 선택 필드가 이미 지정되어 있습니다. 이를 호출하는 메소드에서 작업이 계속됩니다.

DatabaseQuery $query 객체의 getContentCount()에서 getListQuery()를 호출하면 select에 설정된 값이 COUNT(*)로 대체됩니다.

getItem($id)에서 getListQuery()를 호출하면 $query->where('a.id = ' . (int) $id) 조건이 적용되고 특정 요소만 선택됩니다. 그리고 이미 여기서는 상위 Adapter 클래스에 쿼리의 테이블 이름이 a.*로 포함되어 있음을 알 수 있습니다. 이는 getListQuery() 메소드 구현에서도 이러한 접두어를 사용해야 함을 의미합니다.

getItems()에서 getListQuery()를 호출하는 경우 인덱싱할 요소 목록을 이동하기 위해 구성한 쿼리에 $offset 및 $limit가 추가됩니다.
요약: getListQuery() - 세 가지 다른 SQL 쿼리에 대한 "작업 부분"을 포함해야 합니다. 여기서 Joomla를 구현하는 데 특별히 어려운 점은 없습니다. 하지만 필요한 경우 getListQuery()를 생성하지 않고 3가지 메소드를 직접 구현할 수도 있습니다.

비 줌라 방식: JoomShopping의 경우 제품이 여러 카테고리를 가질 수 있으며 역사적으로 제품의 카테고리 ID(catid) 구성 요소가 별도의 테이블에 저장되어 있다는 사실을 발견했습니다. 동시에, 수년 동안 제품의 주요 카테고리를 지정하는 것은 불가능했습니다. 제품 카테고리를 수신하면 카테고리가 포함된 테이블로 쿼리가 전송되었습니다. 여기서 첫 번째 쿼리 결과만 가져와서 기본 카테고리 ID를 기준으로 정렬했습니다(예: 오름차순). 상품을 편집할 때 카테고리를 변경했다면, 주요 상품 카테고리는 ID 번호가 낮은 카테고리였습니다. 이를 기반으로 제품의 URL이 생성되었으며 제품이 한 카테고리에서 다른 카테고리로 이동할 수 있습니다.

하지만 거의 2년 전에 이 JoomShopping 동작이 수정되었습니다. 이 구성 요소는 오랜 역사를 갖고 있고 사용자가 많으며 이전 버전과의 호환성을 깨뜨릴 수 없기 때문에 수정 사항은 선택 사항으로 설정되었습니다. 제품의 기본 카테고리를 지정하는 기능은 구성 요소 설정에서 활성화되어야 합니다. 그러면 테이블에 main_category_id가 제품으로 채워집니다.

그러나 이 기능은 기본적으로 비활성화되어 있습니다. 그리고 스마트 검색 플러그인에서 JoomShopping 구성 요소의 매개변수를 가져와서 기본 제품 카테고리를 지정하는 옵션이 활성화되어 있는지 확인해야 합니다. 최근에 활성화되었을 수 있으며 일부 제품의 기본 카테고리가 지정되지 않았습니다. 미묘한 차이도 있습니다...) 구성요소 매개변수를 기반으로 제품을 수신하는 SQL 쿼리를 생성합니다. main_category_id 필드 또는 이전의 잘못된 방식으로 카테고리 ID를 가져오기 위한 JOIN 요청입니다.

이 요청에는 다국어 사용의 뉘앙스가 즉시 부각됩니다. Joomla 방식에 따르면 사이트의 각 언어에 대해 별도의 요소가 생성되고 이들 사이에 연결이 설정됩니다. 따라서 러시아어의 경우 하나의 기사입니다. 같은 영문 기사가 별도로 작성되고 있습니다. 그런 다음 언어 연결을 사용하여 서로 연결하고 Joomla 프런트엔드에서 언어를 전환하면 한 기사에서 다른 기사로 리디렉션됩니다.

JoomShopping에서는 이것이 수행되는 방식이 아닙니다. 모든 언어의 데이터는 제품과 함께 동일한 테이블에 저장됩니다(Ok). 다른 언어에 대한 데이터 추가는 해당 언어의 접미사(흠...)가 포함된 열을 추가하여 수행됩니다. 즉, 데이터베이스에는 직함이나 이름 필드만 있는 것이 아닙니다. 하지만 name_ru-RU, name_en-GB 등의 필드가 있습니다.
The anatomy of smart search in Joomla art Creating a plugin I.
Joomla JoomShopping 제품 테이블 구조 조각
동시에 관리 패널과 CLI 모두에서 인덱싱할 수 있도록 범용 SQL 쿼리를 설계해야 합니다. 동시에 CRON을 사용하여 CLI를 시작할 때 인덱싱 언어를 선택하는 것도 작업입니다. 나는 이 글을 쓰는 시점에서 이 문제에 대한 본격적인 해결책을 당분간 미루어 왔다는 것을 인정합니다. 언어는 JoomShopping 매개변수에서 기본 언어를 가져오거나 사이트의 기본 언어를 사용하는 자체 getLangTag() 메소드를 사용하여 선택됩니다. 즉, 지금까지 이 솔루션은 단일 언어 사이트에만 해당됩니다. 다른 언어로의 검색은 아직 작동하지 않습니다.

그러나 3개월 후에 이 문제를 해결했습니다. 그러나 이미 SW JProjects 구성 요소용 스마트 검색 플러그인에 있습니다. 해결 방법에 대해 자세히 알려드리겠습니다.

그동안 JoomShopping에 무슨 일이 일어났는지 살펴보겠습니다

<?php

/**
 * @package     Joomla.Plugin
 * @subpackage  Finder.Wtjoomshoppingfinder
 *
 * @copyright   (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

\defined('_JEXEC') or die;

use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Database\DatabaseInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Joomla\Plugin\Finder\Wtjoomshoppingfinder\Extension\Wtjoomshoppingfinder;

return new class () implements ServiceProviderInterface {
    /**
     * Registers the service provider with a DI container.
     *
     * @param   Container  $container  The DI container.
     *
     * @return  void
     *
     * @since   4.3.0
     */
    public function register(Container $container)
    {
        $container->set(
            PluginInterface::class,
            function (Container $container) {
                $plugin     = new Wtjoomshoppingfinder(
                    $container->get(DispatcherInterface::class),
                    (array) PluginHelper::getPlugin('finder', 'wtjoomshoppingfinder')
                );
                $plugin->setApplication(Factory::getApplication());

                // Our plugin uses DatabaseTrait, so the setDatabase() method appeared 
                // If it is not present, then we use only setApplication().
                $plugin->setDatabase($container->get(DatabaseInterface::class));

                return $plugin;
            }
        );
    }
};
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

체크포인트

Joomla에서 데이터베이스를 쿼리하는 방법을 만들었고 스마트 검색 플러그인의 작동 방식에 대해 많은 것을 배웠습니다.

다음 글에서는 콘텐츠 색인 생성 방법을 만들고 플러그인 생성을 완료하겠습니다. 또한 인덱싱된 항목이 데이터베이스에 저장되는 방식에 대해 알아보고 이것이 왜 중요한지 이해하고 다국어의 비표준 구현을 통해 다국어 구성 요소의 콘텐츠 인덱싱 문제를 해결합니다.

Joomla 커뮤니티 리소스

  • https://joomla.org/
  • Joomla 커뮤니티 매거진에 실린 이 기사
  • Mattermost의 Joomla 커뮤니티 채팅(자세히 보기)

위 내용은 Joomla 아트의 스마트 검색 분석 플러그인 만들기 I.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:dev.to
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
저자별 최신 기사
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿