Example #1
1
 /**
  * @override
  * @see _P::execute()
  * @return \Magento\Framework\Controller\Result\Redirect
  */
 public function execute()
 {
     // 2016-06-05
     // @see urldecode() здесь вызывать уже не надо, проверял.
     /** @var string $redirectUrl */
     $redirectUrl = df_request($this->redirectUrlKey()) ?: df_url();
     /**
      * 2016-12-02
      * Если адрес для перенаправления покупателя передётся в адресе возврата,
      * то адрес для перенаправления там закодирован посредством @see base64_encode()
      * @see \Dfe\BlackbaudNetCommunity\Url::get()
      */
     if (!df_starts_with($redirectUrl, 'http')) {
         $redirectUrl = base64_decode($redirectUrl);
     }
     try {
         /** @var Session|DfSession $s */
         $s = df_customer_session();
         if (!$this->mc()) {
             /**
              * 2016-12-01
              * Учётная запись покупателя отсутствует в Magento,
              * и в то же время информации провайдера SSO недостаточно
              * для автоматической регистрации покупателя в Magento
              * (случай Blackbaud NetCommunity).
              * Перенаправляем покупателя на стандартную страницу регистрации Magento,
              * где часть полей будет уже заполнена данными от провайдера SSO,
              * а пароль будет либо скрытым, либо необязательным полем.
              * После регистрации свежесозданная учётная запись будет привязана
              * к учётной записи покупателя в провайдере SSO.
              */
             $redirectUrl = df_customer_url()->getRegisterUrl();
         } else {
             /**
              * 2015-10-08
              * По аналогии с @see \Magento\Customer\Controller\Account\LoginPost::execute()
              * https://github.com/magento/magento2/blob/1.0.0-beta4/app/code/Magento/Customer/Controller/Account/LoginPost.php#L84-L85
              */
             $s->setCustomerDataAsLoggedIn($this->mc()->getDataModel());
             $s->regenerateId();
             /**
              * По аналогии с @see \Magento\Customer\Model\Account\Redirect::updateLastCustomerId()
              * Напрямую тот метод вызвать не можем, потому что он protected,
              * а использовать весь класс @see \Magento\Customer\Model\Account\Redirect пробовал,
              * но оказалось неудобно из-за слишком сложной процедуры перенаправлений.
              */
             if ($s->getLastCustomerId() != $s->getId()) {
                 $s->unsBeforeAuthUrl()->setLastCustomerId($s->getId());
             }
         }
     } catch (\Exception $e) {
         df_message_error($e);
     }
     $this->postProcess();
     return $this->resultRedirectFactory->create()->setUrl($redirectUrl);
 }
Example #2
0
 /**
  * @override
  * @param string $value
  * @return bool
  */
 public function isValid($value)
 {
     $this->prepareValidation($value);
     /**
     * Думаю, правильно будет конвертировать строки типа «09» в целые числа без сбоев.
     * Обратите внимание, что
     * 9 === (int)'09'.
     *
     * Обратите также внимание, что если строка равна '0',
     * то нам применять @see ltrim нельзя, потому что иначе получим пустую строку.
     *
     * 2015-01-23
     * Раньше код был таким:
     				if ('0' !== $value) {
     					$value = ltrim($value, '0');
     				}
     				return strval($value) === strval(intval($value));
     			это приводило к неправильной работе метода для значения «0.0» (вещественное число),
     * потому что ltrim(0.0, '0') возвращает пустую строку.
     * Предыдущая версия кода была написала 2014-08-30
     * (хотя и версии до неё были тоже дефектными, просто там дефекты были другие).
     */
     /** @var string $valueAsString */
     $valueAsString = is_string($value) && '0' !== $value && !df_starts_with($value, '0.') ? ltrim($value, '0') : strval($value);
     return $valueAsString === strval((int) $value);
 }
