Cara Mengesampingkan Blok Inti, Model, dan pengontrol di Magento2


49

Saya terjebak dalam mengesampingkan Blok model inti dan pengontrol di Magento2. Adakah yang bisa membantu dalam hal ini?

Mari kita ambil toolbar daftar sebagai contoh di mana saya perlu menambahkan opsi urutkan baru yang disebut urutkan berdasarkan yang paling populer . Bagaimana saya menambahkannya? Saya kira untuk ini kita perlu menambahkan opsi di tingkat blok dan kondisi di List.phptingkat pengumpulan.


1
Mengesampingkan kelas inti adalah ide yang buruk, dan dapat dilakukan dengan berbagai cara. Bisakah Anda menggambarkan kasus spesifik Anda?
KAndy

@KAndy: - mari kita ambil contoh toolbar daftar di mana saya perlu menambahkan opsi sortir baru yang disebut sort by paling populer lalu bagaimana menambahkannya saya berharap untuk ini kita perlu menambahkan opsi di level blok dan kondisi di level pengumpulan List.php
Pradeep Kumar

Anda sebenarnya perlu menggunakan setelah Execute Plugin pada \ Magento \ Catalog \ Block \ Product \ ProductList \ Toolbar :: getAvailableOrders untuk ini. Jika ada yang akan menggunakan plugin, pelanggan mendapat semua pesanan. dalam hal menggunakan penulisan ulang, Anda mendapat konflik modul dan satu modul tidak akan berfungsi
KAndy

@KAndy: - tolong beri contoh kode saya tidak mendapatkan plugin saya perlu di.xml dan plugin kode php cara kerjanya dan juga cara menambahkan kolom baru untuk admin grid menggunakan plugin ex order grid tolong bantu saya dalam plugin ex code
Pradeep Kumar

@Kandy: - silakan bagikan contoh kode plugin apa pun dalam model produk, tambahkan teks dengan getname produk ()
Pradeep Kumar

Jawaban:


30

Magento2 memberikan konsep yang sangat bagus yang disebut Plugin

kita dapat melakukan apa yang pernah dan sebelum fungsi inti dan juga kita memiliki satu lagi dipanggil yang akan melakukan keduanya sebelum dan sesudah di bawah ini adalah kode yang akan mencakup semua info

Buat file di.xml di Mymodule / etc / di.xml

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
   <type name="Magento\Catalog\Block\Product\View">
        <plugin name="inroduct-custom-module" type="Sugarcode\Test\Block\Plugin\Product\View" sortOrder="1"/>
    </type>
    <type name="Magento\Catalog\Model\Product">
        <plugin name="getname-test-module" type="Sugarcode\Test\Model\Plugin\Product" sortOrder="10"/>
    </type>
</config>

dalam hal ini saya mengambil contoh Model produk dan Blok Tampilan Produk

Saya menggunakan sekitar di blok Product View yang merupakan fungsi menggunakan awalan sekitar dan kemudian pastikan 2 parameter harus ada yang pertama adalah objek yang Anda gunakan 2 satu Penutupan yang mempertahankan info pengembalian lama

<?php
namespace Sugarcode\Test\Block\Plugin\Product;

class View 
{ 
    public function aroundGetProduct(\Magento\Catalog\Block\Product\View $subject, \Closure $proceed)
    {

        echo 'Do Some Logic Before <br>';
        $returnValue = $proceed(); // it get you old function return value
        //$name='#'.$returnValue->getName().'#';
        //$returnValue->setName($name);
        echo 'Do Some Logic  After <br>';
        return $returnValue; // if its object make sure it return same object which you addition data
    }


}

Dalam model i Digunakan sebelum dan sesudahnya

<?php
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Sugarcode\Test\Model\Plugin;

class Product
{        
    public function beforeSetName(\Magento\Catalog\Model\Product $subject, $name)
    {
        return array('(' . $name . ')');
    }

     public function afterGetName(\Magento\Catalog\Model\Product $subject, $result)
    {
        return '|' . $result . '|';
    }

}

dengan cara ini kita dapat menyimpan kode lama jadi jika besok kode inti Magento diperbarui, kita akan memiliki kode baru yang diperbarui dan logika kustom kita jika kita langsung mengganti maka kita kehilangan kode baru yang diperbarui dari fungsi atau file itu :-)

http://devdocs.magento.com/guides/v2.0/extension-dev-guide/plugins.html


Bagaimana jika Anda ingin menambahkan metode baru ke kelas? Selain preferensi pilihan apa yang kita miliki?
MagePsycho

