ec-cube3のプラグインを作ってコンテストに応募する

  • 10
    いいね
  • 1
    コメント
この記事は最終更新日から1年以上が経過しています。

Hamee Advent Calenderの5日目です。

はじめに

ec-cubeの3が出て触りたかったので何かプラグインを作って、丁度開催中(2015/12/18 までプラグインを募集中)のEC-CUBE PLUGIN AWARD 3.0に出してみます。

何を作るか

といっても、初めてだし、時間もないので、簡単でニッチなプラグインにしようと思います。
プラグインで出来ることがある程度わかりたいので、ショップ側画面と管理画面を変更するような内容で考えることにします・・・数分後・・・

こちらのサイトでもお買い求め頂けます。プラグイン

決まりました。これを作ります。
本店サイトのページに来たお客様を本店以外のモールへ誘導する、逆サテライトサイトなプラグインです。お店からすれば本店からお客様を逃しなくないですが、お客様からすればよく利用するモールでも売っているならそっちでも買いたいんじゃないかな、って思って考えました。

作り方を知る

まずは、本家サイトからいろいろ辿っていきます。

プラグインの構成

ec-cube3plugin
{PluginName}
├───config.yml        //プラグイン基本情報
├───PluginManager.php //プラグインのインストール時、更新時などの処理
├───ServiceProvider
    ├───*.php         //config.ymlで定義したServiceProviderで呼び出されるファイル
├───event.yml         //利用するフックポイント
├───{EventName}.php   //フックポイントに介入する処理
├───Migration
    ├───Version{YYYYmmddHHiiss}.php //テーブル作成や削除
    Resource
    ├───doctrine
        ├───*.dcm.yml //テーブル定義
    Form              
    ├───Type
        ├───*.php   //独自拡張フォームの定義
    ├───Extension
        ├───*.php   //独自拡張フォームの定義

プラグインを作る

仕様書に沿って書いていきます。

プラグイン基本設定

設定ファイル{config.yml}を書きます。

yaml
name: こちらのサイトでもお買い求め頂けます。プラグイン
code: OtherSite
version: 0.0.1
service:
    - OtherSiteServiceProvider
orm.path:
    - /Resource/doctrine
migration.path:
    - /migration
event: OtherSiteEvent

プラグインマネージャ

プラグインのインストール時、更新時の処理{PluginManager.php}を書いていきます。

php
<?php

namespace Plugin\OtherSite;

use Eccube\Plugin\AbstractPluginManager;

class PluginManager extends AbstractPluginManager
{

    public function install($config, $app)
    {
        $this->migrationSchema($app, __DIR__ . '/Migration', $config['code']);
    }

    public function uninstall($config, $app)
    {
        $this->migrationSchema($app, __DIR__ . '/Migration', $config['code'], 0);
    }

    public function enable($config, $app)
    {
    }

    public function disable($config, $app)
    {
    }

    public function update($config, $app)
    {
    }
}

どこに処理を介入させるか

今回は商品詳細画面にモールへのリンクボタン、管理画面の商品登録・更新時にモールのURLの設定する処理を介入させる{event.yml}を記述します。

yaml
eccube.event.render.admin_product_product_new.before:
    - [addContentOnProductEdit, NORMAL]
    - [registerOtherSite, NORMAL]
eccube.event.render.admin_product_product_edit.before:
    - [addContentOnProductEdit, NORMAL]
    - [registerOtherSite, NORMAL]
eccube.event.render.product_detail.before:
    - [showOtherSite, NORMAL]

また、最初に書いちゃいましたが設定ファイル{config.yml}にeventの設定を追記します。

yaml
event: OtherSiteEvent

データベース

モールのURLを保存するテーブルを作成します。今回は楽天市場、Yahooショッピング、Amazon、DeNAショッピング、ポンパレモールのURLを保存するようにします。

まずは、
entity{Entity/OtherSite.php}

doctrineの設定{Resource/doctrine/Plugin.OtherSite.Entity.OtherSite.dcm.yml}を書きます。

php
<?php

namespace Plugin\OtherSite\Entity;

class OtherSite extends \Eccube\Entity\AbstractEntity
{
    private $product_id;
    private $rakuten_url;
    private $yahoo_url;
    private $amazon_url;
    private $dena_url;
    private $ponpare_url;
    private $Product;

    public function getRakutenUrl()
    {
        return $this->rakuten_url;
    }

