/** * 2016-03-26 * Ситуация, когда платёж не найден, является нормальной, * потому что к одной учётной записи Stripe может быть привязано несколько магазинов, * и Stripe будет оповещать сразу все магазины о событиях одного из них. * Магазину надо уметь различать свои события и чужие, * и мы делаем это именно по идентификатору транзакции. * @return Payment|DfPayment|null */ public function payment() { return dfc($this, function () { /** @var int|null $id */ $id = df_fetch_one('sales_payment_transaction', 'payment_id', ['txn_id' => $this->id()]); return !$id ? null : df_load(Payment::class, $id); }); }
/** * 2016-06-04 * @return \DateTime|null */ public function dob() { return dfc($this, function () { /** @var \DateTime|null $result */ $result = $this->_dob(); if (!$result && df_is_customer_attribute_required('dob')) { $result = new \DateTime(); $result->setDate(1900, 1, 1); } return $result; }); }
/** * 2016-07-31 * @param string $class * @return Settings */ protected function child($class) { return dfc($this, function ($class) { /** * 2015-08-04 * Ошибочно писать здесь self::s($class) * потому что класс ребёнка не обязательно должен быть наследником класса родителя: * ему достаточно быть наследником @see \Df\Config\Settings * @var Settings $result */ $result = df_sc($class, __CLASS__); $result->setScope($this->scope()); return $result; }, func_get_args()); }
/** * 2016-08-10 * @param callable $f * @param mixed|null $d [optional] * @param string|null $key [optional] * @return mixed */ private function filter(callable $f, $d = null, $key = null) { return dfc($this, function ($f, $d, $key) { return call_user_func($f, $this->v($d, $key)); }, [$f, $d, $key ?: df_caller_f(1)]); }
/** * 2016-08-20 * @return T|null */ protected function transL() { return dfc($this, function () { return df_trans_by_payment_last($this->ii()); }); }
/** * 2016-07-10 * @return string */ public function asText() { return dfc($this, function () { return df_cc_n($this->asArray()); }); }
/** * 2016-11-12 * @override * @see \Df\Payment\ConfigProvider::s() * @return S */ protected function s() { return dfc($this, function () { return df_ar(parent::s(), S::class); }); }
/** * 2015-12-07 * 2016-01-01 * Сегодня заметил, что Magento 2, в отличие от Magento 1.x, * допускает иерархическую вложенность групп настроек большую, чем 3, например: * https://github.com/magento/magento2/blob/2.0.0/app/code/Magento/Cron/etc/adminhtml/system.xml#L14 * В Magento 1.x вложенность всегда такова: section / group / field. * В Magento 2 вложенность может быть такой: section / group / group / field. * @return array(string => mixed) */ protected function value() { return dfc($this, function () { /** @var string[] $pathA */ $pathA = array_slice(df_explode_xpath($this->getPath()), 1); /** @var string $fieldName */ $fieldName = array_pop($pathA); /** @var string $path */ $path = 'groups/' . implode('/groups/', $pathA) . '/fields/' . $fieldName; /** @var array(string => mixed) $result */ /** * 2016-09-02 * При сохранении настроек вне области действия по умолчанию * в результат попадает ключ «inherit». Удаляем его. * https://code.dmitry-fedyuk.com/m2e/allpay/issues/24 */ $result = dfa_unset(dfa_deep($this->_data, $path), 'inherit'); df_result_array($result); return $result; }); }
/** * 2016-07-10 * @return Transaction */ private function requestTransaction() { return dfc($this, function () { return df_load(Transaction::class, $this->requestIdG(), true, 'txn_id'); }); }
/** * 2016-08-27 * @return S */ protected function s() { return dfc($this, function () { return S::convention($this); }); }
/** @return X */ public function e() { return dfc($this, function () { return df_xml_parse($this[self::$P__E]); }); }
/** * 2016-09-07 * Размер транзакции в платёжной валюте: «Mage2.PRO» → «Payment» → <...> → «Payment Currency». * @return float|int|string */ protected final function amountF() { return dfc($this, function () { return $this->amountFormat($this->amount()); }); }
/** * 2016-06-06 * Цель плагина — устранение дефекта ядра, который проявляется в том, * что непосредственно после авторизации посетителя через какой-либо сторонний сервис * (в моих случаях: Facebook, Amazon) имя покупателя не отображается в шапке. * Это блок @see \Magento\Customer\Block\Account\Customer не имеет атрибута «cacheable»: * https://github.com/magento/magento2/blob/2.1.0-rc1/app/design/frontend/Magento/luma/Magento_Customer/layout/default.xml#L11 * * Это разумно, потому что этот блок отображается на всех страницах витрины, * и он используется не только для авторизованных посетителей, но и для анонимных, * и если он будет «cacheable», то тогда полностраничное кэширование не будет работать вовсе. * * Однако из-за отсутствия атрибута «cacheable» система считает, * что она может кэшировать страницу целиком: * @see \Magento\Framework\View\Layout::isCacheable() * https://github.com/magento/magento2/blob/2.1.0-rc1/lib/internal/Magento/Framework/View/Layout.php#L1073-L1083 public function isCacheable() { $this->build(); $cacheableXml = !(bool)count($this->getXml()->xpath('//' . Element::TYPE_BLOCK . '[@cacheable="false"]')); return $this->cacheable && $cacheableXml; } * Это, в принципе, ещё тоже само по себе не смертельно, ведь блок работает через AJAX, * и по хорошему вполне бы мог корректно подгружать имя посетителя асинхронно * даже при полностью закэшированной странице. * * Однако коварный метод @see \Magento\PageCache\Model\Layout\LayoutPlugin::afterGenerateXml() * https://github.com/magento/magento2/blob/2.1.0-rc1/app/code/Magento/PageCache/Model/Layout/LayoutPlugin.php#L37-L51 * видит, что isCacheable() вернуло true, и устанавливает заголовок «Сache-Сontrol: public»: *«Set appropriate Cache-Control headers. We have to set public headers in order to tell Varnish and Builtin app that page should be cached»: public function afterGenerateXml(\Magento\Framework\View\Layout $subject, $result) { if ($subject->isCacheable() && $this->config->isEnabled()) { $this->response->setPublicHeaders($this->config->getTtl()); } return $result; } * * Непосвящённому программисту может быть ещё неочевидно, что здесь такого особенного. * Однако затем в дело вступает метод @see \Magento\Framework\App\PageCache\Kernel::process(): * https://github.com/magento/magento2/blob/2.1.0-rc1/lib/internal/Magento/Framework/App/PageCache/Kernel.php#L65-L90 * Он видит, что заголовок «Сache-Сontrol» начинается с «Сache-Сontrol: public»: * if (preg_match('/public.*s-maxage=(\d+)/', $response->getHeader('Cache-Control')->getFieldValue(), $matches)) * ... и грохает все куки вызовом функции @see header_remove() $response->clearHeader('Set-Cookie'); if (!headers_sent()) { header_remove('Set-Cookie'); } * Тут уже ясно, что наступает пипец, но может быть ещё неочевидно, какой именно. * Пипец же в том, что в числе прочих грохается кука * @see \Magento\Customer\Model\Customer\NotificationStorage::UPDATE_CUSTOMER_SESSION * https://github.com/magento/magento2/blob/2.1.0-rc1/app/code/Magento/Customer/Model/Customer/NotificationStorage.php#L12 * * Эта кука ранее была установлена методом * @see \Magento\Customer\Model\Plugin\CustomerNotification::beforeDispatch(): * https://github.com/magento/magento2/blob/2.1.0-rc1/app/code/Magento/Customer/Model/Plugin/CustomerNotification.php#L70-L97 * if ($this->state->getAreaCode() == Area::AREA_FRONTEND && $this->notificationStorage->isExists( NotificationStorage::UPDATE_CUSTOMER_SESSION, $this->session->getCustomerId() )) { ... $publicCookieMetadata = $this->cookieMetadataFactory->createPublicCookieMetadata(); $publicCookieMetadata->setDurationOneYear(); $publicCookieMetadata->setPath('/'); $publicCookieMetadata->setHttpOnly(false); $this->cookieManager->setPublicCookie( NotificationStorage::UPDATE_CUSTOMER_SESSION, $this->session->getCustomerId(), $publicCookieMetadata ); * В свою очередь, в notification storage ключ UPDATE_CUSTOMER_SESSION устанавливается * при сохранении покупателя: * @see \Magento\Customer\Model\ResourceModel\Customer::_afterSave() protected function _afterSave(\Magento\Framework\DataObject $customer) { $this->getNotificationStorage()->add( NotificationStorage::UPDATE_CUSTOMER_SESSION, $customer->getId() ); return parent::_afterSave($customer); } * При авторизации покупателя через внешнюю систему мы как раз и делаем сохранение покупателя: * ведь мы получаем данные покупателя из внешней системы и их надо сохранить в Magento: * @see \Df\Sso\CustomerReturn::customer() * https://github.com/mage2pro/core/blob/4cd771d1/Customer/External/ReturnT.php?ts=4#L191 * * Итак, куки грохаются, ключ «update_customer_session» из кук пропадает. * Что теперь происходит в браузере? Смотрим: updateSession = $.cookieStorage.get('update_customer_session'); if (updateSession) { mageStorage.post( options.updateSessionUrl, JSON.stringify({ 'customer_id': updateSession, 'form_key': window.FORM_KEY }) ).done( function() { $.cookieStorage .setConf({path: '/', expires: -1}) .set('update_customer_session', null) ; } ); } * Вот именно здесь браузер должен поддягивать свежую информацию о покупателе. * Но мы этого удовольствия лишены, потому что куки-то грохнуты. * Вот для исправления этой ситуации и предназначен мой метод. * @see \Magento\Framework\View\Layout::isCacheable() * * 2016-06-06 * df_cookie_m()->getCookie(NotificationStorage::UPDATE_CUSTOMER_SESSION) * здесь нихуя не работает, потому что * @see \Magento\Framework\Stdlib\Cookie\PhpCookieReader::getCookie() * тупо смотрит в $_COOKIE (куки прошлого сеанса), * но не смотрит те новые куки, которые мы установили в этом сеансе. * * @param Sb $sb * @param int|void $result * @return int|void */ public function afterIsCacheable(Sb $sb, $result) { return $result && !dfc($this, function () { return df_find(function ($h) { return df_starts_with($h, 'Set-Cookie: update_customer_session') || df_starts_with($h, 'Set-Cookie: ' . self::NEED_UPDATE_CUSTOMER_DATA); }, headers_list()); }); }
/** * 2016-11-13 * Поддержка фиксированного списка валют. * Используется модулем «Omise»: * https://code.dmitry-fedyuk.com/m2e/omise/blob/0.0.6/etc/adminhtml/system.xml#L154 * При таком синтаксисе мы намеренно не добавляем в результат «Order Currency» и «Base Currency». * Метод будет возвращать только те значения из dfValues, * которые включены администратором в перечень разрешённых валют. * @return string[] */ private function filter() { return dfc($this, function () { return df_fe_fc_csv($this, 'dfValues'); }); }
/** * 2016-07-13 * 2016-07-28 * Транзакции может не быть в случае каких-то сбоев. * Решил не падать из-за этого, потому что мы можем попасть сюда * в невинном сценарии отображения таблицы заказов * (в контексте рисования колонки с названиями способов оплаты). * @return T|null */ private function transParent() { return dfc($this, function () { return df_trans_by_payment_first($this->ii()); }); }
/** * 2016-09-07 * Намеренно не используем @see _storeId * @return Store */ private function store() { return dfc($this, function () { return $this->o()->getStore(); }); }
/** * 2016-05-06 * @return array(string => string) */ private function meta() { return dfc($this, function () { return Metadata::vars($this->store(), $this->o()); }); }
/** * 2016-08-05 * @return string */ private function tag() { return dfc($this, function () { return strtolower($this[self::$P__TAG]); }); }
/** * 2016-05-30 * @return bool */ protected function thirdPartyLocalhost() { return dfc($this, function () { return df_is_localhost() && !df_my(); }); }
/** * 2016-11-30 * Другой алгоритм: $this->getParentBlock() instanceof Links *@used-by _toHtml() * @used-by loggedOut() * @return string */ private function isInHeader() { return dfc($this, function () { return 'header.links' === df_parent_name($this); }); }
/** * 2016-08-27 * @return T|null */ private function t() { return dfc($this, function () { return df_trans_by_payment_last($this->p()); }); }
/** * 2016-09-05 * «Mage2.PRO» → «Payment» → <...> → «Payment Currency» * Текущая валюта может меняться динамически (в том числе посетителем магазина и сессией), * поэтому мы используем параметр store, а не scope * @param null|string|int|S|Store $s [optional] * @param Currency|string|null $oc [optional] * @return Currency */ private function currency($s = null, $oc = null) { return dfc($this, function ($s = null, $oc = null) { return CurrencyFE::v($this->v('currency', $s), $s, $oc); }, func_get_args()); }
/** * 2016-08-14 * @param string $class [optional] * @return EavSetup */ protected final function sEav($class = EavSetup::class) { return dfc($this, function ($class) { return df_create($class, ['setup' => $this->s()]); }, [$class]); }
/** * 2016-07-18 * @return Settings */ private function ss() { return dfc($this, function () { return $this->paymentMethod()->s(); }); }
/** * 2016-06-04 * @used-by mc() * @return string */ private final function fId() { return dfc($this, function () { return Schema::fIdC($this); }); }