@MagePsycho: - jika Anda memiliki sesuatu fungsi baru itu berarti keluar dari Magento. jika bloknya membuat blok baru dan memperluasnya dari inti tetapi tanpa preferensi. jika beberapa model kemudian menulis urutan saya harap tidak ada cara lain
Pradeep Kumar

19

Akhirnya saya mendapatkannya !!!!
Saya mengikuti langkah-langkah di bawah ini untuk mengesampingkan Blok, Pengendali dan Model. Saya mengambil Exmaple Model Produk dan Blok Tampilan Produk dan Melihat Pengendali / Tindakan

Buat file bernama di.xml di /etc/di.xml Anda

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
    <preference for="Magento\Catalog\Model\Product" type="Sugarcode\Test\Model\Product" />
    <preference for="Magento\Catalog\Block\Product\View" type="Sugarcode\Test\Block\Product\View" />
    <preference for="Magento\Catalog\Controller\Product\View" type="Sugarcode\Test\Controller\Product\View" />
</config>

Kemudian saya membuat File Model di /Model/Product.php

<?php
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Sugarcode\Test\Model;

class Product extends \Magento\Catalog\Model\Product
{
    /**
     * Get product name
     *
     * @return string
     * @codeCoverageIgnoreStart
     */
    public function getName()
    {
        return $this->_getData(self::NAME).'Local';
    }    
}

Kemudian saya membuat Block file di /Block/Product/View.php

<?php
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Sugarcode\Test\Block\Product;
/**
 * Product View block
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
 */
class View extends \Magento\Catalog\Block\Product\View
{


    /**
     * Retrieve current product model
     *
     * @return \Magento\Catalog\Model\Product
     */
    public function getProduct()
    {
       echo 'Local Block';
       if (!$this->_coreRegistry->registry('product') && $this->getProductId()) {
            $product = $this->productRepository->getById($this->getProductId());
            $this->_coreRegistry->register('product', $product);
        }
        return $this->_coreRegistry->registry('product');
    }


}

Sekarang Buat Pengontrol Tampilan Produk / Controll/Product/View.php

<?php
/**
 *
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Sugarcode\Test\Controller\Product;

class View extends \Magento\Catalog\Controller\Product\View
{

    /**
     * Product view action
     *
     * @return \Magento\Framework\Controller\Result\Forward|\Magento\Framework\Controller\Result\Redirect
     */
    public function execute()
    {
        // Get initial data from request
       echo 'I Am in Local Controller';
       $categoryId = (int) $this->getRequest()->getParam('category', false);
        $productId = (int) $this->getRequest()->getParam('id');
        $specifyOptions = $this->getRequest()->getParam('options');

        if ($this->getRequest()->isPost() && $this->getRequest()->getParam(self::PARAM_NAME_URL_ENCODED)) {
            $product = $this->_initProduct();
            if (!$product) {
                return $this->noProductRedirect();
            }
            if ($specifyOptions) {
                $notice = $product->getTypeInstance()->getSpecifyOptionMessage();
                $this->messageManager->addNotice($notice);
            }
            if ($this->getRequest()->isAjax()) {
                $this->getResponse()->representJson(
                    $this->_objectManager->get('Magento\Framework\Json\Helper\Data')->jsonEncode([
                        'backUrl' => $this->_redirect->getRedirectUrl()
                    ])
                );
                return;
            }
            $resultRedirect = $this->resultRedirectFactory->create();
            $resultRedirect->setRefererOrBaseUrl();
            return $resultRedirect;
        }

        // Prepare helper and params
        $params = new \Magento\Framework\Object();
        $params->setCategoryId($categoryId);
        $params->setSpecifyOptions($specifyOptions);

        // Render page
        try {
            $page = $this->resultPageFactory->create(false, ['isIsolated' => true]);
            $this->viewHelper->prepareAndRender($page, $productId, $this, $params);
            return $page;
        } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
            return $this->noProductRedirect();
        } catch (\Exception $e) {
            $this->_objectManager->get('Psr\Log\LoggerInterface')->critical($e);
            $resultForward = $this->resultForwardFactory->create();
            $resultForward->forward('noroute');
            return $resultForward;
        }
    }
}

Ini berfungsi dengan baik untuk saya :-)


6

Ada dua langkah untuk mengganti file Block, Model And Controller

1) Tambahkan preferensi dalam di.xml

2) Buat blok, model, dan file pengontrol di modul Anda

Namespace: Prince

Nama Modul: Helloworld

Misalnya untuk mengganti blok katalog produk ListProduct

