├── Api
└── TranslationRepositoryInterface.php
├── Block
└── Adminhtml
│ ├── Form
│ ├── Field
│ │ ├── Magento.php
│ │ ├── Mapped.php
│ │ └── YesNo.php
│ ├── IntegrationKey.php
│ └── Multiselect.php
│ └── System
│ └── Config
│ └── Form
│ └── Info.php
├── Model
├── Config.php
├── Config
│ └── Source
│ │ ├── CategoryList.php
│ │ └── SynchronizationType.php
├── GetTranslationEntitiesList.php
├── GetTranslationEntity.php
├── Mapped.php
├── System
│ └── Config
│ │ └── Backend
│ │ └── Mapped.php
├── TranslationRepository.php
└── UpdateTranslationEntity.php
├── README.md
├── Setup
└── InstallData.php
├── composer.json
├── etc
├── acl.xml
├── adminhtml
│ └── system.xml
├── config.xml
├── di.xml
├── integration
│ └── api.xml
├── module.xml
└── webapi.xml
├── registration.php
└── view
└── adminhtml
├── templates
└── integration_key.phtml
└── web
├── css
└── chosen.min.css
├── images
├── chosen-sprite.png
└── chosen-sprite@2x.png
└── js
└── chosen.jquery.min.js
/Api/TranslationRepositoryInterface.php:
--------------------------------------------------------------------------------
1 | translationEntity = $translationEntity;
29 | $this->config = $config;
30 | parent::__construct($context, $data);
31 | }
32 |
33 | /**
34 | * @param string $value
35 | * @return $this
36 | */
37 | public function setInputName($value)
38 | {
39 | return $this->setName($value);
40 | }
41 |
42 | /**
43 | * Render block HTML
44 | *
45 | * @return string
46 | */
47 | public function _toHtml()
48 | {
49 | if (!$this->getOptions()) {
50 |
51 | //$savedFields = $this->config->getMappedFields();
52 |
53 | $this->addOption(0, ' ');
54 |
55 | foreach ($this->translationEntity->getTranslatableAttributes() as $key => $translatableAttributes) {
56 | $this->addOption(
57 | '',
58 | '---- ' . ucfirst($key) . ' ---',
59 | ['disabled' => true]
60 | );
61 |
62 | foreach ($translatableAttributes as $translatableAttribute) {
63 | $params = [];
64 | // $params = isset($savedFields[$key .'/'. $translatableAttribute['code']]) ? ['disabled' => true] : [];
65 |
66 | $this->addOption(
67 | $key .'/'. $translatableAttribute['code'],
68 | ucfirst($key) . ' - ' . $translatableAttribute['label'],
69 | $params
70 | );
71 | }
72 | }
73 | }
74 |
75 | return parent::_toHtml();
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Block/Adminhtml/Form/Field/Mapped.php:
--------------------------------------------------------------------------------
1 | _groupRenderer) {
32 | $this->_groupRenderer = $this->createBlock(\Magefan\Crowdin\Block\Adminhtml\Form\Field\YesNo::class);
33 | $this->_groupRenderer->setClass('yesno_fields_select');
34 | }
35 |
36 | return $this->_groupRenderer;
37 | }
38 |
39 | /**
40 | * @return \Magento\Framework\View\Element\BlockInterface
41 | * @throws \Magento\Framework\Exception\LocalizedException
42 | */
43 | protected function _getTranslatableFiels()
44 | {
45 | if (!$this->magentoFieldsRenderer) {
46 | $this->magentoFieldsRenderer = $this->createBlock(\Magefan\Crowdin\Block\Adminhtml\Form\Field\Magento::class);
47 | $this->magentoFieldsRenderer->setClass('translatable_fields_select');
48 | }
49 | return $this->magentoFieldsRenderer;
50 | }
51 |
52 | /**
53 | * @param $object
54 | * @return \Magento\Framework\View\Element\BlockInterface
55 | * @throws \Magento\Framework\Exception\LocalizedException
56 | */
57 | protected function createBlock($object)
58 | {
59 | return $this->getLayout()->createBlock(
60 | $object,
61 | '',
62 | ['data' =>
63 | ['is_render_to_js_template' => true]
64 | ]);
65 | }
66 |
67 | /**
68 | * Prepare to render
69 | *
70 | * @return void
71 | * @throws \Magento\Framework\Exception\LocalizedException
72 | */
73 | protected function _prepareToRender()
74 | {
75 |
76 | $this->addColumn(
77 | 'magento_translatable_fields',
78 | [
79 | 'label' => __('Translatable Field'),
80 | 'renderer' => $this->_getTranslatableFiels()
81 | ]
82 | );
83 | $this->addColumn(
84 | 'is_crowdin_sync_enabled',
85 | [
86 | 'label' => __('Enable Sync'),
87 | 'renderer' => $this->_getYesNoFields()
88 | ]
89 | );
90 |
91 | $this->_addAfter = false;
92 | $this->_addButtonLabel = __('Add');
93 | }
94 |
95 | /**
96 | * @param \Magento\Framework\Data\Form\Element\AbstractElement $element
97 | * @return bool
98 | */
99 | protected function _isInheritCheckboxRequired(\Magento\Framework\Data\Form\Element\AbstractElement $element)
100 | {
101 | return false;
102 | }
103 |
104 | /**
105 | * Prepare existing row data object
106 | *
107 | * @param \Magento\Framework\DataObject $row
108 | * @return void
109 | * @throws \Magento\Framework\Exception\LocalizedException
110 | */
111 | protected function _prepareArrayRow(\Magento\Framework\DataObject $row)
112 | {
113 | $optionExtraAttr = [];
114 |
115 | $optionExtraAttr['option_' . $this->_getTranslatableFiels()->calcOptionHash($row->getData('magento_translatable_fields'))] = 'selected="selected"';
116 | $optionExtraAttr['option_' . $this->_getYesNoFields()->calcOptionHash($row->getData('is_crowdin_sync_enabled'))] = 'selected="selected"';
117 |
118 | $row->setData('option_extra_attrs', $optionExtraAttr);
119 | }
120 |
121 | /**
122 | * @return \Magento\Framework\Phrase|string
123 | * @throws \Exception
124 | */
125 | protected function _toHtml()
126 | {
127 | try {
128 | return parent::_toHtml();
129 | } catch (\Mautic\Exception\RequiredParameterMissingException $e) {
130 | return __(
131 | 'Cannot Display Translatable Fields .',
132 | $this->escapeHtml($e->getMessage())
133 | );
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/Block/Adminhtml/Form/Field/YesNo.php:
--------------------------------------------------------------------------------
1 | 'yes',
43 | 0 => 'no'
44 | ];
45 | }
46 |
47 | /**
48 | * @param string $value
49 | * @return $this
50 | */
51 | public function setInputName($value)
52 | {
53 | return $this->setName($value);
54 | }
55 |
56 | /**
57 | * @return string
58 | * @throws \Mautic\Exception\RequiredParameterMissingException
59 | */
60 | protected function _toHtml()
61 | {
62 | if (!$this->getOptions()) {
63 | foreach ($this->_getYesNoFields() as $groupId => $groupLabel) {
64 | $this->addOption($groupId, addslashes($groupLabel));
65 | }
66 | }
67 |
68 | return parent::_toHtml();
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Block/Adminhtml/Form/IntegrationKey.php:
--------------------------------------------------------------------------------
1 | integrationService = $integrationService;
41 | }
42 |
43 | /**
44 | * Set template to itself
45 | *
46 | * @return $this
47 | */
48 | protected function _prepareLayout()
49 | {
50 | parent::_prepareLayout();
51 | if (!$this->getTemplate()) {
52 | $this->setTemplate(static::BUTTON_TEMPLATE);
53 | }
54 | return $this;
55 | }
56 |
57 | /**
58 | * Render button
59 | *
60 | * @param \Magento\Framework\Data\Form\Element\AbstractElement $element
61 | * @return string
62 | */
63 | public function render(\Magento\Framework\Data\Form\Element\AbstractElement $element)
64 | {
65 | $element->unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue();
66 | return parent::render($element);
67 | }
68 |
69 | /**
70 | *
71 | * @param \Magento\Framework\Data\Form\Element\AbstractElement $element
72 | * @return string
73 | */
74 | protected function _getElementHtml(\Magento\Framework\Data\Form\Element\AbstractElement $element)
75 | {
76 | return $this->_toHtml() . $this->getIntegrationKey();
77 |
78 | }
79 |
80 | /**
81 | * @return bool
82 | */
83 | public function isIntegrationActivated(): bool
84 | {
85 | return (bool)$this->getIntegration()->getStatus();
86 | }
87 |
88 | /**
89 | * @return mixed
90 | */
91 | private function getIntegration()
92 | {
93 | if (is_null($this->integration)) {
94 | $this->integration = $this->integrationService->get($this->integrationService->findByName(Config::INTEGRATION_NAME)->getId());
95 | }
96 |
97 | return $this->integration;
98 | }
99 |
100 | /**
101 | * @return string
102 | */
103 | private function getIntegrationKey(): string
104 | {
105 | $result = '';
106 | $integrationId = $this->integrationService->findByName(Config::INTEGRATION_NAME)->getId();
107 | $integration = $this->integrationService->get($integrationId);
108 |
109 | if ($this->isIntegrationActivated()) {
110 | $key = implode(
111 | '-',
112 | [
113 | $integration->getData('consumer_key'),
114 | $integration->getData('consumer_secret'),
115 | $integration->getData('token'),
116 | $integration->getData('token_secret'),
117 | ]
118 | );
119 |
120 | $result = ' ';
121 | }
122 |
123 | return $result;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/Block/Adminhtml/Form/Multiselect.php:
--------------------------------------------------------------------------------
1 | getViewFileUrl('Magefan_Crowdin::css/chosen.min.css') . "\">
23 |
27 | ";
37 | }
38 | }
--------------------------------------------------------------------------------
/Block/Adminhtml/System/Config/Form/Info.php:
--------------------------------------------------------------------------------
1 | moduleList = $moduleList;
32 | }
33 |
34 | /**
35 | * Return info block html
36 | * @param \Magento\Framework\Data\Form\Element\AbstractElement $element
37 | * @return string
38 | */
39 | public function render(\Magento\Framework\Data\Form\Element\AbstractElement $element)
40 | {
41 | $useUrl = true;
42 | $m = $this->moduleList->getOne($this->getModuleName());
43 | $html = '
44 | ' . $this->escapeHtml($this->getModuleTitle()) . ' v' . $this->escapeHtml($m['setup_version']) . ' was developed by ';
45 | if ($useUrl) {
46 | $html .= '
Magefan ';
47 | } else {
48 | $html .= '
Magefan ';
49 | }
50 |
51 | $html .= '
';
52 | $html .= '
Crowdin ';
53 | $html .= ' is a cloud-based localization management software with 2M+ user accounts and 130K+ localization projects that helps teams and companies reach new markets. People from over 160 countries use Crowdin to translate & localize their software, mobile and desktop apps, games, websites, help articles, marketing communication, and other content.';
54 | $html .= '
';
55 |
56 | return $html;
57 | }
58 |
59 | /**
60 | * Return extension url
61 | * @return string
62 | */
63 | protected function getModuleUrl()
64 | {
65 | return 'https://mage' . 'fan.com/magento2-extensions?utm_source=m2admin_crowdin_config&utm_medium=link&utm_campaign=regular';
66 | }
67 |
68 | /**
69 | * Return extension title
70 | * @return string
71 | */
72 | protected function getModuleTitle()
73 | {
74 | return 'Crowdin Integration Extension';
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Model/Config.php:
--------------------------------------------------------------------------------
1 | scopeConfig = $scopeConfig;
42 | }
43 |
44 | /**
45 | * Retrieve true if module is enabled
46 | *
47 | * @return bool
48 | */
49 | public function isEnabled($storeId = null): bool
50 | {
51 | return (bool)$this->getConfig(self::XML_PATH_EXTENSION_ENABLED, $storeId);
52 | }
53 |
54 | /**
55 | * @param null $storeId
56 | * @return array
57 | */
58 | public function getMappedFields($storeId = null): array
59 | {
60 | return (array)json_decode((string)$this->getConfig(self::XML_PATH_FIELDS_MAPPING, $storeId), true);
61 | }
62 |
63 | public function getLocaleByStoreId($storeId = null)
64 | {
65 | return (string) $this->getConfig('general/locale/code', $storeId);
66 | }
67 |
68 | /**
69 | * Retrieve store config value
70 | * @param string $path
71 | * @param null $storeId
72 | * @return mixed
73 | */
74 | public function getConfig($path, $storeId = null)
75 | {
76 | return $this->scopeConfig->getValue(
77 | $path,
78 | ScopeInterface::SCOPE_STORE,
79 | $storeId
80 | );
81 | }
82 |
83 | /**
84 | * @param null $storeId
85 | * @return int
86 | */
87 | public function getCatalogSynchronizationOption($storeId = null): int
88 | {
89 | return (int)$this->getConfig(self::XML_PATH_CATALOG_SYNCHRONIZATION, $storeId);
90 | }
91 |
92 | /**
93 | * @param null $storeId
94 | * @return array
95 | */
96 | public function getCatalogSynchronizationValues($storeId = null)
97 | {
98 | return explode(',', $this->getConfig(self::XML_PATH_CATALOG_SPECIFIC_SYNCHRONIZATION, $storeId));
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/Model/Config/Source/CategoryList.php:
--------------------------------------------------------------------------------
1 | categoryFactory = $categoryFactory;
39 | $this->categoryCollectionFactory = $categoryCollectionFactory;
40 | }
41 |
42 | /**
43 | * @return array
44 | * @throws LocalizedException
45 | */
46 | public function toOptionArray()
47 | {
48 | $preparedDataArray = $this->toArray();
49 | $options = [];
50 | foreach ($preparedDataArray as $key => $value) {
51 | $options[] = [
52 | 'value' => $key,
53 | 'label' => $value
54 | ];
55 | }
56 |
57 | return $options;
58 | }
59 |
60 | /**
61 | * @return array
62 | * @throws LocalizedException
63 | */
64 | public function toArray()
65 | {
66 | $categories = $this->getCategoryCollection();
67 |
68 | $categoryList = [];
69 | foreach ($categories as $category) {
70 | $categoryList[$category->getEntityId()] = [
71 | 'name' => $category->getName(),
72 | 'path' => $category->getPath(),
73 | 'cat_id' => $category->getId()
74 | ];
75 | }
76 |
77 | $categoryArray = [];
78 | foreach ($categoryList as $key => $value) {
79 | if ($path = $this->getCategoryPath($value['path'], $categoryList)) {
80 | $categoryArray[$key] = $path . ' (ID: ' . $value['cat_id'] . ')';
81 | }
82 | }
83 |
84 | asort($categoryArray);
85 |
86 | return $categoryArray;
87 | }
88 |
89 | /**
90 | * @return Collection
91 | * @throws LocalizedException
92 | */
93 | public function getCategoryCollection()
94 | {
95 | $collection = $this->categoryCollectionFactory->create();
96 | $collection->addAttributeToSelect(['path', 'name']);
97 |
98 | return $collection;
99 | }
100 |
101 | /**
102 | * @param $path
103 | * @param $categoryList
104 | *
105 | * @return string
106 | */
107 | public function getCategoryPath($path, $categoryList)
108 | {
109 | $categoryPath = [];
110 | $rootCategories = [1, 2];
111 | $path = explode('/', $path);
112 |
113 | if ($path) {
114 | foreach ($path as $categoryId) {
115 | if (!in_array($categoryId, $rootCategories)) {
116 | if (!empty($categoryList[$categoryId]['name'])) {
117 | $categoryPath[] = $categoryList[$categoryId]['name'];
118 | }
119 | }
120 | }
121 | }
122 |
123 | if (!empty($categoryPath)) {
124 | return implode(' » ', $categoryPath);
125 | }
126 |
127 | return false;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/Model/Config/Source/SynchronizationType.php:
--------------------------------------------------------------------------------
1 | 0, 'label' => __(' All (default)')],
18 | ['value' => 1, 'label' => __('Specific categories and products')],
19 | ['value' => 2, 'label' => __('All except specific categories and products')]
20 | ];
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Model/GetTranslationEntitiesList.php:
--------------------------------------------------------------------------------
1 | self::CATEGORY_ENTITY,
51 | self::PRODUCTS => self::PRODUCT_ENTITY,
52 | self::EMAILS => self::EMAIL_ENTITY,
53 | self::CMS_PAGES => self::CMS_PAGE_ENTITY,
54 | self::CMS_BLOCKS => self::CMS_BLOCK_ENTITY,
55 | self::PRODUCT_ATTRIBUTES => self::PRODUCT_ATTRIBUTE_ENTITY,
56 | ];
57 |
58 | /**
59 | * @var ProductRepository
60 | */
61 | protected $productRepository;
62 |
63 | /**
64 | * @var EmailCollectionFactory
65 | */
66 | protected $emailCollectionFactory;
67 |
68 | /**
69 | * @var SearchCriteriaBuilder
70 | */
71 | protected $searchCriteriaBuilder;
72 |
73 | /**
74 | * @var FilterBuilder
75 | */
76 | protected $filterBuilder;
77 |
78 | /**
79 | * @var Attribute
80 | */
81 | protected $attributeFactory;
82 |
83 | /**
84 | * @var CategoryAttributeRepositoryInterface
85 | */
86 | protected $categoryAttributeRepository;
87 |
88 | /**
89 | * @var ProductAttributeRepositoryInterface
90 | */
91 | protected $productAttributeRepository;
92 |
93 | /**
94 | * @var FilterGroupBuilder
95 | */
96 | protected $filterGroupBuilder;
97 |
98 | /**
99 | * @var PageRepositoryInterface
100 | */
101 | protected $pageRepository;
102 |
103 | /**
104 | * @var BlockRepositoryInterface
105 | */
106 | protected $blockRepository;
107 |
108 | /**
109 | * @var Config
110 | */
111 | private $config;
112 |
113 | /**
114 | * @var \string[][]
115 | */
116 | private $entitiesList = [
117 | [
118 | 'name' => 'Products',
119 | 'id' => self::PRODUCTS
120 | ],
121 | [
122 | 'name' => 'Email Templates',
123 | 'id' => self::EMAILS
124 | ],
125 | [
126 | 'name' => 'Product Attributes',
127 | 'id' => self::PRODUCT_ATTRIBUTES
128 | ],
129 | [
130 | 'name' => 'CMS Pages',
131 | 'id' => self::CMS_PAGES
132 | ],
133 | [
134 | 'name' => 'CMS Block',
135 | 'id' => self::CMS_BLOCKS
136 | ]
137 | ];
138 |
139 | /**
140 | * @param CategoryCollectionFactory $categoryCollectionFactory
141 | * @param ProductRepository $productRepository
142 | * @param EmailCollectionFactory $emailCollectionFactory
143 | * @param SearchCriteriaBuilder $searchCriteriaBuilder
144 | * @param FilterBuilder $filterBuilder
145 | * @param Attribute $attributeFactory
146 | * @param CategoryAttributeRepositoryInterface $categoryAttributeRepository
147 | * @param ProductAttributeRepositoryInterface $productAttributeRepository
148 | * @param FilterGroupBuilder $filterGroupBuilder
149 | * @param PageRepositoryInterface $pageRepository
150 | * @param BlockRepositoryInterface $blockRepository
151 | * @param Config $config
152 | */
153 | public function __construct(
154 | CategoryCollectionFactory $categoryCollectionFactory,
155 | ProductRepository $productRepository,
156 | EmailCollectionFactory $emailCollectionFactory,
157 | SearchCriteriaBuilder $searchCriteriaBuilder,
158 | FilterBuilder $filterBuilder,
159 | Attribute $attributeFactory,
160 | CategoryAttributeRepositoryInterface $categoryAttributeRepository,
161 | ProductAttributeRepositoryInterface $productAttributeRepository,
162 | FilterGroupBuilder $filterGroupBuilder,
163 | PageRepositoryInterface $pageRepository,
164 | BlockRepositoryInterface $blockRepository,
165 | Config $config
166 | ) {
167 | $this->categoryCollectionFactory = $categoryCollectionFactory;
168 | $this->productRepository = $productRepository;
169 | $this->emailCollectionFactory = $emailCollectionFactory;
170 | $this->searchCriteriaBuilder = $searchCriteriaBuilder;
171 | $this->filterBuilder = $filterBuilder;
172 | $this->attributeFactory = $attributeFactory;
173 | $this->categoryAttributeRepository = $categoryAttributeRepository;
174 | $this->productAttributeRepository = $productAttributeRepository;
175 | $this->filterGroupBuilder = $filterGroupBuilder;
176 | $this->pageRepository = $pageRepository;
177 | $this->blockRepository = $blockRepository;
178 | $this->config = $config;
179 | }
180 |
181 | /**
182 | * @return array
183 | */
184 | public function execute($storeId): array
185 | {
186 | $this->storeId = $storeId;
187 |
188 | $this->addCategoriesEntities();
189 | $this->addProductsEntities();
190 | $this->addEmailsEntities();
191 | $this->addCmsPagesEntities();
192 | $this->addCmsBlocksEntities();
193 | $this->addProductAttributesEntities();
194 |
195 | return (array) $this->entitiesList;
196 | }
197 |
198 | private function addCategoriesEntities()
199 | {
200 |
201 | $categoryCollection = $this->categoryCollectionFactory->create()->addAttributeToSelect('name');
202 |
203 | if ($option = $this->config->getCatalogSynchronizationOption()){
204 |
205 | $categoriesIds = $this->config->getCatalogSynchronizationValues();
206 | $categoryCollection->addAttributeToFilter('entity_id', array($option != 1 ? 'nin' : 'in' => $categoriesIds));
207 | }
208 |
209 | foreach ($categoryCollection as $item) {
210 | $isRootCategory = ((int)$item->getLevel() === 0);
211 | $parentId = (((int)$item->getLevel()) === 1) ? self::CATEGORY_ENTITY : self::CATEGORY_ENTITY. '_' . $item->getParentId();
212 |
213 | $category = [
214 | 'name' => $isRootCategory ? __('Categories') : $item->getName(),
215 | 'id' => $isRootCategory ? self::CATEGORY_ENTITY : self::CATEGORY_ENTITY . '_' . $item->getId(),
216 | 'parentId' => $parentId,
217 | 'type' => 'html'
218 | ];
219 |
220 | if ($isRootCategory) {
221 | unset($category['parentId']);
222 | }
223 |
224 | $this->entitiesList[] = $category;
225 | }
226 | }
227 |
228 | private function addProductsEntities()
229 | {
230 | $filters = [];
231 | $filters[] = $this->filterBuilder
232 | ->setField('store_id')
233 | ->setConditionType('eq')
234 | ->setValue(0)
235 | ->create();
236 |
237 | if ($option = $this->config->getCatalogSynchronizationOption()){
238 |
239 | $categoriesIds = $this->config->getCatalogSynchronizationValues();
240 | $categoryFilter = $this->filterBuilder->setField('category_id')
241 | ->setValue($categoriesIds)
242 | ->setConditionType($option != 1 ? 'nin' : 'in')
243 | ->create();
244 | $filters[] = $categoryFilter;
245 | }
246 |
247 | $searchCriteria = $this->searchCriteriaBuilder->addFilters($filters)->create();
248 |
249 | $this->addEntityToResponse(self::PRODUCTS, $this->productRepository->getList($searchCriteria)->getItems());
250 | }
251 |
252 | /**
253 | * @param string $type
254 | * @param $collection
255 | */
256 | private function addEntityToResponse(string $type, $collection)
257 | {
258 | foreach ($collection as $item) {
259 | $itemId = self::ENTITY_TYPES[$type] . '_' . $item->getId();
260 | $this->entitiesList[] = [
261 | 'name' => $item->getName(),
262 | 'id' => $itemId,
263 | 'parentId' => $type,
264 | 'type' => 'html',
265 | ];
266 | }
267 | }
268 |
269 | private function addEmailsEntities()
270 | {
271 | $emailCollection = $this->emailCollectionFactory->create();
272 |
273 | foreach ($emailCollection->getItems() as $templates) {
274 | $this->entitiesList[] = [
275 | 'name' => $templates->getTemplateCode(),
276 | 'id' => self::EMAIL_ENTITY . '_' . $templates->getTemplateId(),
277 | 'parentId' => self::EMAILS,
278 | 'type' => 'html'
279 | ];
280 | }
281 | }
282 |
283 | private function addCmsPagesEntities()
284 | {
285 | $this->addCmsEntity($this->pageRepository, self::CMS_PAGE_ENTITY, self::CMS_PAGES);
286 | }
287 |
288 | private function addCmsBlocksEntities()
289 | {
290 | $this->addCmsEntity($this->blockRepository, self::CMS_BLOCK_ENTITY, self::CMS_BLOCKS);
291 | }
292 |
293 | private function addCmsEntity($repository, $cmsEntityType, $parentId)
294 | {
295 | $cmsEntitiesForAllStores = $this->getCmsEntitiesByStore($repository, 0);
296 | $cmsEntitiesForCurrentStore = $this->getCmsEntitiesByStore($repository, $this->storeId);
297 | $cmsEntities = [];
298 |
299 | foreach (array_merge($cmsEntitiesForAllStores, $cmsEntitiesForCurrentStore) as $cmsEntity) {
300 | $cmsEntities[$cmsEntity->getIdentifier()] = [
301 | 'name' => $cmsEntity->getTitle(),
302 | 'id' => $cmsEntityType . '_' . $cmsEntity->getIdentifier(),
303 | 'parentId' => $parentId,
304 | 'type' => 'html'
305 | ];
306 | }
307 |
308 | foreach ($cmsEntities as $cmsEntity) {
309 | $this->entitiesList[] = $cmsEntity;
310 | }
311 | }
312 |
313 | /**
314 | * @param $repository
315 | * @param $storeId
316 | * @return mixed
317 | */
318 | private function getCmsEntitiesByStore($repository, $storeId)
319 | {
320 | $this->searchCriteriaBuilder->addFilters(
321 | [
322 | $this->filterBuilder
323 | ->setField('store_id')
324 | ->setConditionType('eq')
325 | ->setValue($storeId)
326 | ->create()
327 | ]
328 | );
329 |
330 | return $repository->getList($this->searchCriteriaBuilder->create())->getItems();
331 | }
332 |
333 | private function addProductAttributesEntities()
334 | {
335 | //$productAttributesList = $this->productAttributeRepository->getList($this->searchCriteriaBuilder->create())->getItems();
336 | $this->getProductAttributes($this->productAttributeRepository);
337 | }
338 |
339 | /**
340 | * Return attributes that can be translated and not already in product entity
341 | * @param $repository
342 | */
343 | public function getProductAttributes($repository)
344 | {
345 | $inputFilter = $this->filterBuilder
346 | ->setField('frontend_input')
347 | ->setConditionType('in')
348 | ->setValue(['multiselect', 'select'])
349 | ->create();
350 |
351 | $isUserDefinedFilter = $this->filterBuilder
352 | ->setField('is_user_defined')
353 | ->setConditionType('eq')
354 | ->setValue(1)
355 | ->create();
356 |
357 | $inputGroup = $this->filterGroupBuilder->addFilter($inputFilter)->create();
358 | $isUserDefinedGroup = $this->filterGroupBuilder->addFilter($isUserDefinedFilter)->create();
359 |
360 | $searchCriteria = $this->searchCriteriaBuilder->setFilterGroups([$inputGroup, $isUserDefinedGroup])->create();
361 | $attributesList = $repository->getList($searchCriteria)->getItems();
362 |
363 | foreach ($attributesList as $attribute) {
364 | $this->entitiesList[] = [
365 | 'name' => $attribute->getDefaultFrontendLabel() ?: $attribute->getAttributeCode() . ' [label not set]',
366 | 'id' => self::PRODUCT_ATTRIBUTE_ENTITY . '_' . $attribute->getId(),
367 | 'parentId' => self::PRODUCT_ATTRIBUTES,
368 | 'type' => 'html'
369 | ];
370 | }
371 | }
372 | }
373 |
--------------------------------------------------------------------------------
/Model/GetTranslationEntity.php:
--------------------------------------------------------------------------------
1 | categoryCollectionFactory = $categoryCollectionFactory;
163 | $this->searchCriteriaBuilder = $searchCriteriaBuilder;
164 | $this->filterBuilder = $filterBuilder;
165 | $this->attributeFactory = $attributeFactory;
166 | $this->categoryAttributeRepository = $categoryAttributeRepository;
167 | $this->productAttributeRepository = $productAttributeRepository;
168 | $this->filterGroupBuilder = $filterGroupBuilder;
169 | $this->categoryRepository = $categoryRepository;
170 | $this->productRepository = $productRepository;
171 | $this->storeManager = $storeManager;
172 | $this->store = $store;
173 | $this->config = $config;
174 | $this->emailTemplate = $emailTemplate;
175 | $this->pageRepository = $pageRepository;
176 | $this->blockRepository = $blockRepository;
177 | $this->attributeOptionManagement = $attributeOptionManagement;
178 | $this->emulation = $emulation;
179 | }
180 |
181 | /**
182 | * @param $id
183 | * @return array|void
184 | */
185 | public function execute($id)
186 | {
187 | $response = ['success' => true];
188 | $entityData = explode('_', $id);
189 | $type = $entityData[0];
190 |
191 | unset($entityData[0]);
192 |
193 | $id = implode('_', $entityData);
194 |
195 | if (trim((string)$id) === '') {
196 | return ['message' => 'Missed entity id!', 'success' => false];
197 | }
198 |
199 | if (!in_array($type, $this->allowedStringId) && !is_numeric($id)) {
200 | return ['message' => 'Entity id (' . $id . ') for this entity should be numeric!', 'success' => false];
201 | }
202 |
203 | switch ($type) {
204 | case GetTranslationEntitiesList::CATEGORY_ENTITY:
205 | $response = $this->getCategoryData((int)$id);
206 | break;
207 | case GetTranslationEntitiesList::PRODUCT_ENTITY:
208 | $response = $this->getProductData((int)$id);
209 | break;
210 | case GetTranslationEntitiesList::EMAIL_ENTITY:
211 | $response = $this->getEmailTemplateData((int)$id);
212 | break;
213 | case GetTranslationEntitiesList::CMS_PAGE_ENTITY:
214 | $response = $this->getCmsPageData($id);
215 | break;
216 | case GetTranslationEntitiesList::CMS_BLOCK_ENTITY:
217 | $response = $this->getCmsBlockData($id);
218 | break;
219 | case GetTranslationEntitiesList::PRODUCT_ATTRIBUTE_ENTITY:
220 | $response = $this->getProductAttributeData((int)$id);
221 | break;
222 | }
223 |
224 | return $response;
225 | }
226 |
227 | /**
228 | * @param int $id
229 | * @return array
230 | */
231 | private function getCategoryData(int $id): array
232 | {
233 | $data = [];
234 |
235 | $attributes = $this->removeNotAllowedFields($this->getCatalogAttributes($this->categoryAttributeRepository), 'category');
236 |
237 | foreach ($this->storeManager->getStores() as $store) {
238 | if (!$store->getIsActive()) {
239 | continue;
240 | }
241 |
242 | $storeId = $store->getId();
243 | $category = $this->categoryRepository->get($id, $store->getId());
244 |
245 | foreach ($attributes as $attribute) {
246 | $data[$storeId][$attribute['code']] = $category->getData($attribute['code']);
247 | }
248 |
249 | $data[$storeId]['store_id'] = $storeId;
250 | }
251 |
252 | return $data;
253 | }
254 |
255 | /**
256 | * @return array
257 | */
258 | public function getTranslatableAttributes(): array
259 | {
260 | $attributes = [];
261 | $attributes['category'] = $this->getCatalogAttributes($this->categoryAttributeRepository);
262 | $attributes['product'] = $this->getCatalogAttributes($this->productAttributeRepository);
263 | $attributes['email'] = $this->getEmailAttributes();
264 | $attributes['page'] = $this->getCmsAttributesByType('page');
265 | $attributes['block'] = $this->getCmsAttributesByType('block');
266 |
267 | return $attributes;
268 | }
269 |
270 | /**
271 | * @param int $id
272 | * @return array
273 | */
274 | private function getProductData(int $id): array
275 | {
276 | $data = [];
277 |
278 | $attributes = $this->removeNotAllowedFields($this->getCatalogAttributes($this->productAttributeRepository), 'product');
279 |
280 | foreach ($this->storeManager->getStores() as $store) {
281 | if (!$store->getIsActive()) {
282 | continue;
283 | }
284 |
285 | $storeId = $store->getId();
286 | $product = $this->productRepository->getById($id, false, $storeId);
287 |
288 | foreach ($attributes as $attribute) {
289 | $data[$storeId][$attribute['code']] = $product->getData($attribute['code']);
290 | }
291 |
292 | $data[$storeId]['store_id'] = $storeId;
293 | }
294 |
295 | return $data;
296 | }
297 |
298 | /**
299 | * @param int $id
300 | * @return array
301 | */
302 | private function getEmailTemplateData(int $id): array
303 | {
304 | $data = [];
305 | $attributes = $this->removeNotAllowedFields($this->getEmailAttributes(), 'email');
306 |
307 | $emailTemplate = $this->emailTemplate->load($id);
308 |
309 | if (!$emailTemplate->getId()) {
310 | return ['message' => __('Entity with id %1 does not exist!', $id), 'success' => false];
311 | }
312 |
313 | foreach ($this->storeManager->getStores() as $store) {
314 | if (!$store->getIsActive()) {
315 | continue;
316 | }
317 |
318 | $storeId = $store->getId();
319 |
320 | foreach ($attributes as $attribute) {
321 | $data[$storeId][$attribute['code']] = $emailTemplate->getData($attribute['code']);
322 | }
323 |
324 | $data[$storeId]['store_id'] = $storeId;
325 | }
326 |
327 | return $data;
328 | }
329 |
330 | /**
331 | * @param string $identifier
332 | * @return array
333 | */
334 | private function getCmsPageData(string $identifier): array
335 | {
336 | return $this->getCmsEntityData($this->pageRepository, 'page', $identifier);
337 | }
338 |
339 | /**
340 | * @param string $identifier
341 | * @return array
342 | */
343 | private function getCmsBlockData(string $identifier): array
344 | {
345 | return $this->getCmsEntityData($this->blockRepository, 'block', $identifier);
346 | }
347 |
348 | /**
349 | * @param $repository
350 | * @param string $entityType
351 | * @param string $identifier
352 | * @return array
353 | */
354 | private function getCmsEntityData($repository, string $entityType, string $identifier): array
355 | {
356 |
357 | $stores = [0];
358 | $data = [];
359 | $attributes = $this->removeNotAllowedFields($this->getCmsAttributesByType($entityType), $entityType);
360 |
361 | foreach ($this->storeManager->getStores() as $store) {
362 | //if ($store->getIsActive())
363 | $stores[$store->getId()] = $store->getId();
364 | }
365 |
366 | foreach ($stores as $storeId) {
367 | $cmsEntity = $this->getCmsEntityByIdentifier($identifier, $repository, $storeId);
368 |
369 | if (!$cmsEntity) {
370 | if (isset($data[0])) {
371 | $data[$storeId] = $data[0];
372 | }
373 |
374 | continue;
375 | }
376 |
377 | foreach ($attributes as $attribute) {
378 | $data[$storeId][$attribute['code']] = $cmsEntity->getData($attribute['code']);
379 | }
380 |
381 | $data[$storeId]['store_id'] = $storeId;
382 | }
383 |
384 | return $data;
385 | }
386 |
387 | /**
388 | * @param $identifier
389 | * @param $repository
390 | * @param $storeId
391 | * @return false|mixed
392 | */
393 | private function getCmsEntityByIdentifier($identifier, $repository, $storeId)
394 | {
395 | $storeFilter = $this->filterBuilder
396 | ->setField('store_id')
397 | ->setConditionType('eq')
398 | ->setValue($storeId)
399 | ->create();
400 |
401 | $identifierFilter = $this->filterBuilder
402 | ->setField('identifier')
403 | ->setConditionType('eq')
404 | ->setValue($identifier)
405 | ->create();
406 |
407 | $storeGroup = $this->filterGroupBuilder->addFilter($storeFilter)->create();
408 | $identifierGroup = $this->filterGroupBuilder->addFilter($identifierFilter)->create();
409 |
410 | $searchCriteria = $this->searchCriteriaBuilder->setFilterGroups([$storeGroup, $identifierGroup])->create();
411 | $cmsEntity = $repository->getList($searchCriteria)->getItems();
412 |
413 | return reset($cmsEntity);
414 | }
415 |
416 | /**
417 | * @param int $id
418 | * @return array
419 | */
420 | private function getProductAttributeData(int $id): array
421 | {
422 | $baseOptionLabel = $this->getBaseAttributesOptionLabels($id);
423 |
424 | $data = [];
425 |
426 | foreach ($this->storeManager->getStores() as $store) {
427 | if (!$store->getIsActive() || !$store->getId()) {
428 | continue;
429 | }
430 |
431 | $storeId = $store->getId();
432 |
433 | $this->emulation->startEnvironmentEmulation($storeId);
434 |
435 | $attributeCode = $this->productAttributeRepository->get($id)->getAttributeCode();
436 | $attributeOptions = $this->attributeOptionManagement->getItems($attributeCode);
437 |
438 | foreach ($attributeOptions as $option) {
439 |
440 | if (!$option->getValue()) {
441 | continue;
442 | }
443 |
444 | $labelInCrowdinForOriginalText = $baseOptionLabel[$option->getValue()];
445 | $labelInCrowdinForOriginalText = str_replace('&', 'and', $labelInCrowdinForOriginalText);
446 | $labelInCrowdinForOriginalText = preg_replace("/[^A-Za-z0-9]/", '_', $labelInCrowdinForOriginalText);
447 |
448 |
449 | $data[$storeId][$labelInCrowdinForOriginalText] = (string)$option->getLabel();
450 | }
451 |
452 | $data[$storeId]['store_id'] = $storeId;
453 |
454 | $this->emulation->stopEnvironmentEmulation();
455 | }
456 |
457 | return $data;
458 | }
459 |
460 | /**
461 | * @param int $attributeId
462 | * @return array
463 | */
464 | private function getBaseAttributesOptionLabels(int $attributeId): array
465 | {
466 | $this->emulation->startEnvironmentEmulation(0);
467 | $attributeCode = $this->productAttributeRepository->get($attributeId)->getAttributeCode();
468 |
469 |
470 | $attributeOptions = $this->attributeOptionManagement->getItems($attributeCode);
471 | $baseOptionLabel = [];
472 |
473 | foreach ($attributeOptions as $option) {
474 | if (!$option->getValue()) {
475 | continue;
476 | }
477 |
478 | $baseOptionLabel[$option->getValue()] = (string)$option->getLabel();
479 | }
480 | $this->emulation->stopEnvironmentEmulation();
481 |
482 | return $baseOptionLabel;
483 | }
484 |
485 | /**
486 | * @param $entityType
487 | * @return array[]
488 | */
489 | private function getCmsAttributesByType($entityType): array
490 | {
491 | $cmsAttributes = [
492 | 'page' => [
493 | ['label' => __('Title'), 'code' => 'title', 'type' => 'text'],
494 | ['label' => __('Content Heading'), 'code' => 'content_heading', 'type' => 'text'],
495 | ['label' => __('Content'), 'code' => 'content', 'type' => 'html'],
496 | ['label' => __('Meta Title'), 'code' => 'meta_title', 'type' => 'html'],
497 | ['label' => __('Meta Description'), 'code' => 'meta_description', 'type' => 'html'],
498 | ['label' => __('Meta Keywords'), 'code' => 'meta_keywords', 'type' => 'html']
499 | ],
500 | 'block' => [
501 | ['label' => __('Title'), 'code' => 'title', 'type' => 'text'],
502 | ['label' => __('Content'), 'code' => 'content', 'type' => 'html']
503 | ]
504 | ];
505 |
506 | return $cmsAttributes[$entityType];
507 | }
508 |
509 | /**
510 | * @return array[]
511 | */
512 | private function getEmailAttributes(): array
513 | {
514 | return [
515 | ['label' => __('Template Name'), 'code' => 'template_code', 'type' => 'text'],
516 | ['label' => __('Template Subject'), 'code' => 'template_subject', 'type' => 'text'],
517 | ['label' => __('Template Text'), 'code' => 'template_text', 'type' => 'html']
518 | ];
519 | }
520 |
521 | /**
522 | * @param $repository
523 | * @return array
524 | */
525 | private function getCatalogAttributes($repository): array
526 | {
527 | $inputFilter = $this->filterBuilder
528 | ->setField('frontend_input')
529 | ->setConditionType('in')
530 | ->setValue(['text', 'textarea'])
531 | ->create();
532 |
533 | $typeFilter = $this->filterBuilder
534 | ->setField('backend_type')
535 | ->setConditionType('in')
536 | ->setValue(['varchar', 'text'])
537 | ->create();
538 |
539 | $visibilityFilter = $this->filterBuilder
540 | ->setField('is_visible')
541 | ->setConditionType('eq')
542 | ->setValue(1)
543 | ->create();
544 |
545 | $inputGroup = $this->filterGroupBuilder->addFilter($inputFilter)->create();
546 | $typeGroup = $this->filterGroupBuilder->addFilter($typeFilter)->create();
547 | $visibilityGroup = $this->filterGroupBuilder->addFilter($visibilityFilter)->create();
548 |
549 | $searchCriteria = $this->searchCriteriaBuilder->setFilterGroups([$inputGroup, $typeGroup, $visibilityGroup])->create();
550 | $attributesList = $repository->getList($searchCriteria)->getItems();
551 |
552 | $attributes = [];
553 |
554 | foreach ($attributesList as $attribute) {
555 | $type = ($attribute->getFrontendInput() == 'textarea') ? 'html' : 'text';
556 |
557 | $attributes[] = [
558 | 'label' => $attribute->getDefaultFrontendLabel(),
559 | 'code' => $attribute->getAttributeCode() ,
560 | 'type' => $type,
561 | ];
562 | }
563 |
564 | return $attributes;
565 | }
566 |
567 | /**
568 | * @param array $fields
569 | * @param string $entityType
570 | * @return array
571 | */
572 | private function removeNotAllowedFields(array $fields, string $entityType): array
573 | {
574 | $mappedFields = $this->config->getMappedFields();
575 |
576 | foreach ($fields as $key => $attribute) {
577 |
578 | $attributeKey = $entityType . '/' . $attribute['code'];
579 |
580 | if (isset($mappedFields[$attributeKey]) && $mappedFields[$attributeKey] === '0') {
581 | unset($fields[$key]);
582 | }
583 | }
584 |
585 | return $fields;
586 | }
587 | }
588 |
--------------------------------------------------------------------------------
/Model/Mapped.php:
--------------------------------------------------------------------------------
1 | mathRandom = $mathRandom;
29 | }
30 |
31 | /**
32 | * Generate a storable representation of a value
33 | *
34 | * @param int|float|string|array $value
35 | * @return string
36 | */
37 | public function serializeValue($value)
38 | {
39 | if (is_numeric($value)) {
40 | $data = (float) $value;
41 | return (string) $data;
42 | } elseif (is_array($value)) {
43 | $data = [];
44 | foreach ($value as $attributeCode => $isSyncEnabled) {
45 | if (!array_key_exists($attributeCode, $data)) {
46 | $data[$attributeCode] = $isSyncEnabled;
47 | }
48 | }
49 | return json_encode($data, JSON_UNESCAPED_UNICODE);
50 | } else {
51 | return '';
52 | }
53 | }
54 |
55 | /**
56 | * Create a value from a storable representation
57 | *
58 | * @param int|float|string $value
59 | * @return array
60 | */
61 | public function unserializeValue($value)
62 | {
63 | if (is_string($value) && !empty($value)) {
64 | return json_decode($value, true);
65 | } else {
66 | return [];
67 | }
68 | }
69 |
70 | /**
71 | * Check whether value is in form retrieved by _encodeArrayFieldValue()
72 | *
73 | * @param string|array $value
74 | * @return bool
75 | */
76 | protected function isEncodedArrayFieldValue($value)
77 | {
78 | if (!is_array($value)) {
79 | return false;
80 | }
81 |
82 | unset($value['__empty']);
83 |
84 | foreach ($value as $row) {
85 | if (!is_array($row)
86 | || !array_key_exists('is_crowdin_sync_enabled', $row)
87 | || !array_key_exists('magento_translatable_fields', $row)
88 | ) {
89 | return false;
90 | }
91 | }
92 | return true;
93 | }
94 |
95 | /**
96 | * Encode value to be used in \Magento\Config\Block\System\Config\Form\Field\FieldArray\AbstractFieldArray
97 | *
98 | * @param array $value
99 | * @return array
100 | */
101 | protected function encodeArrayFieldValue(array $value)
102 | {
103 | $result = [];
104 |
105 | foreach ($value as $attributeCode => $isSyncEnabled) {
106 | $resultId = $this->mathRandom->getUniqueHash('_');
107 | $result[$resultId] = ['magento_translatable_fields' => $attributeCode, 'is_crowdin_sync_enabled' => $isSyncEnabled];
108 | }
109 |
110 | return $result;
111 | }
112 |
113 | /**
114 | * Decode value from used in \Magento\Config\Block\System\Config\Form\Field\FieldArray\AbstractFieldArray
115 | *
116 | * @param array $value
117 | * @return array
118 | */
119 | protected function decodeArrayFieldValue(array $value)
120 | {
121 | $result = [];
122 |
123 | unset($value['__empty']);
124 |
125 | foreach ($value as $row) {
126 |
127 | if (!is_array($row)
128 | || !array_key_exists('is_crowdin_sync_enabled', $row)
129 | || !array_key_exists('magento_translatable_fields', $row)
130 | ) {
131 | continue;
132 | }
133 |
134 | $result[$row['magento_translatable_fields']] = $row['is_crowdin_sync_enabled'];
135 | }
136 |
137 | return $result;
138 | }
139 |
140 | /**
141 | * Make value readable by \Magento\Config\Block\System\Config\Form\Field\FieldArray\AbstractFieldArray
142 | *
143 | * @param string|array $value
144 | * @return array
145 | */
146 | public function makeArrayFieldValue($value)
147 | {
148 | $value = $this->unserializeValue($value);
149 |
150 | if (!$this->isEncodedArrayFieldValue($value)) {
151 | $value = $this->encodeArrayFieldValue($value);
152 | }
153 | return $value;
154 | }
155 |
156 | /**
157 | * Make value ready for store
158 | *
159 | * @param string|array $value
160 | * @return string
161 | */
162 | public function makeStorableArrayFieldValue($value)
163 | {
164 | if ($this->isEncodedArrayFieldValue($value)) {
165 | $value = $this->decodeArrayFieldValue($value);
166 | }
167 |
168 | ksort($value);
169 |
170 | return $this->serializeValue($value);
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/Model/System/Config/Backend/Mapped.php:
--------------------------------------------------------------------------------
1 | translatableIntegrationMapped = $translatableIntegrationMapped;
47 | parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data);
48 | }
49 |
50 | /**
51 | * Process data after load
52 | *
53 | * @return void
54 | */
55 | protected function _afterLoad()
56 | {
57 | $value = $this->getValue();
58 | $value = $this->translatableIntegrationMapped->makeArrayFieldValue($value);
59 | $this->setValue($value);
60 | }
61 |
62 | /**
63 | * Prepare data before save
64 | *
65 | * @return void
66 | */
67 | public function beforeSave()
68 | {
69 | $value = $this->getValue();
70 | $value = $this->translatableIntegrationMapped->makeStorableArrayFieldValue($value);
71 | $this->setValue($value);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Model/TranslationRepository.php:
--------------------------------------------------------------------------------
1 | getTranslationEntitiesList = $getTranslationEntitiesList;
60 | $this->getTranslationEntity = $getTranslationEntity;
61 | $this->updateTranslationEntity = $updateTranslationEntity;
62 | $this->_storeManager = $storeManager;
63 | $this->config = $config;
64 | }
65 |
66 | /**
67 | * @param string $storeId
68 | * @return mixed
69 | */
70 | public function getEntitiesList($storeId)
71 | {
72 | if (!$this->config->isEnabled()) {
73 | return __('Extension doesn\'t enabled!');
74 | }
75 | ini_set('max_execution_time', '0');
76 |
77 | return $this->getTranslationEntitiesList->execute($storeId);
78 |
79 | }
80 |
81 | /**
82 | * @return array
83 | */
84 | public function getStoresList()
85 | {
86 | if (!$this->config->isEnabled()) {
87 | return __('Extension doesn\'t enabled!');
88 | }
89 |
90 | $options = [];
91 |
92 | foreach ($this->_storeManager->getStores() as $key => $value) {
93 | $options[] = [
94 | 'id' => $value->getId(),
95 | 'name' => $value->getName(),
96 | 'locale' => $this->config->getLocaleByStoreId($value->getId()),
97 | ];
98 | }
99 |
100 | return $options;
101 | }
102 |
103 | /**
104 | * @param string $id
105 | * @return mixed
106 | */
107 | public function getEntity($id)
108 | {
109 | if (!$this->config->isEnabled()) {
110 | return __('Extension doesn\'t enabled!');
111 | }
112 |
113 | return $this->getTranslationEntity->execute($id);
114 | }
115 |
116 | /**
117 | * @param string $id
118 | * @param string $data
119 | * @return mixed
120 | */
121 | public function updateEntity(string $id, string $data)
122 | {
123 | if (!$this->config->isEnabled()) {
124 | return __('Extension doesn\'t enabled!');
125 | }
126 |
127 | return $this->updateTranslationEntity->execute($id, $data);
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/Model/UpdateTranslationEntity.php:
--------------------------------------------------------------------------------
1 | categoryRepository = $categoryRepository;
163 | $this->productRepository = $productRepository;
164 | $this->emulation = $emulation;
165 | $this->emailTemplateResourceModel = $emailTemplateResourceModel;
166 | $this->emailTemplate = $emailTemplate;
167 | $this->pageRepository = $pageRepository;
168 | $this->blockRepository = $blockRepository;
169 | $this->storeManager = $storeManager;
170 | $this->resource = $resource;
171 | $this->getPageByIdentifier = $getPageByIdentifier;
172 | $this->getBlockByIdentifier = $getBlockByIdentifier;
173 | $this->pageFactory = $pageFactory;
174 | $this->blockFactory = $blockFactory;
175 | $this->searchCriteriaBuilder = $searchCriteriaBuilder;
176 | $this->filterBuilder = $filterBuilder;
177 | $this->filterGroupBuilder = $filterGroupBuilder;
178 | }
179 |
180 |
181 | /**
182 | * @param $id
183 | * @return array|void
184 | */
185 | public function execute($id, $data)
186 | {
187 | $response = ['success' => true];
188 | $entityData = explode('_', $id);
189 | $type = $entityData[0];
190 |
191 | unset($entityData[0]);
192 |
193 | $id = implode('_', $entityData);
194 | $data = json_decode($data, true);
195 |
196 | if (trim((string)$id) === '') {
197 | return ['message' => 'Missed entity id!', 'success' => false];
198 | }
199 |
200 | if (!in_array($type, $this->allowedStringId) && !is_numeric($id)) {
201 | return ['message' => 'Entity id (' . $id . ') for this entity should be numeric!', 'success' => false];
202 | }
203 |
204 | if (!isset($data['store_id'])) {
205 | return ['message' => __('Missed store_id!'), 'success' => false];
206 | }
207 |
208 | $this->storeId = (int)$data['store_id'];
209 | unset($data['store_id']);
210 |
211 | $this->emulation->startEnvironmentEmulation($this->storeId, 'adminhtml');
212 |
213 | switch ($type) {
214 | case GetTranslationEntitiesList::CATEGORY_ENTITY:
215 | $this->updateCategoryData((int)$id, $data);
216 | break;
217 | case GetTranslationEntitiesList::PRODUCT_ENTITY:
218 | $this->updateProductData((int)$id, $data);
219 | break;
220 | case GetTranslationEntitiesList::EMAIL_ENTITY:
221 | $this->updateEmailTemplateData((int)$id, $data);
222 | break;
223 | case GetTranslationEntitiesList::CMS_PAGE_ENTITY:
224 | $response = $this->updateCmsPageData($id, $data);
225 | break;
226 | case GetTranslationEntitiesList::CMS_BLOCK_ENTITY:
227 | $response = $this->updateCmsBlockData($id, $data);
228 | break;
229 | case GetTranslationEntitiesList::PRODUCT_ATTRIBUTE_ENTITY:
230 | $response = $this->updateProductAttributeData((int)$id, $data);
231 | break;
232 | }
233 |
234 | $this->emulation->stopEnvironmentEmulation();
235 |
236 | return $response;
237 | }
238 |
239 | /**
240 | * @param int $id
241 | * @param $data
242 | */
243 | private function updateCategoryData(int $id, $data)
244 | {
245 | $category = $this->categoryRepository->get($id, $this->storeId);
246 |
247 | foreach ($data as $attributeCode => $attributeValue) {
248 | $category->setData($attributeCode, $attributeValue);
249 |
250 | /**
251 | * https://github.com/magento/magento2/issues/31083
252 | */
253 | $category->getResource()->saveAttribute($category, $attributeCode);
254 | }
255 |
256 | //$this->categoryRepository->save($category);
257 | }
258 |
259 | /**
260 | * @param int $id
261 | * @param array $data
262 | */
263 | private function updateProductData(int $id, array $data)
264 | {
265 | $product = $this->productRepository->getById($id, false, $this->storeId);
266 |
267 | foreach ($data as $attributeCode => $attributeValue) {
268 | $product->setData($attributeCode, $attributeValue);
269 |
270 | /**
271 | * https://github.com/magento/magento2/issues/31083
272 | */
273 | $product->getResource()->saveAttribute($product, $attributeCode);
274 | }
275 |
276 | //$this->productRepository->save($product);
277 | }
278 |
279 | /**
280 | * @param string $identifier
281 | * @param array $data
282 | */
283 | private function updateCmsPageData(string $identifier, array $data)
284 | {
285 | $page = $this->getCmsEntityByIdentifier($identifier, $this->pageRepository, $this->pageFactory);
286 |
287 | // if page saved on all store views
288 | if (in_array(0, $page->getStoreId())) {
289 | // create new page
290 | $page = $this->duplicateCmsPage($page, $this->storeId);
291 | }
292 |
293 | foreach ($data as $attributeCode => $attributeValue) {
294 | $page->setData($attributeCode, $attributeValue);
295 | }
296 |
297 | $this->pageRepository->save($page);
298 | }
299 |
300 | /**
301 | * @param $page
302 | * @param $storeId
303 | * @return mixed
304 | */
305 | private function duplicateCmsPage($page, $storeId)
306 | {
307 | $storeIds = [];
308 |
309 | foreach ($this->storeManager->getStores() as $store) {
310 | $storeIds[$store->getId()] = $store->getId();
311 | }
312 |
313 | unset($storeIds[$storeId]);
314 |
315 | $page->setStoreId($storeIds);
316 | $this->pageRepository->save($page);
317 |
318 | $newPage = $this->pageFactory
319 | ->create(['data' => $page->getData()])
320 | ->setId(null)
321 | //->setIsActive(0)
322 | ->setCreationTime(null)
323 | ->setUpdateTime(null)
324 | ->setStoreId([$storeId]);
325 |
326 | return $this->pageRepository->save($newPage);
327 | }
328 |
329 | /**
330 | * @param string $identifier
331 | * @param array $data
332 | */
333 | private function updateCmsBlockData(string $identifier, array $data)
334 | {
335 | $block = $this->getCmsEntityByIdentifier($identifier, $this->blockRepository, $this->blockFactory);
336 |
337 | // if block saved on all store views
338 | if (in_array(0, $block->getStoreId())) {
339 | // create new page
340 | $block = $this->duplicateCmsBlock($block, $this->storeId);
341 | }
342 |
343 | foreach ($data as $attributeCode => $attributeValue) {
344 | $block->setData($attributeCode, $attributeValue);
345 | }
346 |
347 | $this->blockRepository->save($block);
348 | }
349 |
350 | /**
351 | * @param $block
352 | * @param $storeId
353 | * @return mixed
354 | */
355 | private function duplicateCmsBlock($block, $storeId)
356 | {
357 | $storeIds = [];
358 |
359 | foreach ($this->storeManager->getStores() as $store) {
360 | $storeIds[$store->getId()] = $store->getId();
361 | }
362 |
363 | unset($storeIds[$storeId]);
364 | $block->setStores($storeIds);
365 | $this->blockRepository->save($block);
366 |
367 | $newBlock = $this->blockFactory
368 | ->create(['data' => $block->getData()])
369 | ->setId(null)
370 | //->setIsActive(0)
371 | ->setCreationTime(null)
372 | ->setUpdateTime(null)
373 | ->setStores([$storeId]);
374 |
375 | return $this->blockRepository->save($newBlock);
376 | }
377 |
378 | /**
379 | * @param $identifier
380 | * @param $repository
381 | * @return false|mixed
382 | */
383 | private function getCmsEntityByIdentifier($identifier, $repository, $factory)
384 | {
385 | $storeFilter = $this->filterBuilder
386 | ->setField('store_id')
387 | ->setConditionType('eq')
388 | ->setValue($this->storeId)
389 | ->create();
390 |
391 | $identifierFilter = $this->filterBuilder
392 | ->setField('identifier')
393 | ->setConditionType('eq')
394 | ->setValue($identifier)
395 | ->create();
396 |
397 | $storeGroup = $this->filterGroupBuilder->addFilter($storeFilter)->create();
398 | $identifierGroup = $this->filterGroupBuilder->addFilter($identifierFilter)->create();
399 |
400 | $searchCriteria = $this->searchCriteriaBuilder->setFilterGroups([$storeGroup, $identifierGroup])->create();
401 | $cmsEntity = $repository->getList($searchCriteria)->getItems();
402 |
403 | if (!$cmsEntity) {
404 | // in case if entity does not exist on specific store, need to check if it assigned to all stores
405 | $storeFilter = $this->filterBuilder
406 | ->setField('store_id')
407 | ->setConditionType('eq')
408 | ->setValue(0)
409 | ->create();
410 |
411 | $storeGroup = $this->filterGroupBuilder->addFilter($storeFilter)->create();
412 | $searchCriteria = $this->searchCriteriaBuilder->setFilterGroups([$storeGroup, $identifierGroup])->create();
413 | $cmsEntity = $repository->getList($searchCriteria)->getItems();
414 | }
415 |
416 | if (!$cmsEntity) {
417 | $cmsEntity = $factory->create(
418 | ['data' =>
419 | [
420 | 'title' => $identifier,
421 | 'identifier' => $identifier
422 | ]
423 | ]
424 | )
425 | ->setId(null)
426 | ->setCreationTime(null)
427 | ->setUpdateTime(null)
428 | ->setStoreId([$this->storeId]);
429 |
430 | return $repository->save($cmsEntity);
431 | }
432 |
433 | return reset($cmsEntity);
434 | }
435 |
436 | /**
437 | * @param $id
438 | * @param array $data
439 | */
440 | private function updateEmailTemplateData($id, array $data)
441 | {
442 | $emailTemplate = $this->emailTemplate->load($id);
443 |
444 | foreach ($data as $attributeCode => $attributeValue) {
445 | $emailTemplate->setData($attributeCode, $attributeValue);
446 | }
447 |
448 | $this->emailTemplateResourceModel->save($emailTemplate);
449 | }
450 |
451 | /**
452 | * @param $attributeId
453 | * @param array $data
454 | */
455 | private function updateProductAttributeData($attributeId, array $data)
456 | {
457 | $connection = $this->resource->getConnection();
458 |
459 | /**
460 | * [Black] => Array
461 | * (
462 | * [value] => Black
463 | * [store_id] => 0
464 | * [option_id] => 49
465 | * [attribute_id] => 93
466 | * )
467 | * [Blue] => Array
468 | * (
469 | * [value] => Blue
470 | * [store_id] => 0
471 | * [option_id] => 50
472 | * [attribute_id] => 93
473 | * )
474 | */
475 | $selectAttributesData = $connection->select()
476 | ->from(
477 | ['option' => $this->resource->getTableName('eav_attribute_option')],
478 | [
479 | 'value' => 'option_value.value',
480 | 'store_id' => 'option_value.store_id',
481 | 'option_id' => 'option.option_id',
482 | 'attribute_id' => 'option.attribute_id',
483 | ]
484 | )
485 | ->joinLeft(
486 | ['option_value' => $this->resource->getTableName('eav_attribute_option_value')],
487 | 'option.option_id = option_value.option_id',
488 | []
489 | )->where('option.attribute_id = ?', $attributeId);
490 |
491 | $attributeOriginalValues = $connection->fetchAssoc(
492 | $selectAttributesData->where('option_value.store_id = ?', Store::DEFAULT_STORE_ID)
493 | );
494 |
495 | foreach ($data as $originalAttributeVal => $translatedAttributeVal) {
496 | if (isset($attributeOriginalValues[$originalAttributeVal])) {
497 | $optionId = $attributeOriginalValues[$originalAttributeVal]['option_id'];
498 |
499 | $valueId = $connection->fetchOne(
500 | $connection->select()
501 | ->from(
502 | [$connection->getTableName('eav_attribute_option_value')],
503 | ['value_id'])
504 | ->where('store_id = ?', $this->storeId)
505 | ->where('option_id = ?', $optionId)
506 | );
507 |
508 | $attributeData = [
509 | 'value_id' => (int)$valueId,
510 | 'option_id' => $optionId,
511 | 'store_id' => $this->storeId,
512 | 'value' => $translatedAttributeVal
513 | ];
514 |
515 | if (!$valueId) {
516 | unset($attributeData['value_id']);
517 | }
518 |
519 | $connection->insertOnDuplicate($this->resource->getTableName('eav_attribute_option_value'), $attributeData);
520 | }
521 | }
522 | }
523 | }
524 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [Crowdin Integration for Magento 2](https://magefan.com/magento-2-crowdin-integration)
2 |
3 | [](https://packagist.org/packages/magefan/module-crowdin-integration)
4 | [](https://packagist.org/packages/magefan/module-crowdin-integration)
5 |
6 | This extension allows you to integrate Magento 2 Shop with Crowdin to easily manage and perform your store localization.
7 |
8 |
9 |
10 | ## Requirements
11 | * Magento Community 2.2.0-2.4.x (CE, EE, ECE, B2B)
12 |
13 | ## Online Documentation
14 | [Crowdin Documentation](https://store.crowdin.com/magento?utm_source=github.com&utm_medium=referral&utm_campaign=magefan)
15 |
16 | [Magefan Documentation](https://magefan.com/magento-2-crowdin-integration/documentation)
17 |
18 | ## Installation Instruction
19 | https://magefan.com/magento-2-crowdin-integration/installation
20 |
21 | ## Support
22 | If you have any issues, please [contact us](mailto:support@magefan.com)
23 | then if you still need help, open a bug report in GitHub's
24 | [issue tracker](https://github.com/magefan/module-blog/issues).
25 |
26 | ## License
27 | The code is licensed under [EULA](https://magefan.com/end-user-license-agreement).
28 |
--------------------------------------------------------------------------------
/Setup/InstallData.php:
--------------------------------------------------------------------------------
1 | integrationManager = $integrationManager;
48 | $this->configWriter = $configWriter;
49 | $this->translationEntity = $translationEntity;
50 | }
51 |
52 | /**
53 | * @param ModuleDataSetupInterface $setup
54 | * @param ModuleContextInterface $context
55 | */
56 | public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
57 | {
58 | $this->createCrowdinIntegrationApiKeys();
59 | $this->presetTranslatableAttributes();
60 | }
61 |
62 | private function presetTranslatableAttributes()
63 | {
64 | $presetValue = [];
65 |
66 | foreach ($this->translationEntity->getTranslatableAttributes() as $key => $translatableAttributes) {
67 | foreach ($translatableAttributes as $translatableAttribute) {
68 | $presetValue[$key .'/'. $translatableAttribute['code']] = 1;
69 | }
70 | }
71 |
72 | $this->configWriter->save(Config::XML_PATH_FIELDS_MAPPING, json_encode($presetValue), $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, $scopeId = 0);
73 | }
74 |
75 | private function createCrowdinIntegrationApiKeys()
76 | {
77 | /**
78 | * Keep resource the same as in api.xml!!!
79 | */
80 | $integrations = [
81 | Config::INTEGRATION_NAME => [
82 | Integration::NAME => 'Crowdin Integration',
83 | Integration::EMAIL => '',
84 | Integration::ENDPOINT => '',
85 | Integration::IDENTITY_LINK_URL => '',
86 | 'resource' => [
87 | 'Magefan_Crowdin::config_integration',
88 | 'Magento_Cms::save_design'
89 | ]
90 | ],
91 | ];
92 |
93 | $this->integrationManager->processConfigBasedIntegrations($integrations);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "magefan/module-crowdin-integration",
3 | "description": "Crowdin Integration",
4 | "type": "magento2-module",
5 | "version": "2.0.7",
6 | "autoload": {
7 | "files": [
8 | "registration.php"
9 | ],
10 | "psr-4": {
11 | "Magefan\\Crowdin\\": ""
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/etc/acl.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/etc/adminhtml/system.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 | Magefan Extensions
12 |
13 |
14 | Crowdin Integration
15 | magefan
16 | Magefan_Crowdin::config_integration
17 |
18 | General
19 | 1
20 |
21 | Magefan\Crowdin\Block\Adminhtml\System\Config\Form\Info
22 |
23 |
24 |
25 | Enabled
26 | Magento\Config\Model\Config\Source\Yesno
27 |
28 |
29 |
30 |
31 | Magefan\Crowdin\Block\Adminhtml\Form\IntegrationKey
32 |
33 | 1
34 |
35 |
36 |
37 |
38 | Synchronize To Crowdin
39 | 1
40 |
41 | Magefan\Crowdin\Block\Adminhtml\Form\Field\Mapped
42 | Magefan\Crowdin\Model\System\Config\Backend\Mapped
43 | If some field won't be selected in mapping, it will be enabled by default. If you remove all mappings, all fields will be enable to sync to Crowdin
44 |
45 |
46 |
47 | Catalog Synchronization
48 |
49 | Synchronize Categories And Products
50 | Magefan\Crowdin\Model\Config\Source\SynchronizationType
51 |
52 |
53 |
54 | Magefan\Crowdin\Model\Config\Source\CategoryList
55 | Magefan\Crowdin\Block\Adminhtml\Form\Multiselect
56 |
57 | 1,2
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/etc/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 | {
14 | "block\/content":"1",
15 | "block\/title":"1",
16 | "category\/assign_product_with_attributes":"1",
17 | "category\/description":"1",
18 | "category\/meta_description":"1",
19 | "category\/meta_keywords":"1",
20 | "category\/meta_title":"1",
21 | "category\/name":"1",
22 | "category\/url_key":"1",
23 | "email\/template_code":"1",
24 | "email\/template_subject":"1",
25 | "email\/template_text":"1",
26 | "page\/content":"1",
27 | "page\/content_heading":"1",
28 | "page\/meta_description":"1",
29 | "page\/meta_keywords":"1",
30 | "page\/meta_title":"1",
31 | "page\/title":"1",
32 | "product\/accessory_custom_engraving_text":"1",
33 | "product\/accessory_description_detailed_extra":"1",
34 | "product\/accessory_description_extra":"1",
35 | "product\/accessory_description_pagebuilder_extra":"1",
36 | "product\/description":"1",
37 | "product\/description_extra":"1",
38 | "product\/meta_description":"1",
39 | "product\/meta_keyword":"1",
40 | "product\/meta_title":"1",
41 | "product\/mfdc_reviews_count":"1",
42 | "product\/name":"1",
43 | "product\/short_description":"1",
44 | "product\/url_key":"1",
45 | "product\/video_file":"1"
46 | }
47 |
48 |
49 |
50 | 0
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/etc/di.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
11 |
12 |
--------------------------------------------------------------------------------
/etc/integration/api.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/etc/module.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/etc/webapi.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/registration.php:
--------------------------------------------------------------------------------
1 |
11 |
12 | isIntegrationActivated()) { ?>
13 |
14 |
15 | = $block->escapeHtml(__('Copy Integration Key')); ?>
16 |
17 |
18 |
19 |
39 |
40 |
62 |
63 |
64 |
65 |
66 | = __('Please Activate Integration named "crowdin" to synchronize Magento and Crowdin.', $block->getUrl('adminhtml/integration'))?>
67 |
68 |
69 |
70 |
76 |
77 |
--------------------------------------------------------------------------------
/view/adminhtml/web/css/chosen.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | Chosen, a Select Box Enhancer for jQuery and Prototype
3 | by Patrick Filler for Harvest, http://getharvest.com
4 |
5 | Version 1.8.7
6 | Full source at https://github.com/harvesthq/chosen
7 | Copyright (c) 2011-2018 Harvest http://getharvest.com
8 |
9 | MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
10 | This file is generated by `grunt build`, do not edit it by hand.
11 | */
12 | .chosen-container {
13 | position: relative;
14 | display: inline-block;
15 | vertical-align: middle;
16 | font-size: 13px;
17 | -webkit-user-select: none;
18 | -moz-user-select: none;
19 | -ms-user-select: none;
20 | user-select: none
21 | }
22 |
23 | .chosen-container * {
24 | -webkit-box-sizing: border-box;
25 | box-sizing: border-box
26 | }
27 |
28 | .chosen-container .chosen-drop {
29 | position: absolute;
30 | top: 100%;
31 | z-index: 1010;
32 | width: 100%;
33 | border: 1px solid #aaa;
34 | border-top: 0;
35 | background: #fff;
36 | -webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
37 | box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
38 | clip: rect(0, 0, 0, 0);
39 | -webkit-clip-path: inset(100% 100%);
40 | clip-path: inset(100% 100%)
41 | }
42 |
43 | .chosen-container.chosen-with-drop .chosen-drop {
44 | clip: auto;
45 | -webkit-clip-path: none;
46 | clip-path: none
47 | }
48 |
49 | .chosen-container a {
50 | cursor: pointer
51 | }
52 |
53 | .chosen-container .chosen-single .group-name, .chosen-container .search-choice .group-name {
54 | margin-right: 4px;
55 | overflow: hidden;
56 | white-space: nowrap;
57 | text-overflow: ellipsis;
58 | font-weight: 400;
59 | color: #999
60 | }
61 |
62 | .chosen-container .chosen-single .group-name:after, .chosen-container .search-choice .group-name:after {
63 | content: ":";
64 | padding-left: 2px;
65 | vertical-align: top
66 | }
67 |
68 | .chosen-container-single .chosen-single {
69 | position: relative;
70 | display: block;
71 | overflow: hidden;
72 | padding: 0 0 0 8px;
73 | height: 25px;
74 | border: 1px solid #aaa;
75 | border-radius: 5px;
76 | background-color: #fff;
77 | background: -webkit-gradient(linear, left top, left bottom, color-stop(20%, #fff), color-stop(50%, #f6f6f6), color-stop(52%, #eee), to(#f4f4f4));
78 | background: linear-gradient(#fff 20%, #f6f6f6 50%, #eee 52%, #f4f4f4 100%);
79 | background-clip: padding-box;
80 | -webkit-box-shadow: 0 0 3px #fff inset, 0 1px 1px rgba(0, 0, 0, .1);
81 | box-shadow: 0 0 3px #fff inset, 0 1px 1px rgba(0, 0, 0, .1);
82 | color: #444;
83 | text-decoration: none;
84 | white-space: nowrap;
85 | line-height: 24px
86 | }
87 |
88 | .chosen-container-single .chosen-default {
89 | color: #999
90 | }
91 |
92 | .chosen-container-single .chosen-single span {
93 | display: block;
94 | overflow: hidden;
95 | margin-right: 26px;
96 | text-overflow: ellipsis;
97 | white-space: nowrap
98 | }
99 |
100 | .chosen-container-single .chosen-single-with-deselect span {
101 | margin-right: 38px
102 | }
103 |
104 | .chosen-container-single .chosen-single abbr {
105 | position: absolute;
106 | top: 6px;
107 | right: 26px;
108 | display: block;
109 | width: 12px;
110 | height: 12px;
111 | background: url(../images/chosen-sprite.png) -42px 1px no-repeat;
112 | font-size: 1px
113 | }
114 |
115 | .chosen-container-single .chosen-single abbr:hover {
116 | background-position: -42px -10px
117 | }
118 |
119 | .chosen-container-single.chosen-disabled .chosen-single abbr:hover {
120 | background-position: -42px -10px
121 | }
122 |
123 | .chosen-container-single .chosen-single div {
124 | position: absolute;
125 | top: 0;
126 | right: 0;
127 | display: block;
128 | width: 18px;
129 | height: 100%
130 | }
131 |
132 | .chosen-container-single .chosen-single div b {
133 | display: block;
134 | width: 100%;
135 | height: 100%;
136 | background: url(../images/chosen-sprite.png) no-repeat 0 2px
137 | }
138 |
139 | .chosen-container-single .chosen-search {
140 | position: relative;
141 | z-index: 1010;
142 | margin: 0;
143 | padding: 3px 4px;
144 | white-space: nowrap
145 | }
146 |
147 | .chosen-container-single .chosen-search input[type=text] {
148 | margin: 1px 0;
149 | padding: 4px 20px 4px 5px;
150 | width: 100%;
151 | height: auto;
152 | outline: 0;
153 | border: 1px solid #aaa;
154 | background: url(../images/chosen-sprite.png) no-repeat 100% -20px;
155 | font-size: 1em;
156 | font-family: sans-serif;
157 | line-height: normal;
158 | border-radius: 0
159 | }
160 |
161 | .chosen-container-single .chosen-drop {
162 | margin-top: -1px;
163 | border-radius: 0 0 4px 4px;
164 | background-clip: padding-box
165 | }
166 |
167 | .chosen-container-single.chosen-container-single-nosearch .chosen-search {
168 | position: absolute;
169 | clip: rect(0, 0, 0, 0);
170 | -webkit-clip-path: inset(100% 100%);
171 | clip-path: inset(100% 100%)
172 | }
173 |
174 | .chosen-container .chosen-results {
175 | color: #444;
176 | position: relative;
177 | overflow-x: hidden;
178 | overflow-y: auto;
179 | margin: 0 4px 4px 0;
180 | padding: 0 0 0 4px;
181 | max-height: 240px;
182 | -webkit-overflow-scrolling: touch
183 | }
184 |
185 | .chosen-container .chosen-results li {
186 | display: none;
187 | margin: 0;
188 | padding: 5px 6px;
189 | list-style: none;
190 | line-height: 15px;
191 | word-wrap: break-word;
192 | -webkit-touch-callout: none
193 | }
194 |
195 | .chosen-container .chosen-results li.active-result {
196 | display: list-item;
197 | cursor: pointer
198 | }
199 |
200 | .chosen-container .chosen-results li.disabled-result {
201 | display: list-item;
202 | color: #ccc;
203 | cursor: default
204 | }
205 |
206 | .chosen-container .chosen-results li.highlighted {
207 | background-color: #3875d7;
208 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(20%, #3875d7), color-stop(90%, #2a62bc));
209 | background-image: linear-gradient(#3875d7 20%, #2a62bc 90%);
210 | color: #fff
211 | }
212 |
213 | .chosen-container .chosen-results li.no-results {
214 | color: #777;
215 | display: list-item;
216 | background: #f4f4f4
217 | }
218 |
219 | .chosen-container .chosen-results li.group-result {
220 | display: list-item;
221 | font-weight: 700;
222 | cursor: default
223 | }
224 |
225 | .chosen-container .chosen-results li.group-option {
226 | padding-left: 15px
227 | }
228 |
229 | .chosen-container .chosen-results li em {
230 | font-style: normal;
231 | text-decoration: underline
232 | }
233 |
234 | .chosen-container-multi .chosen-choices {
235 | position: relative;
236 | overflow: hidden;
237 | margin: 0;
238 | padding: 0 5px;
239 | width: 100%;
240 | height: auto;
241 | border: 1px solid #aaa;
242 | background-color: #fff;
243 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(1%, #eee), color-stop(15%, #fff));
244 | background-image: linear-gradient(#eee 1%, #fff 15%);
245 | cursor: text
246 | }
247 |
248 | .chosen-container-multi .chosen-choices li {
249 | float: left;
250 | list-style: none
251 | }
252 |
253 | .chosen-container-multi .chosen-choices li.search-field {
254 | margin: 0;
255 | padding: 0;
256 | white-space: nowrap
257 | }
258 |
259 | .chosen-container-multi .chosen-choices li.search-field input[type=text] {
260 | margin: 1px 0;
261 | padding: 0;
262 | height: 25px;
263 | outline: 0;
264 | border: 0 !important;
265 | background: 0 0 !important;
266 | -webkit-box-shadow: none;
267 | box-shadow: none;
268 | color: #999;
269 | font-size: 100%;
270 | font-family: sans-serif;
271 | line-height: normal;
272 | border-radius: 0;
273 | width: 25px
274 | }
275 |
276 | .chosen-container-multi .chosen-choices li.search-choice {
277 | position: relative;
278 | margin: 3px 5px 3px 0;
279 | padding: 3px 20px 3px 5px;
280 | border: 1px solid #aaa;
281 | max-width: 100%;
282 | border-radius: 3px;
283 | background-color: #eee;
284 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), to(#eee));
285 | background-image: linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
286 | background-size: 100% 19px;
287 | background-repeat: repeat-x;
288 | background-clip: padding-box;
289 | -webkit-box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, .05);
290 | box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, .05);
291 | color: #333;
292 | line-height: 13px;
293 | cursor: default
294 | }
295 |
296 | .chosen-container-multi .chosen-choices li.search-choice span {
297 | word-wrap: break-word
298 | }
299 |
300 | .chosen-container-multi .chosen-choices li.search-choice .search-choice-close {
301 | position: absolute;
302 | top: 4px;
303 | right: 3px;
304 | display: block;
305 | width: 12px;
306 | height: 12px;
307 | background: url(../images/chosen-sprite.png) -42px 1px no-repeat;
308 | font-size: 1px
309 | }
310 |
311 | .chosen-container-multi .chosen-choices li.search-choice .search-choice-close:hover {
312 | background-position: -42px -10px
313 | }
314 |
315 | .chosen-container-multi .chosen-choices li.search-choice-disabled {
316 | padding-right: 5px;
317 | border: 1px solid #ccc;
318 | background-color: #e4e4e4;
319 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), to(#eee));
320 | background-image: linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
321 | color: #666
322 | }
323 |
324 | .chosen-container-multi .chosen-choices li.search-choice-focus {
325 | background: #d4d4d4
326 | }
327 |
328 | .chosen-container-multi .chosen-choices li.search-choice-focus .search-choice-close {
329 | background-position: -42px -10px
330 | }
331 |
332 | .chosen-container-multi .chosen-results {
333 | margin: 0;
334 | padding: 0
335 | }
336 |
337 | .chosen-container-multi .chosen-drop .result-selected {
338 | display: list-item;
339 | color: #ccc;
340 | cursor: default
341 | }
342 |
343 | .chosen-container-active .chosen-single {
344 | border: 1px solid #5897fb;
345 | -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3);
346 | box-shadow: 0 0 5px rgba(0, 0, 0, .3)
347 | }
348 |
349 | .chosen-container-active.chosen-with-drop .chosen-single {
350 | border: 1px solid #aaa;
351 | border-bottom-right-radius: 0;
352 | border-bottom-left-radius: 0;
353 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(20%, #eee), color-stop(80%, #fff));
354 | background-image: linear-gradient(#eee 20%, #fff 80%);
355 | -webkit-box-shadow: 0 1px 0 #fff inset;
356 | box-shadow: 0 1px 0 #fff inset
357 | }
358 |
359 | .chosen-container-active.chosen-with-drop .chosen-single div {
360 | border-left: none;
361 | background: 0 0
362 | }
363 |
364 | .chosen-container-active.chosen-with-drop .chosen-single div b {
365 | background-position: -18px 2px
366 | }
367 |
368 | .chosen-container-active .chosen-choices {
369 | border: 1px solid #5897fb;
370 | -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3);
371 | box-shadow: 0 0 5px rgba(0, 0, 0, .3)
372 | }
373 |
374 | .chosen-container-active .chosen-choices li.search-field input[type=text] {
375 | color: #222 !important
376 | }
377 |
378 | .chosen-disabled {
379 | opacity: .5 !important;
380 | cursor: default
381 | }
382 |
383 | .chosen-disabled .chosen-single {
384 | cursor: default
385 | }
386 |
387 | .chosen-disabled .chosen-choices .search-choice .search-choice-close {
388 | cursor: default
389 | }
390 |
391 | .chosen-rtl {
392 | text-align: right
393 | }
394 |
395 | .chosen-rtl .chosen-single {
396 | overflow: visible;
397 | padding: 0 8px 0 0
398 | }
399 |
400 | .chosen-rtl .chosen-single span {
401 | margin-right: 0;
402 | margin-left: 26px;
403 | direction: rtl
404 | }
405 |
406 | .chosen-rtl .chosen-single-with-deselect span {
407 | margin-left: 38px
408 | }
409 |
410 | .chosen-rtl .chosen-single div {
411 | right: auto;
412 | left: 3px
413 | }
414 |
415 | .chosen-rtl .chosen-single abbr {
416 | right: auto;
417 | left: 26px
418 | }
419 |
420 | .chosen-rtl .chosen-choices li {
421 | float: right
422 | }
423 |
424 | .chosen-rtl .chosen-choices li.search-field input[type=text] {
425 | direction: rtl
426 | }
427 |
428 | .chosen-rtl .chosen-choices li.search-choice {
429 | margin: 3px 5px 3px 0;
430 | padding: 3px 5px 3px 19px
431 | }
432 |
433 | .chosen-rtl .chosen-choices li.search-choice .search-choice-close {
434 | right: auto;
435 | left: 4px
436 | }
437 |
438 | .chosen-rtl.chosen-container-single .chosen-results {
439 | margin: 0 0 4px 4px;
440 | padding: 0 4px 0 0
441 | }
442 |
443 | .chosen-rtl .chosen-results li.group-option {
444 | padding-right: 15px;
445 | padding-left: 0
446 | }
447 |
448 | .chosen-rtl.chosen-container-active.chosen-with-drop .chosen-single div {
449 | border-right: none
450 | }
451 |
452 | .chosen-rtl .chosen-search input[type=text] {
453 | padding: 4px 5px 4px 20px;
454 | background: url(chosen-sprite.png) no-repeat -30px -20px;
455 | direction: rtl
456 | }
457 |
458 | .chosen-rtl.chosen-container-single .chosen-single div b {
459 | background-position: 6px 2px
460 | }
461 |
462 | .chosen-rtl.chosen-container-single.chosen-with-drop .chosen-single div b {
463 | background-position: -12px 2px
464 | }
465 |
466 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 144dpi), only screen and (min-resolution: 1.5dppx) {
467 | .chosen-container .chosen-results-scroll-down span, .chosen-container .chosen-results-scroll-up span, .chosen-container-multi .chosen-choices .search-choice .search-choice-close, .chosen-container-single .chosen-search input[type=text], .chosen-container-single .chosen-single abbr, .chosen-container-single .chosen-single div b, .chosen-rtl .chosen-search input[type=text] {
468 | background-image: url(../images/chosen-sprite@2x.png) !important;
469 | background-size: 52px 37px !important;
470 | background-repeat: no-repeat !important
471 | }
472 | }
473 |
474 | .chosen-container.chosen-container-multi {
475 | width: 250px !important;
476 | }
477 |
478 | .mf-action-submenu.action-submenu {
479 | top: 100%;
480 | margin-top: 2px;
481 | }
482 |
483 | .mf-action-submenu.action-submenu._active {
484 | max-height: none;
485 | overflow-y: unset;
486 | }
487 |
488 | .mf-field-submenu.action-submenu {
489 | display: flex;
490 | align-items: center;
491 | padding: 8px;
492 | }
493 |
494 | .mf-action-submenu.action-submenu._active .mf-field-submenu.action-submenu {
495 | right: -20%;
496 | }
497 |
498 | .mf-field-submenu.action-submenu .mf-label{
499 | margin: 9px;
500 | }
501 |
502 | .mf-field-submenu.action-submenu .mf-input{
503 | margin-right: 10px;
504 | }
505 |
506 | .action-submenu._active {
507 | border: 0px;
508 | }
509 |
510 | .mf-action-submenu.action-submenu._active .mf-field-submenu.action-submenu.mf-select {
511 | right: -120%;
512 | }
513 |
--------------------------------------------------------------------------------
/view/adminhtml/web/images/chosen-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/magefan/module-crowdin-integration/900ddd907a9f08a1a3bdfde266a048ee9ccf629c/view/adminhtml/web/images/chosen-sprite.png
--------------------------------------------------------------------------------
/view/adminhtml/web/images/chosen-sprite@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/magefan/module-crowdin-integration/900ddd907a9f08a1a3bdfde266a048ee9ccf629c/view/adminhtml/web/images/chosen-sprite@2x.png
--------------------------------------------------------------------------------
/view/adminhtml/web/js/chosen.jquery.min.js:
--------------------------------------------------------------------------------
1 | /* Chosen v1.8.7 | (c) 2011-2018 by Harvest | MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md */
2 |
3 | (function(){var t,e,s,i,n=function(t,e){return function(){return t.apply(e,arguments)}},r=function(t,e){function s(){this.constructor=t}for(var i in e)o.call(e,i)&&(t[i]=e[i]);return s.prototype=e.prototype,t.prototype=new s,t.__super__=e.prototype,t},o={}.hasOwnProperty;(i=function(){function t(){this.options_index=0,this.parsed=[]}return t.prototype.add_node=function(t){return"OPTGROUP"===t.nodeName.toUpperCase()?this.add_group(t):this.add_option(t)},t.prototype.add_group=function(t){var e,s,i,n,r,o;for(e=this.parsed.length,this.parsed.push({array_index:e,group:!0,label:t.label,title:t.title?t.title:void 0,children:0,disabled:t.disabled,classes:t.className}),o=[],s=0,i=(r=t.childNodes).length;s"+this.escape_html(t.group_label)+""+t.html:t.html},t.prototype.mouse_enter=function(){return this.mouse_on_container=!0},t.prototype.mouse_leave=function(){return this.mouse_on_container=!1},t.prototype.input_focus=function(t){if(this.is_multiple){if(!this.active_field)return setTimeout(function(t){return function(){return t.container_mousedown()}}(this),50)}else if(!this.active_field)return this.activate_field()},t.prototype.input_blur=function(t){if(!this.mouse_on_container)return this.active_field=!1,setTimeout(function(t){return function(){return t.blur_test()}}(this),100)},t.prototype.label_click_handler=function(t){return this.is_multiple?this.container_mousedown(t):this.activate_field()},t.prototype.results_option_build=function(t){var e,s,i,n,r,o,h;for(e="",h=0,n=0,r=(o=this.results_data).length;n=this.max_shown_results));n++);return e},t.prototype.result_add_option=function(t){var e,s;return t.search_match&&this.include_option_in_results(t)?(e=[],t.disabled||t.selected&&this.is_multiple||e.push("active-result"),!t.disabled||t.selected&&this.is_multiple||e.push("disabled-result"),t.selected&&e.push("result-selected"),null!=t.group_array_index&&e.push("group-option"),""!==t.classes&&e.push(t.classes),s=document.createElement("li"),s.className=e.join(" "),t.style&&(s.style.cssText=t.style),s.setAttribute("data-option-array-index",t.array_index),s.innerHTML=t.highlighted_html||t.html,t.title&&(s.title=t.title),this.outerHTML(s)):""},t.prototype.result_add_group=function(t){var e,s;return(t.search_match||t.group_match)&&t.active_options>0?((e=[]).push("group-result"),t.classes&&e.push(t.classes),s=document.createElement("li"),s.className=e.join(" "),s.innerHTML=t.highlighted_html||this.escape_html(t.label),t.title&&(s.title=t.title),this.outerHTML(s)):""},t.prototype.results_update_field=function(){if(this.set_default_text(),this.is_multiple||this.results_reset_cleanup(),this.result_clear_highlight(),this.results_build(),this.results_showing)return this.winnow_results()},t.prototype.reset_single_select_options=function(){var t,e,s,i,n;for(n=[],t=0,e=(s=this.results_data).length;t"+this.escape_html(s)+""+this.escape_html(p)),null!=a&&(a.group_match=!0)):null!=r.group_array_index&&this.results_data[r.group_array_index].search_match&&(r.search_match=!0)));return this.result_clear_highlight(),_<1&&h.length?(this.update_results_content(""),this.no_results(h)):(this.update_results_content(this.results_option_build()),(null!=t?t.skip_highlight:void 0)?void 0:this.winnow_results_set_highlight())},t.prototype.get_search_regex=function(t){var e,s;return s=this.search_contains?t:"(^|\\s|\\b)"+t+"[^\\s]*",this.enable_split_word_search||this.search_contains||(s="^"+s),e=this.case_sensitive_search?"":"i",new RegExp(s,e)},t.prototype.search_string_match=function(t,e){var s;return s=e.exec(t),!this.search_contains&&(null!=s?s[1]:void 0)&&(s.index+=1),s},t.prototype.choices_count=function(){var t,e,s;if(null!=this.selected_option_count)return this.selected_option_count;for(this.selected_option_count=0,t=0,e=(s=this.form_field.options).length;t0?this.keydown_backstroke():this.pending_backstroke||(this.result_clear_highlight(),this.results_search());break;case 13:t.preventDefault(),this.results_showing&&this.result_select(t);break;case 27:this.results_showing&&this.results_hide();break;case 9:case 16:case 17:case 18:case 38:case 40:case 91:break;default:this.results_search()}},t.prototype.clipboard_event_checker=function(t){if(!this.is_disabled)return setTimeout(function(t){return function(){return t.results_search()}}(this),50)},t.prototype.container_width=function(){return null!=this.options.width?this.options.width:this.form_field.offsetWidth+"px"},t.prototype.include_option_in_results=function(t){return!(this.is_multiple&&!this.display_selected_options&&t.selected)&&(!(!this.display_disabled_options&&t.disabled)&&!t.empty)},t.prototype.search_results_touchstart=function(t){return this.touch_started=!0,this.search_results_mouseover(t)},t.prototype.search_results_touchmove=function(t){return this.touch_started=!1,this.search_results_mouseout(t)},t.prototype.search_results_touchend=function(t){if(this.touch_started)return this.search_results_mouseup(t)},t.prototype.outerHTML=function(t){var e;return t.outerHTML?t.outerHTML:((e=document.createElement("div")).appendChild(t),e.innerHTML)},t.prototype.get_single_html=function(){return'\n '+this.default_text+' \n
\n \n'},t.prototype.get_multi_html=function(){return'\n'},t.prototype.get_no_results_html=function(t){return'\n '+this.results_none_found+" "+this.escape_html(t)+" \n "},t.browser_is_supported=function(){return"Microsoft Internet Explorer"===window.navigator.appName?document.documentMode>=8:!(/iP(od|hone)/i.test(window.navigator.userAgent)||/IEMobile/i.test(window.navigator.userAgent)||/Windows Phone/i.test(window.navigator.userAgent)||/BlackBerry/i.test(window.navigator.userAgent)||/BB10/i.test(window.navigator.userAgent)||/Android.*Mobile/i.test(window.navigator.userAgent))},t.default_multiple_text="Select Some Options",t.default_single_text="Select an Option",t.default_no_result_text="No results match",t}(),(t=jQuery).fn.extend({chosen:function(i){return e.browser_is_supported()?this.each(function(e){var n,r;r=(n=t(this)).data("chosen"),"destroy"!==i?r instanceof s||n.data("chosen",new s(this,i)):r instanceof s&&r.destroy()}):this}}),s=function(s){function n(){return n.__super__.constructor.apply(this,arguments)}return r(n,e),n.prototype.setup=function(){return this.form_field_jq=t(this.form_field),this.current_selectedIndex=this.form_field.selectedIndex},n.prototype.set_up_html=function(){var e,s;return(e=["chosen-container"]).push("chosen-container-"+(this.is_multiple?"multi":"single")),this.inherit_select_classes&&this.form_field.className&&e.push(this.form_field.className),this.is_rtl&&e.push("chosen-rtl"),s={"class":e.join(" "),title:this.form_field.title},this.form_field.id.length&&(s.id=this.form_field.id.replace(/[^\w]/g,"_")+"_chosen"),this.container=t("
",s),this.container.width(this.container_width()),this.is_multiple?this.container.html(this.get_multi_html()):this.container.html(this.get_single_html()),this.form_field_jq.hide().after(this.container),this.dropdown=this.container.find("div.chosen-drop").first(),this.search_field=this.container.find("input").first(),this.search_results=this.container.find("ul.chosen-results").first(),this.search_field_scale(),this.search_no_results=this.container.find("li.no-results").first(),this.is_multiple?(this.search_choices=this.container.find("ul.chosen-choices").first(),this.search_container=this.container.find("li.search-field").first()):(this.search_container=this.container.find("div.chosen-search").first(),this.selected_item=this.container.find(".chosen-single").first()),this.results_build(),this.set_tab_index(),this.set_label_behavior()},n.prototype.on_ready=function(){return this.form_field_jq.trigger("chosen:ready",{chosen:this})},n.prototype.register_observers=function(){return this.container.on("touchstart.chosen",function(t){return function(e){t.container_mousedown(e)}}(this)),this.container.on("touchend.chosen",function(t){return function(e){t.container_mouseup(e)}}(this)),this.container.on("mousedown.chosen",function(t){return function(e){t.container_mousedown(e)}}(this)),this.container.on("mouseup.chosen",function(t){return function(e){t.container_mouseup(e)}}(this)),this.container.on("mouseenter.chosen",function(t){return function(e){t.mouse_enter(e)}}(this)),this.container.on("mouseleave.chosen",function(t){return function(e){t.mouse_leave(e)}}(this)),this.search_results.on("mouseup.chosen",function(t){return function(e){t.search_results_mouseup(e)}}(this)),this.search_results.on("mouseover.chosen",function(t){return function(e){t.search_results_mouseover(e)}}(this)),this.search_results.on("mouseout.chosen",function(t){return function(e){t.search_results_mouseout(e)}}(this)),this.search_results.on("mousewheel.chosen DOMMouseScroll.chosen",function(t){return function(e){t.search_results_mousewheel(e)}}(this)),this.search_results.on("touchstart.chosen",function(t){return function(e){t.search_results_touchstart(e)}}(this)),this.search_results.on("touchmove.chosen",function(t){return function(e){t.search_results_touchmove(e)}}(this)),this.search_results.on("touchend.chosen",function(t){return function(e){t.search_results_touchend(e)}}(this)),this.form_field_jq.on("chosen:updated.chosen",function(t){return function(e){t.results_update_field(e)}}(this)),this.form_field_jq.on("chosen:activate.chosen",function(t){return function(e){t.activate_field(e)}}(this)),this.form_field_jq.on("chosen:open.chosen",function(t){return function(e){t.container_mousedown(e)}}(this)),this.form_field_jq.on("chosen:close.chosen",function(t){return function(e){t.close_field(e)}}(this)),this.search_field.on("blur.chosen",function(t){return function(e){t.input_blur(e)}}(this)),this.search_field.on("keyup.chosen",function(t){return function(e){t.keyup_checker(e)}}(this)),this.search_field.on("keydown.chosen",function(t){return function(e){t.keydown_checker(e)}}(this)),this.search_field.on("focus.chosen",function(t){return function(e){t.input_focus(e)}}(this)),this.search_field.on("cut.chosen",function(t){return function(e){t.clipboard_event_checker(e)}}(this)),this.search_field.on("paste.chosen",function(t){return function(e){t.clipboard_event_checker(e)}}(this)),this.is_multiple?this.search_choices.on("click.chosen",function(t){return function(e){t.choices_click(e)}}(this)):this.container.on("click.chosen",function(t){t.preventDefault()})},n.prototype.destroy=function(){return t(this.container[0].ownerDocument).off("click.chosen",this.click_test_action),this.form_field_label.length>0&&this.form_field_label.off("click.chosen"),this.search_field[0].tabIndex&&(this.form_field_jq[0].tabIndex=this.search_field[0].tabIndex),this.container.remove(),this.form_field_jq.removeData("chosen"),this.form_field_jq.show()},n.prototype.search_field_disabled=function(){return this.is_disabled=this.form_field.disabled||this.form_field_jq.parents("fieldset").is(":disabled"),this.container.toggleClass("chosen-disabled",this.is_disabled),this.search_field[0].disabled=this.is_disabled,this.is_multiple||this.selected_item.off("focus.chosen",this.activate_field),this.is_disabled?this.close_field():this.is_multiple?void 0:this.selected_item.on("focus.chosen",this.activate_field)},n.prototype.container_mousedown=function(e){var s;if(!this.is_disabled)return!e||"mousedown"!==(s=e.type)&&"touchstart"!==s||this.results_showing||e.preventDefault(),null!=e&&t(e.target).hasClass("search-choice-close")?void 0:(this.active_field?this.is_multiple||!e||t(e.target)[0]!==this.selected_item[0]&&!t(e.target).parents("a.chosen-single").length||(e.preventDefault(),this.results_toggle()):(this.is_multiple&&this.search_field.val(""),t(this.container[0].ownerDocument).on("click.chosen",this.click_test_action),this.results_show()),this.activate_field())},n.prototype.container_mouseup=function(t){if("ABBR"===t.target.nodeName&&!this.is_disabled)return this.results_reset(t)},n.prototype.search_results_mousewheel=function(t){var e;if(t.originalEvent&&(e=t.originalEvent.deltaY||-t.originalEvent.wheelDelta||t.originalEvent.detail),null!=e)return t.preventDefault(),"DOMMouseScroll"===t.type&&(e*=40),this.search_results.scrollTop(e+this.search_results.scrollTop())},n.prototype.blur_test=function(t){if(!this.active_field&&this.container.hasClass("chosen-container-active"))return this.close_field()},n.prototype.close_field=function(){return t(this.container[0].ownerDocument).off("click.chosen",this.click_test_action),this.active_field=!1,this.results_hide(),this.container.removeClass("chosen-container-active"),this.clear_backstroke(),this.show_search_field_default(),this.search_field_scale(),this.search_field.blur()},n.prototype.activate_field=function(){if(!this.is_disabled)return this.container.addClass("chosen-container-active"),this.active_field=!0,this.search_field.val(this.search_field.val()),this.search_field.focus()},n.prototype.test_active_click=function(e){var s;return(s=t(e.target).closest(".chosen-container")).length&&this.container[0]===s[0]?this.active_field=!0:this.close_field()},n.prototype.results_build=function(){return this.parsing=!0,this.selected_option_count=null,this.results_data=i.select_to_array(this.form_field),this.is_multiple?this.search_choices.find("li.search-choice").remove():(this.single_set_selected_text(),this.disable_search||this.form_field.options.length<=this.disable_search_threshold?(this.search_field[0].readOnly=!0,this.container.addClass("chosen-container-single-nosearch")):(this.search_field[0].readOnly=!1,this.container.removeClass("chosen-container-single-nosearch"))),this.update_results_content(this.results_option_build({first:!0})),this.search_field_disabled(),this.show_search_field_default(),this.search_field_scale(),this.parsing=!1},n.prototype.result_do_highlight=function(t){var e,s,i,n,r;if(t.length){if(this.result_clear_highlight(),this.result_highlight=t,this.result_highlight.addClass("highlighted"),i=parseInt(this.search_results.css("maxHeight"),10),r=this.search_results.scrollTop(),n=i+r,s=this.result_highlight.position().top+this.search_results.scrollTop(),(e=s+this.result_highlight.outerHeight())>=n)return this.search_results.scrollTop(e-i>0?e-i:0);if(s0)return this.form_field_label.on("click.chosen",this.label_click_handler)},n.prototype.show_search_field_default=function(){return this.is_multiple&&this.choices_count()<1&&!this.active_field?(this.search_field.val(this.default_text),this.search_field.addClass("default")):(this.search_field.val(""),this.search_field.removeClass("default"))},n.prototype.search_results_mouseup=function(e){var s;if((s=t(e.target).hasClass("active-result")?t(e.target):t(e.target).parents(".active-result").first()).length)return this.result_highlight=s,this.result_select(e),this.search_field.focus()},n.prototype.search_results_mouseover=function(e){var s;if(s=t(e.target).hasClass("active-result")?t(e.target):t(e.target).parents(".active-result").first())return this.result_do_highlight(s)},n.prototype.search_results_mouseout=function(e){if(t(e.target).hasClass("active-result")||t(e.target).parents(".active-result").first())return this.result_clear_highlight()},n.prototype.choice_build=function(e){var s,i;return s=t(" ",{"class":"search-choice"}).html(""+this.choice_label(e)+" "),e.disabled?s.addClass("search-choice-disabled"):((i=t(" ",{"class":"search-choice-close","data-option-array-index":e.array_index})).on("click.chosen",function(t){return function(e){return t.choice_destroy_link_click(e)}}(this)),s.append(i)),this.search_container.before(s)},n.prototype.choice_destroy_link_click=function(e){if(e.preventDefault(),e.stopPropagation(),!this.is_disabled)return this.choice_destroy(t(e.target))},n.prototype.choice_destroy=function(t){if(this.result_deselect(t[0].getAttribute("data-option-array-index")))return this.active_field?this.search_field.focus():this.show_search_field_default(),this.is_multiple&&this.choices_count()>0&&this.get_search_field_value().length<1&&this.results_hide(),t.parents("li").first().remove(),this.search_field_scale()},n.prototype.results_reset=function(){if(this.reset_single_select_options(),this.form_field.options[0].selected=!0,this.single_set_selected_text(),this.show_search_field_default(),this.results_reset_cleanup(),this.trigger_form_field_change(),this.active_field)return this.results_hide()},n.prototype.results_reset_cleanup=function(){return this.current_selectedIndex=this.form_field.selectedIndex,this.selected_item.find("abbr").remove()},n.prototype.result_select=function(t){var e,s;if(this.result_highlight)return e=this.result_highlight,this.result_clear_highlight(),this.is_multiple&&this.max_selected_options<=this.choices_count()?(this.form_field_jq.trigger("chosen:maxselected",{chosen:this}),!1):(this.is_multiple?e.removeClass("active-result"):this.reset_single_select_options(),e.addClass("result-selected"),s=this.results_data[e[0].getAttribute("data-option-array-index")],s.selected=!0,this.form_field.options[s.options_index].selected=!0,this.selected_option_count=null,this.is_multiple?this.choice_build(s):this.single_set_selected_text(this.choice_label(s)),this.is_multiple&&(!this.hide_results_on_select||t.metaKey||t.ctrlKey)?t.metaKey||t.ctrlKey?this.winnow_results({skip_highlight:!0}):(this.search_field.val(""),this.winnow_results()):(this.results_hide(),this.show_search_field_default()),(this.is_multiple||this.form_field.selectedIndex!==this.current_selectedIndex)&&this.trigger_form_field_change({selected:this.form_field.options[s.options_index].value}),this.current_selectedIndex=this.form_field.selectedIndex,t.preventDefault(),this.search_field_scale())},n.prototype.single_set_selected_text=function(t){return null==t&&(t=this.default_text),t===this.default_text?this.selected_item.addClass("chosen-default"):(this.single_deselect_control_build(),this.selected_item.removeClass("chosen-default")),this.selected_item.find("span").html(t)},n.prototype.result_deselect=function(t){var e;return e=this.results_data[t],!this.form_field.options[e.options_index].disabled&&(e.selected=!1,this.form_field.options[e.options_index].selected=!1,this.selected_option_count=null,this.result_clear_highlight(),this.results_showing&&this.winnow_results(),this.trigger_form_field_change({deselected:this.form_field.options[e.options_index].value}),this.search_field_scale(),!0)},n.prototype.single_deselect_control_build=function(){if(this.allow_single_deselect)return this.selected_item.find("abbr").length||this.selected_item.find("span").first().after(' '),this.selected_item.addClass("chosen-single-with-deselect")},n.prototype.get_search_field_value=function(){return this.search_field.val()},n.prototype.get_search_text=function(){return t.trim(this.get_search_field_value())},n.prototype.escape_html=function(e){return t("
").text(e).html()},n.prototype.winnow_results_set_highlight=function(){var t,e;if(e=this.is_multiple?[]:this.search_results.find(".result-selected.active-result"),null!=(t=e.length?e.first():this.search_results.find(".active-result").first()))return this.result_do_highlight(t)},n.prototype.no_results=function(t){var e;return e=this.get_no_results_html(t),this.search_results.append(e),this.form_field_jq.trigger("chosen:no_results",{chosen:this})},n.prototype.no_results_clear=function(){return this.search_results.find(".no-results").remove()},n.prototype.keydown_arrow=function(){var t;return this.results_showing&&this.result_highlight?(t=this.result_highlight.nextAll("li.active-result").first())?this.result_do_highlight(t):void 0:this.results_show()},n.prototype.keyup_arrow=function(){var t;return this.results_showing||this.is_multiple?this.result_highlight?(t=this.result_highlight.prevAll("li.active-result")).length?this.result_do_highlight(t.first()):(this.choices_count()>0&&this.results_hide(),this.result_clear_highlight()):void 0:this.results_show()},n.prototype.keydown_backstroke=function(){var t;return this.pending_backstroke?(this.choice_destroy(this.pending_backstroke.find("a").first()),this.clear_backstroke()):(t=this.search_container.siblings("li.search-choice").last()).length&&!t.hasClass("search-choice-disabled")?(this.pending_backstroke=t,this.single_backstroke_delete?this.keydown_backstroke():this.pending_backstroke.addClass("search-choice-focus")):void 0},n.prototype.clear_backstroke=function(){return this.pending_backstroke&&this.pending_backstroke.removeClass("search-choice-focus"),this.pending_backstroke=null},n.prototype.search_field_scale=function(){var e,s,i,n,r,o,h;if(this.is_multiple){for(r={position:"absolute",left:"-1000px",top:"-1000px",display:"none",whiteSpace:"pre"},s=0,i=(o=["fontSize","fontStyle","fontWeight","fontFamily","lineHeight","textTransform","letterSpacing"]).length;s ").css(r)).text(this.get_search_field_value()),t("body").append(e),h=e.width()+25,e.remove(),this.container.is(":visible")&&(h=Math.min(this.container.outerWidth()-10,h)),this.search_field.width(h)}},n.prototype.trigger_form_field_change=function(t){return this.form_field_jq.trigger("input",t),this.form_field_jq.trigger("change",t)},n}()}).call(this);
--------------------------------------------------------------------------------