[ECCUBE] ECCUBE4のプラグインを学ぶために商品レビュープラグインを改造して返信機能をつけてみる

ECCUBE4学習の3日目です
プラグインサンプルとして提供されている商品レビュープラグインを改造してみようと思います

githubからzipを落としてきて app/Plugin の下に置きます
2019年6月24日の段階では開発ドキュメントにProductReviewの名前で展開するように書かれていますが、実際はProductReview4とします
(そうしなければ次のインストールに失敗します)

$ cd ~/apps/ec-cube/app/Plugin
$ wget https://github.com/EC-CUBE/ProductReview-plugin/archive/4.0.zip
$ unzip 4.0.zip
$ mv ProductReview-plugin-4.0 ProductReview4
$ rm 4.0.zip
$ cd ~/apps/ec-cube/app/Plugin $ wget https://github.com/EC-CUBE/ProductReview-plugin/archive/4.0.zip $ unzip 4.0.zip $ mv ProductReview-plugin-4.0 ProductReview4 $ rm 4.0.zip
$ cd ~/apps/ec-cube/app/Plugin
$ wget https://github.com/EC-CUBE/ProductReview-plugin/archive/4.0.zip
$ unzip 4.0.zip
$ mv ProductReview-plugin-4.0 ProductReview4
$ rm 4.0.zip

落としてきたProductReview4プラグインのインストール&有効化

$ cd ~/apps/ec-cube/
$ bin/console eccube:plugin:install --code=ProductReview4
$ bin/console eccube:plugin:enable --code=ProductReview4
$ cd ~/apps/ec-cube/ $ bin/console eccube:plugin:install --code=ProductReview4 $ bin/console eccube:plugin:enable --code=ProductReview4
$ cd ~/apps/ec-cube/
$ bin/console eccube:plugin:install --code=ProductReview4
$ bin/console eccube:plugin:enable --code=ProductReview4
ProductReview4プラグインのインストール完了

基本設定(プラグイン名やバージョン)は composer.json に書く、と。ここで追加のcomposerも書けるのかー
ふむふむふむ…

見てるだけでは頭に入らないので、改造することでもう少し理解を進めてみます
レビューにショップオーナーからの返信機能を付け加えてみます

データベースの定義は・・・
それらしいのは Entity/ProductReview.php しか見つからない。ここに見よう見まねで付け加えてみましょうか
apps/ec-cube/app/Plugin/ProductReview4/Entity/ProductReview.php の末尾に以下のように追加

/**
* @var string
*
* @ORM\Column(name="reply", type="text", nullable=true)
*/
private $reply;
/**
* Get reply.
*
* @return string
*/
public function getReply()
{
return $this->reply;
}
/**
* Set reply.
*
* @param string $reply
*
* @return ProductReview
*/
public function setReply($reply)
{
$this->reply = $reply;
return $this;
}
/** * @var string * * @ORM\Column(name="reply", type="text", nullable=true) */ private $reply; /** * Get reply. * * @return string */ public function getReply() { return $this->reply; } /** * Set reply. * * @param string $reply * * @return ProductReview */ public function setReply($reply) { $this->reply = $reply; return $this; }
/**
 * @var string
 *
 * @ORM\Column(name="reply", type="text", nullable=true)
 */
private $reply;

/**
 * Get reply.
 *
 * @return string
 */
public function getReply()
{
    return $this->reply;
}

/**
 * Set reply.
 *
 * @param string $reply
 *
 * @return ProductReview
 */
public function setReply($reply)
{
    $this->reply = $reply;

    return $this;
}

DBに反映されるのはインストール時だと思うので、一度無効化>アンインストールしてから、再度インストール>有効化してみます

$ bin/console eccube:plugin:disable --code=ProductReview4
$ bin/console eccube:plugin:uninstall --code=ProductReview4
$ bin/console eccube:plugin:install --code=ProductReview4
$ bin/console eccube:plugin:enable --code=ProductReview4
$ bin/console eccube:plugin:disable --code=ProductReview4 $ bin/console eccube:plugin:uninstall --code=ProductReview4 $ bin/console eccube:plugin:install --code=ProductReview4 $ bin/console eccube:plugin:enable --code=ProductReview4
$ bin/console eccube:plugin:disable --code=ProductReview4
$ bin/console eccube:plugin:uninstall --code=ProductReview4
$ bin/console eccube:plugin:install --code=ProductReview4
$ bin/console eccube:plugin:enable --code=ProductReview4