Example #3
0
 /**
  * 2016-07-28
  * @override
  * @see ObserverInterface::execute()
  * @used-by \Magento\Framework\Event\Invoker\InvokerDefault::_callObserverMethod()
  * @param O $o
  * @return void
  */
 public function execute(O $o)
 {
     /** @var Provider $provider */
     $provider = $o[Plugin::PROVIDER];
     /** @var ISearchResult|ApiSearchResult|UiSearchResult|OrderGC|InvoiceGC|CreditmemoGC $result */
     $result = $o[Plugin::RESULT];
     if (in_array($provider->getName(), ['sales_order_grid_data_source'])) {
         /**
          * 2016-07-28
          * https://github.com/magento/magento2/blob/2.1.0/lib/internal/Magento/Framework/View/Element/UiComponent/DataProvider/SearchResult.php#L37-L40
          * @see \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult::$document
          *
          * Структура документа описана здесь: https://mage2.pro/t/1908
          */
         /** @var string $cacheKey */
         $cacheKey = __METHOD__;
         /** @var string $prop */
         $prop = 'payment_method';
         df_map(function (Document $item) use($cacheKey, $prop) {
             /** @var string|null $methodCode */
             $methodCode = $item[$prop];
             if ($methodCode && df_starts_with($methodCode, 'dfe_')) {
                 /** @var int $id */
                 $id = $item['entity_id'];
                 /**
                  * 2016-07-29
                  * Эта операция очень ресурсоёмка:
                  * для каждой строки таблицы заказов она делает кучу запросов к базе данных.
                  * Поэтому кэшируем результаты в постоянном кэше.
                  */
                 $item[$prop] = df_cache_get_simple([$cacheKey, $id], function () use($id) {
                     /** @var Method $method */
                     $method = df_order($id)->getPayment()->getMethodInstance();
                     return $method->titleDetailed();
                 });
             }
         }, $result);
     }
 }
Example #4
0
 /**
  * 2016-10-06
  * When browsing the default soap url that should return xml soap services,
  * instead there is an exception with the following:
  * «The service interface name "Df\Payment\PlaceOrder" is invalid»
  * https://code.dmitry-fedyuk.com/m2e/stripe/issues/7
  *
  * Magento 2 накладывает ограничения на имена классов-вебсервисов:
  * https://github.com/magento/magento2/blob/2.1.1/app/code/Magento/Webapi/Model/ServiceMetadata.php#L188-L230
  * Однако, как я понял, моего веб-сервиса @see \Df\Payment\PlaceOrder эти ограничения касаются
  * только в сценарии генерации документа WSDL /soap/default?wsdl_list=1
  * Мой веб-сервис предназначен исключительно для моих платёжных модулей, и,
  * будь моя воля, я бы вообще не включал его в документ WSDL.
  * Однако, как я понял, избежать включения веб-сервиса в документ WSDL не так-то просто.
  * Но и менять моё короткое имя Df\Payment\PlaceOrder на имя типа Df\Payment\API\PlaceOrderInterface
  * мне не хочется: это имя используется каждым моим платёжным модулем,
  * и мне удобнее иметь для себя свои имена.
  * Поэтому я и написал этот плагин: чтобы возвращать ядру имя своего сервиса
  * (и других моих сервисов, если они потом будут), обходя ограничения ядра на имена классов сервисов.
  *
  * @see \Magento\Webapi\Model\ServiceMetadata::getServiceName()
  * https://github.com/magento/magento2/blob/2.1.1/app/code/Magento/Webapi/Model/ServiceMetadata.php#L188-L230
  *
  * @param Sb $sb
  * @param \Closure $proceed
  * @param string $interfaceName
  * @param string $version
  * @param bool $preserveVersion Should version be preserved during interface name conversion into service name
  * @return string
  */
 public function aroundGetServiceName(Sb $sb, \Closure $proceed, $interfaceName, $version, $preserveVersion = true)
 {
     return df_starts_with($interfaceName, 'Df\\') ? lcfirst(implode(df_explode_class($interfaceName))) . (!$preserveVersion ? '' : $version) : $proceed($interfaceName, $version, $preserveVersion);
 }