1) Buat file di.xml di FolderPrince/Helloworld/etc

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
 <preference for="Magento\Catalog\Model\Product" type="Prince\Helloworld\Model\Rewrite\Catalog\Product" />
</config>

2) Buat ListProduct.php di FolderPrince/Helloworld/Block/Rewrite/Product

<?php
    namespace Prince\Helloworld\Block\Rewrite\Product;

    class ListProduct extends \Magento\Catalog\Block\Product\ListProduct
    {
        public function _getProductCollection()
        {
            // Do your code here
        }
    }

Misalnya untuk mengganti model produk katalog.

1) Tambahkan preferensi dalam di.xml diPrince/Helloworld/etc

<preference for="Magento\Catalog\Model\Product" type="Prince\Helloworld\Model\Rewrite\Catalog\Product" /> 

2) Buat file Model Product.php di Folder Prince/Helloworld/Model/Rewrite/Catalog

<?php
namespace Prince\Helloworld\Model\Rewrite\Catalog;

class Product extends \Magento\Catalog\Model\Product
{
    public function isSalable()
    {
        // Do your code here

        return parent::isSalable();
    }

}

Overriding Controller

1) Tambahkan preferensi dalam di.xml diPrince/Helloworld/etc

<preference for="Magento\Catalog\Controller\Product\View" type="Prince\Helloworld\Controller\Rewrite\Product\View" />

2) Buat View.php di FolderPrince/Helloworld/Controller/Rewrite/Product

class View extends \Magento\Catalog\Controller\Product\View
{
    public function execute()
    {
        // Do your stuff here
        return parent::execute();
    }
}

Anda bisa mengganti blok, model, dan pengontrol lainnya menggunakan pendekatan yang sama.


Apakah kita perlu menambahkan penulisan ulang setelah Controller, Model & Block? Bagi saya tanpa menambahkan penulisan ulang juga berhasil.
sagar sapkota

@sagarsapkota Ya Anda dapat menggunakan Controller, Model & Block secara langsung tanpa menulis ulang folder.
Pangeran Patel

4

Koreksi kecil tapi bermanfaat besar, kita tidak perlu membuat n jumlah file untuk setiap fungsi dalam konsep plugin. Untuk satu modul satu file plugin sudah cukup karena Anda dapat memperluas semua modul, semua model dan blok dan pengontrol Magento lengkap itu sendiri periksa di bawah kode

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">

    <type name="Magento\Catalog\Block\Product\View">
        <plugin name="inroduct-custom-module" type="Sugarcode\Test\Model\Plugin\Product" sortOrder="1"/>
    </type>
    <type name="Magento\Catalog\Model\Product">
        <plugin name="getname-test-module" type="Sugarcode\Test\Model\Plugin\Product" sortOrder="10"/>
    </type>
    <type name="Magento\Catalog\Controller\Product\View">
        <plugin name="product-cont-test-module" type="Sugarcode\Test\Model\Plugin\Product" sortOrder="10"/>
    </type>
</config>

dan dalam file plugin php

<?php
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Sugarcode\Test\Model\Plugin;

class Product
{        
    public function beforeSetName(\Magento\Catalog\Model\Product $subject, $name)
    {
        return array('(' . $name . ')');
    }

     public function afterGetName(\Magento\Catalog\Model\Product $subject, $result)
    {
        return '|' . $result . '|';
    } 
    public function aroundGetProduct(\Magento\Catalog\Block\Product\View $subject, \Closure $proceed)
    {

        echo 'Do Some Logic Before <br>';
        $returnValue = $proceed(); // it get you old function return value
        $name='#'.$returnValue->getName().'#';
        $returnValue->setName($name);
        echo 'Do Some Logic  After <br>';
        return $returnValue;// if its object make sure it return same object which you addition data
    }
    public function aroundExecute(\Magento\Catalog\Controller\Product\View $subject, \Closure $proceed)
    {
        echo 'I Am in Local Controller Before <br>';
        $returnValue = $proceed(); // it get you old function return value
        //$name='#'.$returnValue->getName().'#';
        //$returnValue->setName($name);
        echo 'I Am in Local Controller  After <br>';
        return $returnValue;// if its object make sure it return same object which you addition data
    }
}

Magento2 Rocks


Hai Pradeep - Anda telah memposting tiga jawaban untuk pertanyaan ini - mungkin Anda layak menggabungkannya menjadi satu jawaban
Robbie Averill

Saya mencoba dengan jawaban ini, Ini menunjukkan kesalahan di Uncaught Error: Call to undefined method Magento\\Backend\\Model\\View\\Result\\Redirect\\Interceptor::getEntityId()sini \Clousure $proceedmendapatkan obejct dariMagento\\Backend\\Model\\View\\Result\\Redirect\\Interceptor
Praful Rajput