mysqlにログインしてテーブル構造確認( show columns from plg_product_review )すると・・・

おお、ちゃんとカラムができてる!
このあともう少し調べてみたら、なにも再インストールをしなくとも
bin/console eccube:plugin:schema-update ProductReview4
だけでDB再定義できることもわかりました

では、お店からの返信を入力できるように管理画面用のFormとtwigを改造します

app/Plugin/ProductReview4/Form/Type/Admin/ProductReviewType.php
こちらのcommentの定義の後ろに追加

~~前略~~
->add('comment', TextareaType::class, [
'constraints' => [
new Assert\NotBlank(),
new Assert\Length(['max' => $config['eccube_ltext_len']]),
],
'attr' => [
'maxlength' => $config['eccube_ltext_len'],
],
])
->add('reply', TextareaType::class, [
'required' => false,
'constraints' => [
new Assert\Length(['max' => $config['eccube_ltext_len']]),
],
'attr' => [
'maxlength' => $config['eccube_ltext_len'],
],
])
;
~~前略~~ ->add('comment', TextareaType::class, [ 'constraints' => [ new Assert\NotBlank(), new Assert\Length(['max' => $config['eccube_ltext_len']]), ], 'attr' => [ 'maxlength' => $config['eccube_ltext_len'], ], ]) ->add('reply', TextareaType::class, [ 'required' => false, 'constraints' => [ new Assert\Length(['max' => $config['eccube_ltext_len']]), ], 'attr' => [ 'maxlength' => $config['eccube_ltext_len'], ], ]) ;
~~前略~~
            ->add('comment', TextareaType::class, [
                'constraints' => [
                    new Assert\NotBlank(),
                    new Assert\Length(['max' => $config['eccube_ltext_len']]),
                ],
                'attr' => [
                    'maxlength' => $config['eccube_ltext_len'],
                ],
            ])
            ->add('reply', TextareaType::class, [
                'required' => false,
                'constraints' => [
                    new Assert\Length(['max' => $config['eccube_ltext_len']]),
                ],
                'attr' => [
                    'maxlength' => $config['eccube_ltext_len'],
                ],
            ])
            ;

app/Plugin/ProductReview4/Resource/template/admin/edit.twig
こちらもコメントの後ろに追加

<div class="row mb-2">
<div class="col-3">
<span>{{ 'コメント'|trans }}</span>
<span class="badge badge-primary ml-1">{{ 'product_review.common.required'|trans }}</span>
</div>
<div class="col">
{{ form_widget(form.comment, {'attr': {'rows': '6'}}) }}
{{ form_errors(form.comment) }}
</div>
</div>
<div class="row mb-2">
<div class="col-3">
<span>{{ '返信'|trans }}</span>
</div>
<div class="col">
{{ form_widget(form.reply, {'attr': {'rows': '6'}}) }}
{{ form_errors(form.reply) }}
</div>
</div>
<div class="row mb-2"> <div class="col-3"> <span>{{ 'コメント'|trans }}</span> <span class="badge badge-primary ml-1">{{ 'product_review.common.required'|trans }}</span> </div> <div class="col"> {{ form_widget(form.comment, {'attr': {'rows': '6'}}) }} {{ form_errors(form.comment) }} </div> </div> <div class="row mb-2"> <div class="col-3"> <span>{{ '返信'|trans }}</span> </div> <div class="col"> {{ form_widget(form.reply, {'attr': {'rows': '6'}}) }} {{ form_errors(form.reply) }} </div> </div>
<div class="row mb-2">
    <div class="col-3">
        <span>{{ 'コメント'|trans }}</span>
        <span class="badge badge-primary ml-1">{{ 'product_review.common.required'|trans }}</span>
    </div>
    <div class="col">
        {{ form_widget(form.comment, {'attr': {'rows': '6'}}) }}
        {{ form_errors(form.comment) }}
    </div>
</div>