Example #5
0
 /**
  * 2015-12-13
  * Отличия от модифицируемого метода
  * @see \Magento\Framework\Data\Form\Element\AbstractElement::getLabelHtml():
  * 1) Добавляем свои классы для Font Awesome.
  * 2) При использовании Font Awesome не добавляем исходную подпись
  * (значением которой является класс Font Awesome)
  * и выводим, по сути, пустые теги <label><span></span></label>.
  * 3) Добавляем атрибут title.
  * 2015-12-28
  * 4) Добавляем класс, соответствующий типу элемента.
  *
  * Пример использования Font Awesome: https://github.com/mage2pro/core/tree/7cb37ab2c4d728bc20d29ca3c7c643e551f6eb0a/Framework/Data/Form/Element/Font.php#L40
  *
  * @see \Df\Framework\Form\Element\Font::onFormInitialized()
  * @see \Magento\Framework\Data\Form\Element\AbstractElement::getLabelHtml()
  * @param Sb|E $sb
  * @param \Closure $proceed
  * @param string|null $idSuffix
  * @return string
  */
 public function aroundGetLabelHtml(Sb $sb, \Closure $proceed, $idSuffix = '')
 {
     /** @var string|null|Phrase $label */
     $label = $sb->getLabel();
     /** @var string $result */
     if (is_null($label)) {
         $result = '';
     } else {
         $label = (string) $label;
         /**
          * 2015-12-25
          * @see \Magento\Framework\Data\Form\Element\Multiline::getLabelHtml()
          * имеет другое значение по-умолчанию параметра $idSuffix:
          * public function getLabelHtml($suffix = 0)
          * https://github.com/magento/magento2/blob/2.0.0/lib/internal/Magento/Framework/Data/Form/Element/Multiline.php#L59
          */
         if ('' === $idSuffix && $sb instanceof Multiline) {
             $idSuffix = 0;
         }
         /** @var bool $isFontAwesome */
         $isFontAwesome = df_starts_with($label, 'fa-');
         /** @var string[] $classA */
         $classA = ['label', 'admin__field-label', 'df-element-' . $sb->getType()];
         if ($isFontAwesome) {
             $classA[] = 'fa';
             $classA[] = $label;
             $label = '';
         }
         /** @var array(string => string) $params */
         $params = ['class' => df_cc_s($classA), 'for' => $sb->getHtmlId() . $idSuffix, 'data-ui-id' => E::uidSt($sb, 'label')];
         /** @var string $title */
         $title = (string) $sb->getTitle();
         if ($title !== $label) {
             $params['title'] = $title;
         }
         $result = df_tag('label', $params, df_tag('span', [], $label)) . "\n";
     }
     return $result;
 }
Example #6
0
/**
 * @param string $resource
 * @return \Magento\Framework\View\Asset\File
 */
function df_asset_create($resource)
{
    return !df_starts_with($resource, 'http') && !df_starts_with($resource, '//') ? df_asset()->createAsset($resource) : df_asset()->createRemoteAsset($resource, dfa(['css' => 'text/css', 'js' => 'application/javascript'], df_file_ext($resource)));
}
Example #7
0
 /**
  * Простой, неполный, но практически адекватный для моих ситуаций
  * способ опредилелить, является ли строка регулярным выражением.
  * @param string $text
  * @return string
  */
 public function isRegex($text)
 {
     return df_starts_with($text, '#');
 }
Example #8
0
/**
 * 2016-08-19
 * @see json_decode() спокойно принимает не только строки, но и числа, а также true.
 * Наша функция возвращает true, если аргумент является именно строкой.
 * @param mixed $v
 * @return bool
 */
function df_check_json_complex($v)
{
    return is_string($v) && df_starts_with($v, '{') && df_check_json($v);
}
Example #9
0
/**
 * 2016-09-01
 * Вообще говоря, заголовок у XML необязателен,
 * но моя функция @see df_xml_prettify() его добавляет,
 * поэтому меня пока данный алгоритм устраивает.
 * Более качественный алгоритм будет более ресурсоёмким: нам надо будет разбирать весь XML.
 * @param mixed $v
 * @return bool
 */
function df_check_xml($v)
{
    return is_string($v) && df_starts_with($v, '<?xml');
}
Example #10
0
 /**
 * 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());
     });
 }
Example #11
0
/**
 * 2016-03-08
 * Добавляет к строке $s приставку $head,
 * если она в этой строке отсутствует.
 * @param string $s
 * @param string $head
 * @return string
 */
function df_prepend($s, $head)
{
    return df_starts_with($s, $head) ? $s : $head . $s;
}
Example #12
0
 /**
  * 2015-12-11
  * $element->getClass() может вернуть строку вида:
  * «df-google-font df-name-family select admin__control-select».
  * Оставляем в ней только наши классы: чьи имена начинаются с df-.
  * Системные классы мы контейнеру не присваиваем,
  * потому что для классов типа .admin__control-select
  * в ядре присутствуют правила CSS, которые считают элементы с этими классами
  * элементами управления, а не контейнерами, и корёжат нам вёрстку.
  * @param AE|Element $e
  * @return string
  */
 public static function getClassDfOnly(AE $e)
 {
     return df_cc_s(array_filter(df_trim(explode(' ', $e->getClass())), function ($class) {
         return df_starts_with($class, 'df-');
     }));
 }