    public function setRakutenUrl($rakuten)
    {
        $this->rakuten_url = $rakuten;
        return $this;
    }

    public function getYahooUrl()
    {
        return $this->yahoo_url;
    }

    public function setYahooUrl($yahoo)
    {
        $this->yahoo_url = $yahoo;
        return $this;
    }

    public function getAmazonUrl()
    {
        return $this->amazon_url;
    }

    public function setAmazonUrl($amazon)
    {
        $this->amazon_url = $amazon;
        return $this;
    }

    public function getDenaUrl()
    {
        return $this->dena_url;
    }

    public function setDenaUrl($dena)
    {
        $this->dena_url = $dena;
        return $this;
    }


    public function getPonpareUrl()
    {
        return $this->ponpare_url;
    }

    public function setPonpareUrl($ponpare)
    {
        $this->ponpare_url = $ponpare;
        return $this;
    }


    public function getProduct()
    {
        return $this->Product;
    }


    public function setProduct(\Eccube\Entity\Product $Product)
    {
        $this->Product = $Product;

        return $this;
    }

    public function getProductId()
    {
        return $this->product_id;
    }

    public function setProductId($productId)
    {
        $this->product_id = $productId;

        return $this;
    }

}
yaml
Plugin\OtherSite\Entity\OtherSite:
    type: entity
    table: plg_other_site
    repositoryClass: Plugin\OtherSite\Repository\OtherSiteRepository
    id:
        product_id:
            type: smallint
            nullable: false
            unsigned: false
            id: true
            generator:
                strategy: NONE
    fields:
        rakuten_url:
            type: string
            default: ""
        yahoo_url:
            type: string
            default: ""
        amazon_url:
            type: string
            default: ""
        dena_url:
            type: string
            default: ""
        ponpare_url:
            type: string
            default: ""
    OneToOne:
        Product:
            targetEntity: Eccube\Entity\Product
            joinColumn:
                name: product_id
                referencedColumnName: product_id
    lifecycleCallbacks: {  }

次にRepository{Repository/OtherSiteRepository.php}を書きます。
今回は使わないから空定義します。

php

<?php

namespace Plugin\OtherSite\Repository;

use Doctrine\ORM\EntityRepository;

class OtherSiteRepository extends EntityRepository
{
}

テーブルを管理するマイグレーションファイル{Migration/Version20151201000000.php}を書きます。

php

<?php
namespace DoctrineMigrations;

use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;

class Version20151201000000 extends AbstractMigration
{

    public function up(Schema $schema)
    {
        $this->createOtherSiteTable($schema);
    }

    public function down(Schema $schema)
    {
        $schema->dropTable('plg_other_site');
    }

    protected function createOtherSiteTable(Schema $schema)
    {
        $table = $schema->createTable("plg_other_site");
        $table->addColumn('product_id', 'integer');
        $option=array('length' => 255,'notnull' => false);
        $table->addColumn('rakuten_url', 'string', $option);
        $table->addColumn('yahoo_url', 'string', $option);
        $table->addColumn('amazon_url', 'string', $option);
        $table->addColumn('dena_url', 'string', $option);
        $table->addColumn('ponpare_url', 'string', $option);
        $table->setPrimaryKey(array('product_id'));
    }
}

フォームの拡張

管理画面の商品登録画面にモールのURL設定を追加する為に{Form/Extension/Admin/OtherSiteCollectionExtension.php}を記述します。

php
<?php

namespace Plugin\OtherSite\Form\Extension\Admin;

use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;

class OtherSiteCollectionExtension extends AbstractTypeExtension
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {

        $builder
            ->add('rakuten_url', 'text', array(
                'label' => '楽天市場URL',
                'mapped' => false,
            ))
            ->add('yahoo_url', 'text', array(
                'label' => 'YahooショッピングURL',
                'mapped' => false,
            ))
            ->add('amazon_url', 'text', array(
                'label' => 'AmazonURL',
                'mapped' => false,
            ))
            ->add('dena_url', 'text', array(
                'label' => 'DeNAショッピングURL',
                'mapped' => false,
            ))
            ->add('ponpare_url', 'text', array(
                'label' => 'ポンパレモールURL',
                'mapped' => false,
            ))
        ;
    }

    public function buildView(FormView $view, FormInterface $form, array $options)
    {
    }

    public function getExtendedType()
    {
        return 'admin_product';
    }

}