3

Anda dapat langsung memperluas blok atau kelas pengontrol magento di blok atau pengontrol khusus Anda. Misalnya, dalam memperluas model faktur PDF dalam modul khusus saya untuk mengubah logo pembuatan faktur PDF dengan cara yang sama Anda dapat mengganti blok atau pengontrol. Anda tidak dapat untuk membuat file di.xml dan tidak perlu mengatur preferensi.

class Invoice extends \Magento\Sales\Model\Order\Pdf\Invoice
{


    /**
     * Return PDF document
     *
     * @param array|Collection $invoices
     * @return \Zend_Pdf
     */
    public function getPdf($invoices = [])
    {

        $this->_beforeGetPdf();
        $this->_initRenderer('invoice');

        $pdf = new \Zend_Pdf();
        $this->_setPdf($pdf);
        $style = new \Zend_Pdf_Style();
        $this->_setFontBold($style, 10);

        foreach ($invoices as $invoice) {
            if ($invoice->getStoreId()) {
                $this->_localeResolver->emulate($invoice->getStoreId());
                $this->_storeManager->setCurrentStore($invoice->getStoreId());
            }
            $page = $this->newPage();
            $order = $invoice->getOrder();
            /* Add image */
            $this->insertCustomLogo($page, $invoice->getStore());
            /* Add address */
            $this->insertCustomAddress($page, $invoice->getStore());
            /* Add head */

            $this->insertOrder(
                $page,
                $order,
                $this->_scopeConfig->isSetFlag(
                    self::XML_PATH_SALES_PDF_INVOICE_PUT_ORDER_ID,
                    \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
                    $order->getStoreId()

                )
            );

            /* Add document text and number */
            $this->insertDocumentNumber($page, __('Invoice # ') . $invoice->getIncrementId());
            /* Add table */

            $this->_drawHeader($page);
            /* Add body */

            foreach ($invoice->getAllItems() as $item) {
                if ($item->getOrderItem()->getParentItem()) {
                    continue;
                }

                /* Draw item */
                $this->_drawItem($item, $page, $order);

                $page = end($pdf->pages);
            }

            /* Add totals */
            $this->insertTotals($page, $invoice);
            if ($invoice->getStoreId()) {
                $this->_localeResolver->revert();
            }
        }

        $this->_afterGetPdf();
        return $pdf;
    } 

   protected function insertCustomLogo(&$page, $store = null)
   {

     $image='demo.png'

     if ($image) {
        $imagePath = '/logo/' . $image;
        if ($this->_mediaDirectory->isFile($imagePath)) {
            $image = \Zend_Pdf_Image::imageWithPath($this->_mediaDirectory->getAbsolutePath($imagePath));
            $top = 830;
            //top border of the page
            $widthLimit = 270;
            //half of the page width
            $heightLimit = 270;
            //assuming the image is not a "skyscraper"
            $width = $image->getPixelWidth();
            $height = $image->getPixelHeight();

            //preserving aspect ratio (proportions)
            $ratio = $width / $height;
            if ($ratio > 1 && $width > $widthLimit) {
                $width = $widthLimit;
                $height = $width / $ratio;
            } elseif ($ratio < 1 && $height > $heightLimit) {
                $height = $heightLimit;
                $width = $height * $ratio;
            } elseif ($ratio == 1 && $height > $heightLimit) {
                $height = $heightLimit;
                $width = $widthLimit;
            }

            $y1 = $top - $height;
            $y2 = $top;
            $x1 = 25;
            $x2 = $x1 + $width;

            //coordinates after transformation are rounded by Zend
            $page->drawImage($image, $x1, $y1, $x2, $y2);

            $this->y = $y1 - 10;
        }
    }
}

}


Apakah ini benar-benar cara untuk masuk dalam M2?
Maks

Trik di Magento 2 adalah menentukan preferensi di di.xml. Saya kehilangan bagian yang cukup penting ini dalam jawaban Anda ...
7ochem

3
  • Pengembang / Helloworld / registrasi.php

    
    \Magento\Framework\Component\ComponentRegistrar::register(
        \Magento\Framework\Component\ComponentRegistrar::MODULE,
        'Developer_Helloworld',
        __DIR__
    );
  • Pengembang / Helloworld / etc / module.xml

    <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
        <module name="Developer_Helloworld" setup_version="1.0.0">
        </module>
    </config>

  • Pengembang / Helloworld / etc / di.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">    
    <preference for="Magento\Catalog\Controller\Product\View" type="Developer\Helloworld\Controller\Catalog\Product\View" />