<div class="row mb-2">
    <div class="col-3">
        <span>{{ '返信'|trans }}</span>
    </div>
    <div class="col">
        {{ form_widget(form.reply, {'attr': {'rows': '6'}}) }}
        {{ form_errors(form.reply) }}
    </div>
</div>
管理画面でお店からの返信を入力できるかの確認

管理画面に返信欄を追加できました

フロント画面にも返信を反映させましょう
app/Plugin/ProductReview4/Resource/template/default/review.twig
前半のCSS部分と後半のHTML部分に追加

~~略~~
<style type="text/css">
#product_review_area {
border-top: 1px solid #E8E8E8;
padding-bottom: 0;
margin-bottom: 20px;
}
#product_review_area .ec-rectHeading {
cursor: pointer;
margin-top: 20px;
margin-bottom: 20px;
}
#product_review_area .ec-rectHeading.is_active i {
transform: rotate(180deg);
}
#product_review_area .review_list {
padding-left: 25px;
}
#product_review_area .review_list li {
margin-bottom: 16px;
}
#product_review_area .review_list .review_date {
font-weight: bold;
}
#product_review_area .recommend_average {
margin-left: 16px;
color: #DE5D50;
}
#product_review_area .review_list .recommend_level {
margin-left: 16px;
color: #DE5D50;
}
#product_review_area .review_list .recommend_name {
margin-left: 16px;
}
#product_review_area .review_list .review_reply {
margin-left: 32px;
margin-top: 12px;
padding-left: 12px;
color: #999;
border-left: 2px solid #999;
}
</style>
<script>
$(function() {
$('#product_review_area').appendTo($('div.ec-layoutRole__main, div.ec-layoutRole__mainWithColumn, div.ec-layoutRole__mainBetweenColumn'));
$('#product_review_area .ec-rectHeading').on('click', function() {
$content = $('#reviewContent');
if ($content.css('display') == 'none') {
$(this).addClass('is_active');
$content.addClass('is_active');
$content.slideDown(300);
} else {
$(this).removeClass('is_active');
$content.removeClass('is_active');
$content.slideUp(300);
}
return false;
});
});
</script>
<!--▼レビューエリア-->
<div id="product_review_area">
<div class="ec-role">
{% set positive_avg_star = ProductReviewAvg %}
{% set nagative_avg_star = 5 - positive_avg_star %}
<div class="ec-rectHeading is_active">
<h4>{{ 'product_review.front.product_detail.title'|trans }}
<!--平均の星の数-->
<span class="recommend_average">{{ stars.stars(positive_avg_star, nagative_avg_star) }}</span>
<!--レビュー数-->
<span>({{ ProductReviewCount }})</span>
<span class="chevron pull-right">
<i class="fas fa-angle-up fa-lg"></i>
</span>
</h4>
</div>
<div id="reviewContent">
{% if ProductReviews %}
<ul class="review_list">
{% for ProductReview in ProductReviews %}
<li>
<p class="review_date">
<!--投稿日-->
{{ ProductReview.create_date|date_day }}
<!--投稿者-->
<span class="recommend_name">
{% if ProductReview.reviewer_url %}
<a href="{{ ProductReview.reviewer_url }}"
target="_blank">{{ 'product_review.front.product_detail.name'|trans({ '%name%': ProductReview.reviewer_name }) }}</a>
{% else %}
{{ 'product_review.front.product_detail.name'|trans({ '%name%': ProductReview.reviewer_name }) }}
{% endif %}
</span>
<!--星の数-->
{% set positive_star = ProductReview.recommend_level %}
{% set nagative_star = 5 - positive_star %}
<span class="recommend_level">
{{ stars.stars(positive_star, nagative_star) }}
</span>
</p>
<!--タイトル-->
<strong>{{ ProductReview.title }}</strong>
<!--レビューコメント-->
<p>{{ ProductReview.comment|nl2br }}</p>
<!--レビュー返信-->
{% if ProductReview.reply %}
<div class="review_reply">
<span class="text-info">お店からの返信</span>
<p>{{ ProductReview.reply|nl2br }}</p>
</div>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p>{{ 'product_review.front.product_detail.no_review'|trans }}</p>
{% endif %}
</div>
<div>
<a href="{{ url('product_review_index', { id: Product.id }) }}"
class="ec-inlineBtn--action">{{ 'product_review.front.product_detail.post_review'|trans }}</a>
</div>
</div>
</div>
<!-- ▲レビューエリア -->
~~略~~ <style type="text/css"> #product_review_area { border-top: 1px solid #E8E8E8; padding-bottom: 0; margin-bottom: 20px; } #product_review_area .ec-rectHeading { cursor: pointer; margin-top: 20px; margin-bottom: 20px; } #product_review_area .ec-rectHeading.is_active i { transform: rotate(180deg); } #product_review_area .review_list { padding-left: 25px; } #product_review_area .review_list li { margin-bottom: 16px; } #product_review_area .review_list .review_date { font-weight: bold; } #product_review_area .recommend_average { margin-left: 16px; color: #DE5D50; } #product_review_area .review_list .recommend_level { margin-left: 16px; color: #DE5D50; } #product_review_area .review_list .recommend_name { margin-left: 16px; } #product_review_area .review_list .review_reply { margin-left: 32px; margin-top: 12px; padding-left: 12px; color: #999; border-left: 2px solid #999; } </style> <script> $(function() { $('#product_review_area').appendTo($('div.ec-layoutRole__main, div.ec-layoutRole__mainWithColumn, div.ec-layoutRole__mainBetweenColumn')); $('#product_review_area .ec-rectHeading').on('click', function() { $content = $('#reviewContent'); if ($content.css('display') == 'none') { $(this).addClass('is_active'); $content.addClass('is_active'); $content.slideDown(300); } else { $(this).removeClass('is_active'); $content.removeClass('is_active'); $content.slideUp(300); } return false; }); }); </script> <!--▼レビューエリア--> <div id="product_review_area"> <div class="ec-role"> {% set positive_avg_star = ProductReviewAvg %} {% set nagative_avg_star = 5 - positive_avg_star %} <div class="ec-rectHeading is_active"> <h4>{{ 'product_review.front.product_detail.title'|trans }} <!--平均の星の数--> <span class="recommend_average">{{ stars.stars(positive_avg_star, nagative_avg_star) }}</span> <!--レビュー数--> <span>({{ ProductReviewCount }})</span> <span class="chevron pull-right"> <i class="fas fa-angle-up fa-lg"></i> </span> </h4> </div> <div id="reviewContent"> {% if ProductReviews %} <ul class="review_list"> {% for ProductReview in ProductReviews %} <li> <p class="review_date"> <!--投稿日--> {{ ProductReview.create_date|date_day }} <!--投稿者--> <span class="recommend_name"> {% if ProductReview.reviewer_url %} <a href="{{ ProductReview.reviewer_url }}" target="_blank">{{ 'product_review.front.product_detail.name'|trans({ '%name%': ProductReview.reviewer_name }) }}</a> {% else %} {{ 'product_review.front.product_detail.name'|trans({ '%name%': ProductReview.reviewer_name }) }} {% endif %} </span> <!--星の数--> {% set positive_star = ProductReview.recommend_level %} {% set nagative_star = 5 - positive_star %} <span class="recommend_level"> {{ stars.stars(positive_star, nagative_star) }} </span> </p> <!--タイトル--> <strong>{{ ProductReview.title }}</strong> <!--レビューコメント--> <p>{{ ProductReview.comment|nl2br }}</p> <!--レビュー返信--> {% if ProductReview.reply %} <div class="review_reply"> <span class="text-info">お店からの返信</span> <p>{{ ProductReview.reply|nl2br }}</p> </div> {% endif %} </li> {% endfor %} </ul> {% else %} <p>{{ 'product_review.front.product_detail.no_review'|trans }}</p> {% endif %} </div> <div> <a href="{{ url('product_review_index', { id: Product.id }) }}" class="ec-inlineBtn--action">{{ 'product_review.front.product_detail.post_review'|trans }}</a> </div> </div> </div> <!-- ▲レビューエリア -->
~~略~~