表示の変更

管理画面に追加するテンプレート{Resource/template/Admin/other_site.twig}と商品詳細画面に追加するテンプレート{Resource/template/Front/other_site.twig}をtwigマナー書きます。

管理画面

twig

</div>
<div class="box accordion">
<div class="box accordion form-horizontal">
    <div class="box-header toggle">
        <h3 class="box-title">
            その他のサイト
            <svg class="cb cb-angle-down icon_down"> <use xlink:href="#cb-angle-down" /></svg>
        </h3>
    </div><!-- /.box-header -->
    <div class="box-body accpanel">
        <div class="form-group">
            <label class="col-sm-3 control-label">{{ form.rakuten_url.vars.label }}</label>
            <div class="col-sm-8 col-lg-9">
                {{ form_widget(form.rakuten_url) }}
            </div>
        </div>
        <div class="form-group">
            <label class="col-sm-3 control-label">{{ form.yahoo_url.vars.label }}</label>
            <div class="col-sm-8 col-lg-9">
                {{ form_widget(form.yahoo_url) }}
            </div>
        </div>
        <div class="form-group">
            <label class="col-sm-3 control-label">{{ form.amazon_url.vars.label }}</label>
            <div class="col-sm-8 col-lg-9">
                {{ form_widget(form.amazon_url) }}
            </div>
        </div>
        <div class="form-group">
            <label class="col-sm-3 control-label">{{ form.dena_url.vars.label }}</label>
            <div class="col-sm-8 col-lg-9">
                {{ form_widget(form.dena_url) }}
            </div>
        </div>
        <div class="form-group">
            <label class="col-sm-3 control-label">{{ form.ponpare_url.vars.label }}</label>
            <div class="col-sm-8 col-lg-9">
                {{ form_widget(form.ponpare_url) }}
            </div>
        </div>
    </div>
</div>

商品詳細画面

php
<div class="other_site">
    <h4>こちらのサイトでもお買い求め頂けます。</h4>
    <ul class="row">
        {% if OtherSite.rakuten_url %}<li class="col-xs-6 col-sm-4"><a href="{{ OtherSite.rakuten_url }}" target="_blank" class="btn btn-default btn-sm btn-block">楽天市場</a></li>{% endif %}
        {% if OtherSite.amazon_url %}<li class="col-xs-6 col-sm-4"><a href="{{ OtherSite.amazon_url }}" target="_blank" class="btn btn-default btn-sm btn-block">Amazon</a></li>{% endif %}
        {% if OtherSite.yahoo_url %}<li class="col-xs-6 col-sm-4"><a href="{{ OtherSite.yahoo_url }}" target="_blank" class="btn btn-default btn-sm btn-block">Yahooショッピング</a></li>{% endif %}
        {% if OtherSite.dena_url %}<li class="col-xs-6 col-sm-4"><a href="{{ OtherSite.dena_url }}" target="_blank" class="btn btn-default btn-sm btn-block">DeNAショッピング</a></li>{% endif %}
        {% if OtherSite.ponpare_url %}<li class="col-xs-6 col-sm-4"><a href="{{ OtherSite.ponpare_url }}" target="_blank" class="btn btn-default btn-sm btn-block">ポンパレモール</a></li>{% endif %}
    </ul>
</div>

イベントの処理を記述する

実際ここは表示の変更やフォームの拡張やデータベースをやりながら記述しています。
OtherSiteEvent.php

php

<?php

namespace Plugin\OtherSite;

use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;

class OtherSiteEvent
{
    private $app;

    public function __construct($app)
    {
        $this->app = $app;
    }

    public function showOtherSite(FilterResponseEvent $event)
    {
        $app = $this->app;

        $id = $app['request']->attributes->get('id');
        $Product = $app['eccube.repository.product']->find($id);
        $OtherSite = $app['eccube.plugin.repository.other_site']->find($id);

        if (count($OtherSite) > 0) {
            $twig = $app->renderView(
                'OtherSite/Resource/template/Front/other_site.twig',
                array(
                    'OtherSite' => $OtherSite,
                )
            );

            $response = $event->getResponse();
            $html = $response->getContent();
            $crawler = new Crawler($html);
            $oldElement = $crawler->filter('.cart_area .btn_area');
            $oldHtml = $oldElement->html();
            $newHtml = $oldHtml.$twig;

            $html = $crawler->html();
            $html = str_replace($oldHtml, $newHtml, $html);

            $response->setContent($html);
            $event->setResponse($response);
        }
    }

