EC-CUBE4とWordPressを連携する方法
EC-CUBE Advent Calendar 2020 15日目の記事です。
EC-CUBE4でWordPressの記事を取得する方法を先日書きました。
EC-CUBE4でWordPressの投稿記事を簡単に出力する方法 - AmidaikeBlog
今回は記事を取得するのではなく、WordPressからEC-CUBE4の関数やEC-CUBE4からWordPressの関数を利用できる方法を説明します。
ちなみにEC-CUBE2系はこちらの記事を参考にしてください。
今回は前回とは異なり、EC-CUBE4とWordPressは同階層にインストールするようにします。
EC-CUBE4のURL
http://xxxxx/
というように、同一ドメイン、同階層でEC-CUBE4とWordPressが動作されている事を想定しています。
EC-CUBE4とWordPressを共存するための設定
インストールさせる順番ですが、先にWordPressをインストールしてからEC-CUBE4をインストールするようにしてください。
WordPressのインストールは普段お使いの方法でインストールしてください。
次にEC-CUBE4を配置しますが、直下にあるindex.php
の内容を以下のように変更します。
<?php use Dotenv\Dotenv; use Eccube\Kernel; use Symfony\Component\Debug\Debug; use Symfony\Component\HttpFoundation\Request; // システム要件チェック if (version_compare(PHP_VERSION, '7.1.3') < 0) { die('Your PHP installation is too old. EC-CUBE requires at least PHP 7.1.3. See the <a href="http://www.ec-cube.net/product/system.php" target="_blank">system requirements</a> page for more information.'); } $autoload = __DIR__.'/vendor/autoload.php'; if (!file_exists($autoload) && !is_readable($autoload)) { die('Composer is not installed.'); } require $autoload; // The check is to ensure we don't use .env in production if (!isset($_SERVER['APP_ENV'])) { if (!class_exists(Dotenv::class)) { throw new \RuntimeException('APP_ENV environment variable is not defined. You need to define environment variables for configuration or add "symfony/dotenv" as a Composer dependency to load variables from a .env file.'); } if (file_exists(__DIR__.'/.env')) { (new Dotenv(__DIR__))->overload(); if (strpos(getenv('DATABASE_URL'), 'sqlite') !== false && !extension_loaded('pdo_sqlite')) { (new Dotenv(__DIR__, '.env.install'))->overload(); } } else { (new Dotenv(__DIR__, '.env.install'))->overload(); } } $env = isset($_SERVER['APP_ENV']) ? $_SERVER['APP_ENV'] : 'dev'; $debug = isset($_SERVER['APP_DEBUG']) ? $_SERVER['APP_DEBUG'] : ('prod' !== $env); if ($debug) { umask(0000); Debug::enable(); } $trustedProxies = isset($_SERVER['TRUSTED_PROXIES']) ? $_SERVER['TRUSTED_PROXIES'] : false; if ($trustedProxies) { Request::setTrustedProxies(explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST); } $trustedHosts = isset($_SERVER['TRUSTED_HOSTS']) ? $_SERVER['TRUSTED_HOSTS'] : false; if ($trustedHosts) { Request::setTrustedHosts(explode(',', $trustedHosts)); } // Request::createFromGlobals以降で呼び出しをするとHTTPSへリンクされなくなる require_once __DIR__.'/wp-load.php'; // Request::createFromGlobals以降で呼び出しをしないとエスケープ処理が行われてしまうため、stripslashes_deepを利用してmagic_quoteを無効にする $_POST = stripslashes_deep($_POST); $request = Request::createFromGlobals(); $maintenanceFile = env('ECCUBE_MAINTENANCE_FILE_PATH', __DIR__.'/.maintenance'); if (file_exists($maintenanceFile)) { $pathInfo = \rawurldecode($request->getPathInfo()); $adminPath = env('ECCUBE_ADMIN_ROUTE', 'admin'); $adminPath = '/'.\trim($adminPath, '/').'/'; if (\strpos($pathInfo, $adminPath) !== 0) { $locale = env('ECCUBE_LOCALE'); $templateCode = env('ECCUBE_TEMPLATE_CODE'); $baseUrl = \htmlspecialchars(\rawurldecode($request->getBaseUrl()), ENT_QUOTES); header('HTTP/1.1 503 Service Temporarily Unavailable'); require __DIR__.'/maintenance.php'; return; } } $kernel = new Kernel($env, $debug); $response = $kernel->handle($request); if ($request->getPathInfo() === '/' || $request->query->get('p') || $response->isNotFound()) { define('WP_USE_THEMES', true); // tokenを以下のように記述しないと出力されないため対応 $app = \Eccube\Application::getInstance(); $tokenProvider = $app->getParentContainer()->get('security.csrf.token_manager'); $tokenProvider->getToken('_token'); /** Loads the WordPress Environment and Template */ require(dirname(__FILE__).'/wp-blog-header.php'); } else { $response->send(); $kernel->terminate($request, $response); }
簡単に説明しますが、57行目にある
require_once __DIR__.'/wp-load.php';
でEC-CUBE4からWordPressの関数をロードさせています。
また、59行目の
$_POST = stripslashes_deep($_POST);
はエスケープ処理を無効化するためのものです。
次に84行目以降ですが、
if ($request->getPathInfo() === '/' || $request->query->get('p') || $response->isNotFound()) { define('WP_USE_THEMES', true); // tokenを以下のように記述しないと出力されないため対応 $app = \Eccube\Application::getInstance(); $tokenProvider = $app->getParentContainer()->get('security.csrf.token_manager'); $tokenProvider->getToken('_token'); /** Loads the WordPress Environment and Template */ require(dirname(__FILE__).'/wp-blog-header.php'); } else { $response->send(); $kernel->terminate($request, $response); }
if文でWordPressの記事なのかそうじゃないのかを判断しています。
88行目にあるApplication
というクラスですが、EC-CUBE3系の名残でありEC-CUBE4系では利用されていないのですが、思わぬ副産物として便利に利用できることが発覚したので、Applicationクラスを利用しています。
今のままではWordPressの管理画面が表示されないので、.htaccess
の52行目付近に以下の内容を追加します。
RewriteCond %{REQUEST_FILENAME} !/wp-admin [NC]
上記の内容でEC-CUBE4とWordPressの共存設定が完了です。
EC-CUBE4をインストールする際は、http://xxxxx/install
から行う様にしてください。
functions.phpに関数を追加
WordPressからEC-CUBE4の関数を呼び出す方法は色々あるのですが、何度も利用する様な関数はfunctions.phpにまとめて書いておきます。 よく利用されるであろう関数を下記に記述しておきます。
- functions.php
/** * EC-CUBE連携用関数 */ require_once __DIR__.'/../../../vendor/autoload.php'; function get_token() { $app = \Eccube\Application::getInstance(); $tokenProvider = $app->getParentContainer()->get('security.csrf.token_manager'); return $tokenProvider->getToken('_token')->getValue(); } add_shortcode('token', 'get_token'); function get_title() { $app = \Eccube\Application::getInstance(); $tokenProvider = $app->getParentContainer()->get(''); } add_shortcode('title', 'get_title'); function get_base_info() { $app = \Eccube\Application::getInstance(); $baseInfoRepositoy = $app->getParentContainer()->get('Eccube\Repository\BaseInfoRepository'); $BaseInfo = $baseInfoRepositoy->get(); return $BaseInfo; } add_shortcode('BaseInfo', 'get_base_info'); function is_granted() { $app = \Eccube\Application::getInstance(); return $app->getParentContainer()->get('security.authorization_checker')->isGranted('ROLE_USER'); } add_shortcode('is_granted', 'is_granted'); function user() { $app = \Eccube\Application::getInstance(); $token = $app->getParentContainer()->get('security.token_storage')->getToken(); if (!\is_object($user = $token->getUser())) { return; } return $user; } add_shortcode('user', 'user'); function get_all_carts() { $app = \Eccube\Application::getInstance(); $CartService = $app->getParentContainer()->get('Eccube\Service\CartService'); return $CartService->getCarts(); } add_shortcode('get_all_carts', 'get_all_carts'); function get_carts_total_quantity() { $Carts = get_all_carts(); $totalQuantity = array_reduce($Carts, function ($total, $Cart) { $total += $Cart->getTotalQuantity(); return $total; }, 0); return $totalQuantity; } add_shortcode('get_carts_total_quantity', 'get_carts_total_quantity'); function get_carts_total_price() { $Carts = get_all_carts(); $totalPrice = array_reduce($Carts, function ($total, $Cart) { $total += $Cart->getTotalPrice(); return $total; }, 0); return $totalPrice; } add_shortcode('get_carts_total_price', 'get_carts_total_price'); // 商品取得 function get_product($id) { $app = \Eccube\Application::getInstance(); $productRepository = $app->getParentContainer()->get('Eccube\Repository\ProductRepository'); return $productRepository->find($id); } add_shortcode('get_product', 'get_product'); // メイン商品画像 function get_main_image($Product) { $app = \Eccube\Application::getInstance(); return $app->getParentContainer()->get('assets.packages')->getUrl($Product->getMainListImage(), 'save_image'); } add_shortcode('get_main_image', 'get_main_image'); // ページ情報 function get_page_data() { $app = \Eccube\Application::getInstance(); $request = $app->getParentContainer()->get('request_stack'); $attributes = $request->getCurrentRequest()->attributes; if ($attributes) { $route = $attributes->get('_route'); if ($route == 'user_data') { $routeParams = $attributes->get('_route_params', []); $route = isset($routeParams['route']) ? $routeParams['route'] : $attributes->get('route', ''); } $pageRepository = $app->getParentContainer()->get('Eccube\Repository\PageRepository'); $Page = $pageRepository->getPageByRoute($route); } return $Page; } add_shortcode('get_page_data', 'get_page_data'); /** * EC-CUBE連携用関数ここまで */
require_once __DIR__.'/../../../vendor/autoload.php';
の記述方法は環境に合わせて修正してください。
それぞれApplicationクラスから必要な情報を取得する様にしており、WordPressからショートコードで利用できるようにしています。
ヘッダーの共通化
EC-CUBE4からWordPressの関数が利用できるようになったので、ヘッダーを共通化してみます。 今回はWordPressで作成したヘッダー内容をEC-CUBE4で利用するようにします。 ただし、この方法だとレイアウト管理が利用できなくなるためご注意ください。
また、titleやmetaタグなどの考慮も必要となるのですが今回は説明から省きます。
WordPress関数を呼び出せる様にEccubeExtension.php
に必要な関数を定義します。
- src/Eccube/Twig/Extension/EccubeExtension.php
<?php /* * This file is part of EC-CUBE * * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved. * * http://www.ec-cube.co.jp/ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Eccube\Twig\Extension; use Eccube\Common\EccubeConfig; use Eccube\Entity\Master\ProductStatus; use Eccube\Entity\Product; use Eccube\Entity\ProductClass; use Eccube\Repository\ProductRepository; use Eccube\Util\StringUtil; use Symfony\Component\Form\FormView; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Intl\Intl; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; use Twig\TwigFunction; class EccubeExtension extends AbstractExtension { /** * @var EccubeConfig */ protected $eccubeConfig; /** * @var ProductRepository */ private $productRepository; /** * @var RequestStack */ private $requestStack; /** * EccubeExtension constructor. * * @param EccubeConfig $eccubeConfig * @param ProductRepository $productRepository * @param RequestStack $requestStack */ public function __construct( EccubeConfig $eccubeConfig, ProductRepository $productRepository, RequestStack $requestStack ) { $this->eccubeConfig = $eccubeConfig; $this->productRepository = $productRepository; $this->requestStack = $requestStack; } /** * Returns a list of functions to add to the existing list. * * @return TwigFunction[] An array of functions */ public function getFunctions() { return [ new TwigFunction('has_errors', [$this, 'hasErrors']), new TwigFunction('active_menus', [$this, 'getActiveMenus']), new TwigFunction('class_categories_as_json', [$this, 'getClassCategoriesAsJson']), new TwigFunction('product', [$this, 'getProduct']), new TwigFunction('php_*', [$this, 'getPhpFunctions'], ['pre_escape' => 'html', 'is_safe' => ['html']]), new TwigFunction('currency_symbol', [$this, 'getCurrencySymbol']), new TwigFunction('get_wp_header', [$this, 'getWpHeader']), new TwigFunction('get_wp_footer', [$this, 'getWpFooter']), new TwigFunction('get_template_directory_uri', [$this, 'getTemplateDirectoryUri']), new TwigFunction('get_sidebar', [$this, 'getSidebar']), new TwigFunction('get_site_url', [$this, 'getSiteUrl']), ]; } /** * Returns a list of filters. * * @return TwigFilter[] */ public function getFilters() { return [ new TwigFilter('no_image_product', [$this, 'getNoImageProduct']), new TwigFilter('date_format', [$this, 'getDateFormatFilter']), new TwigFilter('price', [$this, 'getPriceFilter']), new TwigFilter('ellipsis', [$this, 'getEllipsis']), new TwigFilter('time_ago', [$this, 'getTimeAgo']), new TwigFilter('file_ext_icon', [$this, 'getExtensionIcon'], ['is_safe' => ['html']]), ]; } /** * Name of this extension * * @return string */ public function getName() { return 'eccube'; } /** * Name of this extension * * @param array $menus * * @return array */ public function getActiveMenus($menus = []) { $count = count($menus); for ($i = $count; $i <= 2; $i++) { $menus[] = ''; } return $menus; } /** * return No Image filename * * @return string */ public function getNoImageProduct($image) { return empty($image) ? 'no_image_product.png' : $image; } /** * Name of this extension * * @return string */ public function getDateFormatFilter($date, $value = '', $format = 'Y/m/d') { if (is_null($date)) { return $value; } else { return $date->format($format); } } /** * Name of this extension * * @return string */ public function getPriceFilter($number, $decimals = 0, $decPoint = '.', $thousandsSep = ',') { $locale = $this->eccubeConfig['locale']; $currency = $this->eccubeConfig['currency']; $formatter = new \NumberFormatter($locale, \NumberFormatter::CURRENCY); return $formatter->formatCurrency($number, $currency); } /** * Name of this extension * * @return string */ public function getEllipsis($value, $length = 100, $end = '...') { return StringUtil::ellipsis($value, $length, $end); } /** * Name of this extension * * @return string */ public function getTimeAgo($date) { return StringUtil::timeAgo($date); } /** * FormView にエラーが含まれるかを返す. * * @return bool */ public function hasErrors() { $hasErrors = false; $views = func_get_args(); foreach ($views as $view) { if (!$view instanceof FormView) { throw new \InvalidArgumentException(); } if (count($view->vars['errors'])) { $hasErrors = true; break; } } return $hasErrors; } /** * product_idで指定したProductを取得 * Productが取得できない場合、または非公開の場合、商品情報は表示させない。 * デバッグ環境以外ではProductが取得できなくでもエラー画面は表示させず無視される。 * * @param $id * * @return Product|null */ public function getProduct($id) { try { $Product = $this->productRepository->findWithSortedClassCategories($id); if ($Product->getStatus()->getId() == ProductStatus::DISPLAY_SHOW) { return $Product; } } catch (\Exception $e) { return null; } return null; } /** * Twigでphp関数を使用できるようにする。 * * @return mixed|null */ public function getPhpFunctions() { $arg_list = func_get_args(); $function = array_shift($arg_list); if (is_callable($function)) { return call_user_func_array($function, $arg_list); } trigger_error('Called to an undefined function : php_'.$function, E_USER_WARNING); return null; } /** * Get the ClassCategories as JSON. * * @param Product $Product * * @return string */ public function getClassCategoriesAsJson(Product $Product) { $Product->_calc(); $class_categories = [ '__unselected' => [ '__unselected' => [ 'name' => trans('common.select'), 'product_class_id' => '', ], ], ]; foreach ($Product->getProductClasses() as $ProductClass) { /** @var ProductClass $ProductClass */ if (!$ProductClass->isVisible()) { continue; } /* @var $ProductClass \Eccube\Entity\ProductClass */ $ClassCategory1 = $ProductClass->getClassCategory1(); $ClassCategory2 = $ProductClass->getClassCategory2(); if ($ClassCategory2 && !$ClassCategory2->isVisible()) { continue; } $class_category_id1 = $ClassCategory1 ? (string) $ClassCategory1->getId() : '__unselected2'; $class_category_id2 = $ClassCategory2 ? (string) $ClassCategory2->getId() : ''; $class_category_name2 = $ClassCategory2 ? $ClassCategory2->getName().($ProductClass->getStockFind() ? '' : trans('front.product.out_of_stock_label')) : ''; $class_categories[$class_category_id1]['#'] = [ 'classcategory_id2' => '', 'name' => trans('common.select'), 'product_class_id' => '', ]; $class_categories[$class_category_id1]['#'.$class_category_id2] = [ 'classcategory_id2' => $class_category_id2, 'name' => $class_category_name2, 'stock_find' => $ProductClass->getStockFind(), 'price01' => $ProductClass->getPrice01() === null ? '' : number_format($ProductClass->getPrice01()), 'price02' => number_format($ProductClass->getPrice02()), 'price01_inc_tax' => $ProductClass->getPrice01() === null ? '' : number_format($ProductClass->getPrice01IncTax()), 'price02_inc_tax' => number_format($ProductClass->getPrice02IncTax()), 'product_class_id' => (string) $ProductClass->getId(), 'product_code' => $ProductClass->getCode() === null ? '' : $ProductClass->getCode(), 'sale_type' => (string) $ProductClass->getSaleType()->getId(), ]; } return json_encode($class_categories); } /** * Display file extension icon * * @param $ext * @param $attr * @param $iconOnly アイコンのクラス名のみ返す場合はtrue * * @return string */ public function getExtensionIcon($ext, $attr = [], $iconOnly = false) { $classes = [ 'txt' => 'fa-file-text-o', 'rtf' => 'fa-file-text-o', 'pdf' => 'fa-file-pdf-o', 'doc' => 'fa-file-word-o', 'docx' => 'fa-file-word-o', 'csv' => 'fa-file-excel-o', 'xls' => 'fa-file-excel-o', 'xlsx' => 'fa-file-excel-o', 'ppt' => 'fa-file-powerpoint-o', 'pptx' => 'fa-file-powerpoint-o', 'png' => 'fa-file-image-o', 'jpg' => 'fa-file-image-o', 'jpeg' => 'fa-file-image-o', 'bmp' => 'fa-file-image-o', 'gif' => 'fa-file-image-o', 'zip' => 'fa-file-archive-o', 'tar' => 'fa-file-archive-o', 'gz' => 'fa-file-archive-o', 'rar' => 'fa-file-archive-o', '7zip' => 'fa-file-archive-o', 'mp3' => 'fa-file-audio-o', 'm4a' => 'fa-file-audio-o', 'wav' => 'fa-file-audio-o', 'mp4' => 'fa-file-video-o', 'wmv' => 'fa-file-video-o', 'mov' => 'fa-file-video-o', 'mkv' => 'fa-file-video-o', ]; $ext = strtolower($ext); $class = isset($classes[$ext]) ? $classes[$ext] : 'fa-file-o'; if ($iconOnly) { return $class; } $attr['class'] = isset($attr['class']) ? $attr['class']." fa {$class}" : "fa {$class}"; $html = '<i '; foreach ($attr as $name => $value) { $html .= "{$name}=\"$value\" "; } $html .= '></i>'; return $html; } /** * Get currency symbol * * @param null $currency * * @return bool|string */ public function getCurrencySymbol($currency = null) { if (is_null($currency)) { $currency = $this->eccubeConfig->get('currency'); } $symbol = Intl::getCurrencyBundle()->getCurrencySymbol($currency); return $symbol; } /** * WordPressからヘッダーを取得 */ public function getWpHeader() { $request = $this->requestStack->getCurrentRequest(); if ($request->getPathInfo() === '/' || $request->query->get('p')) { return null; } else { return get_header(); } } /** * WordPressからフッターを取得 */ public function getWpFooter() { $request = $this->requestStack->getCurrentRequest(); if ($request->getPathInfo() === '/' || $request->query->get('p')) { return null; } else { return get_footer(); } } /** * WordPressからリソース用ディレクトリを取得 */ public function getTemplateDirectoryUri() { return get_template_directory_uri(); } /** * WordPressからサイドバーを取得 */ public function getSidebar() { return get_sidebar(); } /** * WordPressからsite_urlを取得 * * @param $path */ public function getSiteUrl($path) { return site_url($path); } }
78〜82行目でWordPressの関数を呼び出す様に定義しており、実際の処理は387行目以降となります。
次に、default_frame.twig
の内容を修正します。
- app/template/default/default_frame.twig
{{ get_wp_header() }} {# Layout: BODY_AFTER #} {% if Layout.BodyAfter %} {{ include('block.twig', {'Blocks': Layout.BodyAfter}) }} {% endif %} {% block stylesheet %}{% endblock %} {# Layout: HEADER #} {% if Layout.Header %} {{ include('block.twig', {'Blocks': Layout.Header}) }} {% endif %} {% if Layout.ContentsTop %} {{ include('block.twig', {'Blocks': Layout.ContentsTop}) }} {% endif %} {# Layout: SIDE_LEFT #} {% if Layout.SideLeft %} {{ include('block.twig', {'Blocks': Layout.SideLeft}) }} {% endif %} <main class="page_{{ app.request.get('_route') }} {{ body_class|default('other_page') }}"> {# Layout: MAIN_TOP #} {% if Layout.MainTop %} {{ include('block.twig', {'Blocks': Layout.MainTop}) }} {% endif %} {# MAIN AREA #} {% block main %}{% endblock %} {# Layout: MAIN_Bottom #} {% if Layout.MainBottom %} {{ include('block.twig', {'Blocks': Layout.MainBottom}) }} {% endif %} {# Layout: SIDE_RIGHT #} {% if Layout.SideRight %} {{ include('block.twig', {'Blocks': Layout.SideRight}) }} {% endif %} {# Layout: CONTENTS_BOTTOM #} {% if Layout.ContentsBottom %} {{ include('block.twig', {'Blocks': Layout.ContentsBottom}) }} {% endif %} </main> {# Layout: CONTENTS_FOOTER #} {% if Layout.Footer %} {{ include('block.twig', {'Blocks': Layout.Footer}) }} {% endif %} {# Layout: DRAWER #} {% if Layout.Drawer %} {{ include('block.twig', {'Blocks': Layout.Drawer}) }} {% endif %} {% block javascript %}{% endblock %} {% if Layout.CloseBodyBefore %} {{ include('block.twig', {'Blocks': Layout.CloseBodyBefore}) }} {% endif %} {% if plugin_snippets is defined %} {{ include('snippet.twig', { snippets: plugin_snippets }) }} {% endif %} {{ get_wp_footer() }}
記述する内容は何でも構いませんので、デザインに合わせて修正してください。
最後にWordPress側のheader.php
のhead
タグ内に以下の内容を記述すれば完成です。
<meta name="eccube-csrf-token" content="<?php echo get_token(); ?>">
記述する箇所は多いのですが、行っていることは簡単なので、 functions.phpやEccubeExtensionをカスタマイズしていくことで便利に使えると思いますので参考にしてください。