<style type="text/css">

    #product_review_area {
        border-top: 1px solid #E8E8E8;
        padding-bottom: 0;
        margin-bottom: 20px;
    }

    #product_review_area .ec-rectHeading {
        cursor: pointer;
        margin-top: 20px;
        margin-bottom: 20px;
    }

    #product_review_area .ec-rectHeading.is_active i {
        transform: rotate(180deg);
    }

    #product_review_area .review_list {
        padding-left: 25px;
    }

    #product_review_area .review_list li {
        margin-bottom: 16px;
    }

    #product_review_area .review_list .review_date {
        font-weight: bold;
    }

    #product_review_area .recommend_average {
        margin-left: 16px;
        color: #DE5D50;
    }

    #product_review_area .review_list .recommend_level {
        margin-left: 16px;
        color: #DE5D50;
    }

    #product_review_area .review_list .recommend_name {
        margin-left: 16px;
    }

    #product_review_area .review_list .review_reply {
        margin-left: 32px;
        margin-top: 12px;
        padding-left: 12px;
        color: #999;
        border-left: 2px solid #999;
    }

</style>

<script>
    $(function() {
        $('#product_review_area').appendTo($('div.ec-layoutRole__main, div.ec-layoutRole__mainWithColumn, div.ec-layoutRole__mainBetweenColumn'));

        $('#product_review_area .ec-rectHeading').on('click', function() {
            $content = $('#reviewContent');
            if ($content.css('display') == 'none') {
                $(this).addClass('is_active');
                $content.addClass('is_active');
                $content.slideDown(300);
            } else {
                $(this).removeClass('is_active');
                $content.removeClass('is_active');
                $content.slideUp(300);
            }
            return false;
        });
    });