    public function registerOtherSite(FilterResponseEvent $event)
    {

        $app = $this->app;
        $id = $app['request']->attributes->get('id');

        $form = $app['form.factory']->createBuilder('admin_product')->getForm();

        $OtherSite=$app['eccube.plugin.repository.other_site']->find($id);

        if (is_null($OtherSite)) {
            $OtherSite = new \Plugin\OtherSite\Entity\OtherSite();
        }
        $form->get('rakuten_url')->setData($OtherSite->getRakutenUrl());
        $form->get('yahoo_url')->setData($OtherSite->getYahooUrl());
        $form->get('amazon_url')->setData($OtherSite->getAmazonUrl());
        $form->get('dena_url')->setData($OtherSite->getDenaUrl());
        $form->get('ponpare_url')->setData($OtherSite->getPonpareUrl());

        $form->handleRequest($app['request']);      

       if ('POST' === $app['request']->getMethod()) {
            if ($form->isValid()) {
                $Product = $app['eccube.repository.product']->find($id);
                $OtherSite
                    ->setProductId($Product->getId())
                    ->setProduct($Product)
                    ->setRakutenUrl($form->get('rakuten_url')->getData())
                    ->setYahooUrl($form->get('yahoo_url')->getData())
                    ->setAmazonUrl($form->get('amazon_url')->getData())
                    ->setDenaUrl($form->get('dena_url')->getData())
                    ->setPonpareUrl($form->get('ponpare_url')->getData())
                    ;
                $app['orm.em']->persist($OtherSite);
                $app['orm.em']->flush();
            }
        }

    }

    public function addContentOnProductEdit(FilterResponseEvent $event)
    {
        $app = $this->app;
        $request = $event->getRequest();
        $response = $event->getResponse();
        $id = $request->attributes->get('id');

        $html = $response->getContent();
        $crawler = new Crawler($html);

        $form = $app['form.factory']
            ->createBuilder('admin_product')
            ->getForm();

        if ($id) {
            $Product = $app['eccube.repository.product']->find($id);
        } else {
            $Product = new \Eccube\Entity\Product();
        }

        $OtherSite = $app['eccube.plugin.repository.other_site']->findOneBy(
            array(
                'product_id' => $id,
            ));
        if (isset($OtherSite)) {
            $form->get('rakuten_url')->setData($OtherSite->getRakutenUrl());
            $form->get('yahoo_url')->setData($OtherSite->getYahooUrl());
            $form->get('amazon_url')->setData($OtherSite->getAmazonUrl());
            $form->get('dena_url')->setData($OtherSite->getDenaUrl());
            $form->get('ponpare_url')->setData($OtherSite->getPonpareUrl());
        }
        $form->handleRequest($request);

        $twig = $app->renderView('OtherSite/Resource/template/Admin/other_site.twig',
                array('form' => $form->createView()));

        $oldElement = $crawler
            ->filter('.accordion')
            ->last();

        if ($oldElement->count() > 0) {
            $oldHtml = $oldElement->html();
            $newHtml = $oldHtml.$twig;
            $html = $crawler->html();
            $html = str_replace($oldHtml, $newHtml, $html);
            $response->setContent($html);
            $event->setResponse($response);
        }
    }
}

パッケージにして完成

最後にファイルを.tar.gzにまとめて完成
注:プラグインディレクトリをパッケージするのではなく、配下のファイル群をパッケージする事。

完成品

管理画面

スクリーンショット 2015-12-05 16.45.33.jpg

商品詳細

スクリーンショット 2015-12-05 16.44.32.jpg

EC-CUBE PLUGIN AWARD 3.0に出す

パートナー登録をする

EC-CUBEパートナー登録
登録したんですが、組織名、組織URL、組織説明はコンテスト応募の敷居を高くしてる気がします。個人で参加出来る事はもっと明示した方がいいのでは?

プラグイン申請準備

EC-CUBEプラグイン申請マニュアルを参考にロゴとか説明とか入力して申請完了。ありがとうございました。

まだまだ全然理解してないけど他に何作ろうかなぁ :trollface:

この投稿は Hamee Advent Calendar 20155日目の記事です。