</config>

  • Pengembang / Helloworld / Pengendali / Katalog / Produk / View.php

    namespace Developer\Helloworld\Controller\Catalog\Product;
    class View extends \Magento\Catalog\Controller\Product\View
    {
        public function execute(){
            echo '__TEST__';exit;
        }
    }
semoga ini membantu

2

Kelas tindakan dapat ditulis ulang dengan cara yang sama seperti di Magento 1. Di Magento 1 kami memiliki beforeatribut di sekitar tag<routers>..<args><modules><... before="Mage_Catalog">Namespace_MyModule ..

Di [module path]/etc/[nothing|adminhtml|frontend]/routes.xml:

<config>
    <router id="[admin|standard|]">
        <route id="catalog" frontName="catalog">
            <module name="Namespace_MyModule" before="Magento_Catalog"/>
        </route>
    </router>
</config>

Dan kelas aksi di \Namespace\MyModule\Controller\[same path of action as in core module]\SameActionName.phpmanaclass SameActionName.php extends \Magento\Catalog\...\SameActionName

Ini adalah modul Magento_Catalog, file yang Magento\Catalog\etc\adminhtml\routes.xmlmendaftarkan rute baru di admin:

<router id="admin">
    <route id="catalog" frontName="catalog">
        <module name="Magento_Catalog" before="Magento_Backend" />
    </route>
</router>

http://devdocs.magento.com/guides/v2.1/extension-dev-guide/routing.html

Untuk mengganti tindakan pengontrol di rute dengan yang kustom, tambahkan kelas pengontrol khusus sebelum pengontrol asli.

Kontroler dan tindakan khusus harus berbagi nama yang sama dengan yang asli.

Sistem memproses pengontrol kustom sebelum yang asli, sementara rute tetap sama.

Jika Anda harus mengatur ulang rute dan desain, teruskan pemrosesan permintaan ke rute lain:

$this->_forward('other/controller/action')

Untuk menghapus aksi controller, teruskan ke noroute, misalnya, di app / code / Company / SomeExtension / Controller / Account.php:

Saya tidak percaya preferensi atau plugin pada kelas Action adalah ide yang bagus oleh praktik terbaik Magento. Dan mungkin ada lebih dari itu.


0

Untuk mengganti kelas secara langsung, Anda harus menggunakan preferensi. Lihat lebih lanjut tentang dev docs: https://devdocs.magento.com/guides/v2.0/extension-dev-guide/build/di-xml-file.html#abstraction-implementation-mappings
Untuk sebagian besar waktu kami menggunakan Interceptor (plugin) karena ini adalah praktik terbaik untuk menulis ulang atau menambahkan bagian dari modifikasi Anda. Lihat dev docs: https://devdocs.magento.com/guides/v2.0/extension-dev-guide/plugins.html

Dengan menjaga contoh pengurutan item daftar Anda dengan menambahkan pesanan sortir baru dengan menambahkan pesanan sortir baru 'Paling Populer' Saya memberikan Anda cara terbaik untuk memodifikasi hasilnya.
Buat modul khusus dan buat konfigurasi app/code/Arsal/SortOption/etc/module.xml:

<?xml version="1.0" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
   <module name="Arsal_SortOption" setup_version="1.0.0" />
</config> 

Sekarang Daftarkan modul Anda app/code/Arsal/SortOption/registration.php:

<?php
 \Magento\Framework\Component\ComponentRegistrar::register(
     \Magento\Framework\Component\ComponentRegistrar::MODULE,
     'Arsal_SortOption',
      __DIR__
 );

Sekarang buat di.xml app/code/Arsal/SortOption/etc/di.xml:

<?xml version="1.0" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Catalog\Block\Product\ProductList\Toolbar">
       <plugin name="toolbar_instance" type="Arsal\SortOption\Block\Product\ProductList\Toolbar" />
    </type>
</config>

Sekarang buat kelas blok Arsal\SortOption\Block\Product\ProductListToolbar.php:

<?php
namespace Arsal\SortOption\Block\Product\ProductList;

class Toolbar {

    public function afterGetAvailableOrders (
        \Magento\Catalog\Block\Product\ProductList\Toolbar $subject, $result
    ) {
        $result ['most_popular'] = 'most popular';
        return $result;
    }

Ini akan menambahkan opsi urutan pesanan khusus untuk mengurutkan daftar pesanan. masukkan deskripsi gambar di sini }

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.