</script>

<!--▼レビューエリア-->
<div id="product_review_area">
    <div class="ec-role">
        {% set positive_avg_star = ProductReviewAvg %}
        {% set nagative_avg_star = 5 - positive_avg_star %}

        <div class="ec-rectHeading is_active">
            <h4>{{ 'product_review.front.product_detail.title'|trans }}
                <!--平均の星の数-->
                <span class="recommend_average">{{ stars.stars(positive_avg_star, nagative_avg_star) }}</span>
                <!--レビュー数-->
                <span>({{ ProductReviewCount }})</span>
                <span class="chevron pull-right">
                    <i class="fas fa-angle-up fa-lg"></i>
                </span>
            </h4>
        </div>
        <div id="reviewContent">
            {% if ProductReviews %}
                <ul class="review_list">
                    {% for ProductReview in ProductReviews %}
                        <li>
                            <p class="review_date">
                                <!--投稿日-->
                                {{ ProductReview.create_date|date_day }}

                                <!--投稿者-->
                                <span class="recommend_name">
                                            {% if ProductReview.reviewer_url %}
                                                <a href="{{ ProductReview.reviewer_url }}"
                                                   target="_blank">{{ 'product_review.front.product_detail.name'|trans({ '%name%': ProductReview.reviewer_name }) }}</a>
                                            {% else %}
                                                {{ 'product_review.front.product_detail.name'|trans({ '%name%': ProductReview.reviewer_name }) }}
                                            {% endif %}
                                        </span>

                                <!--星の数-->
                                {% set positive_star = ProductReview.recommend_level %}
                                {% set nagative_star = 5 - positive_star %}
                                <span class="recommend_level">
                                            {{ stars.stars(positive_star, nagative_star) }}
                                        </span>
                            </p>

                            <!--タイトル-->
                            <strong>{{ ProductReview.title }}</strong>

                            <!--レビューコメント-->
                            <p>{{ ProductReview.comment|nl2br }}</p>

                            <!--レビュー返信-->
                            {% if ProductReview.reply %}
                            <div class="review_reply">
                                <span class="text-info">お店からの返信</span>
                                <p>{{ ProductReview.reply|nl2br }}</p>
                            </div>
                            {% endif %}
                        </li>
                    {% endfor %}
                </ul>
            {% else %}
                <p>{{ 'product_review.front.product_detail.no_review'|trans }}</p>
            {% endif %}
        </div>
        <div>
            <a href="{{ url('product_review_index', { id: Product.id }) }}"
               class="ec-inlineBtn--action">{{ 'product_review.front.product_detail.post_review'|trans }}</a>
        </div>
    </div>
</div>
<!-- ▲レビューエリア -->
商品詳細画面でレビューにお店から返信も追加

これでお店からの返信機能が出来上がりました!

ここまでの改造を反映させた返信機能付き商品レビュープラグインを公開しておきます
ProductReview-custom.zip

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です