├── .gitignore
├── LICENSE.txt
├── README.md
├── README_RU.md
├── frontend
└── foc_csv
│ ├── .babelrc
│ ├── .editorconfig
│ ├── .eslintignore
│ ├── .eslintrc.js
│ ├── .gitignore
│ ├── .postcssrc.js
│ ├── README.md
│ ├── build
│ ├── build.js
│ ├── check-versions.js
│ ├── logo.png
│ ├── utils.js
│ ├── vue-loader.conf.js
│ ├── webpack.base.conf.js
│ ├── webpack.dev.conf.js
│ └── webpack.prod.conf.js
│ ├── config
│ ├── dev.env.js
│ ├── index.js
│ └── prod.env.js
│ ├── index.html
│ ├── package.json
│ ├── src
│ ├── App.vue
│ ├── api
│ │ ├── index.js
│ │ ├── modules
│ │ │ ├── exporter.js
│ │ │ ├── importer.js
│ │ │ └── index.js
│ │ └── routes.js
│ ├── assets
│ │ └── logo.png
│ ├── components
│ │ ├── common
│ │ │ ├── ErrorMessage.vue
│ │ │ ├── Navigation.vue
│ │ │ ├── ProgressBar.vue
│ │ │ └── attributeWidgets
│ │ │ │ ├── TextAttribute.vue
│ │ │ │ ├── TextareaAttribute.vue
│ │ │ │ └── index.js
│ │ ├── exporter
│ │ │ ├── AttributesEncoder.vue
│ │ │ ├── Export.vue
│ │ │ ├── ExportFields.vue
│ │ │ ├── ImagesExportSettings.vue
│ │ │ ├── LeftSidebar.vue
│ │ │ ├── RightSidebar.vue
│ │ │ └── attributeWidgets
│ │ │ │ └── index.js
│ │ ├── importer
│ │ │ ├── AdditionalProcessingSettings.vue
│ │ │ ├── AttributesParser.vue
│ │ │ ├── CsvColumnNameBinder.vue
│ │ │ ├── CsvFileUpload.vue
│ │ │ ├── CsvToDbMatcher.vue
│ │ │ ├── DbFieldsSelect.vue
│ │ │ ├── ImagesImportSettings.vue
│ │ │ ├── ImagesZipUpload.vue
│ │ │ ├── Import.vue
│ │ │ ├── LeftSidebar.vue
│ │ │ ├── LineSkipSettings.vue
│ │ │ ├── MultiCsvFieldsSelector.vue
│ │ │ ├── MulticolumnExtractor.vue
│ │ │ ├── RightSidebar.vue
│ │ │ ├── StatusRewrites.vue
│ │ │ ├── StatusRewritesItem.vue
│ │ │ └── attributeWidgets
│ │ │ │ ├── ColumnAttribute.vue
│ │ │ │ ├── DBColumnAttribute.vue
│ │ │ │ └── index.js
│ │ ├── info
│ │ │ └── Info.vue
│ │ └── util
│ │ │ ├── BackupRestore.vue
│ │ │ ├── ExportProfileData.vue
│ │ │ ├── ImportProfileData.vue
│ │ │ ├── ProfilesControlList.vue
│ │ │ ├── RestoreProfile.vue
│ │ │ ├── RestoreProfiles.vue
│ │ │ └── SerializedDataToggler.vue
│ ├── config.js
│ ├── helpers.js
│ ├── i18n.js
│ ├── i18n
│ │ ├── en.json
│ │ └── ru.json
│ ├── main.js
│ ├── mixins
│ │ └── util.js
│ ├── router
│ │ └── index.js
│ ├── store
│ │ ├── common
│ │ │ ├── actions.js
│ │ │ ├── getters.js
│ │ │ └── mutations.js
│ │ ├── exporter
│ │ │ ├── actions.js
│ │ │ ├── getters.js
│ │ │ ├── index.js
│ │ │ ├── mutation-types.js
│ │ │ ├── mutations.js
│ │ │ └── state.js
│ │ ├── importer
│ │ │ ├── actions.js
│ │ │ ├── getters.js
│ │ │ ├── index.js
│ │ │ ├── mutation-types.js
│ │ │ ├── mutations.js
│ │ │ └── state.js
│ │ └── index.js
│ └── test.json
│ ├── static
│ └── .gitkeep
│ └── yarn.lock
├── gulpfile.js
├── install.xml
├── package.json
├── upload
├── admin
│ ├── controller
│ │ └── extension
│ │ │ └── module
│ │ │ └── foc_csv.php
│ ├── language
│ │ ├── en-gb
│ │ │ └── extension
│ │ │ │ └── module
│ │ │ │ ├── foc_attribute_encoders.php
│ │ │ │ ├── foc_attribute_parsers.php
│ │ │ │ └── foc_csv.php
│ │ └── ru-ru
│ │ │ └── extension
│ │ │ └── module
│ │ │ ├── foc_attribute_encoders.php
│ │ │ ├── foc_attribute_parsers.php
│ │ │ └── foc_csv.php
│ ├── model
│ │ └── extension
│ │ │ └── module
│ │ │ ├── foc_csv.php
│ │ │ ├── foc_csv_common.php
│ │ │ └── foc_csv_exporter.php
│ └── view
│ │ ├── javascript
│ │ └── .gitignore
│ │ └── template
│ │ └── extension
│ │ └── module
│ │ ├── foc_csv.tpl
│ │ └── foc_csv.twig
└── system
│ └── library
│ └── FocSimpleTemplater.php
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | *.zip
2 | node_modules
3 | yarn-error.log
4 | .DS_Store
5 | compiled-files
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FO_CSV - flexible CSV import/export for Opencart
2 | [](https://app.fossa.io/projects/git%2Bgithub.com%2FFreeocart%2Ffo-csv?ref=badge_shield)
3 |
4 |
5 | With this module, you can simplify import/export data from CSV files in your Opencart project.
6 | Current version developed and tested on Opencart 2.3/3.0.2.0.
7 |
8 | ### Why do we need another import/export module?
9 |
10 | There is known fact that Opencart licensed under free GPLv3 license, but many extensions uses they own license (proprietary in most cases) - many authors limiting access to source code (especially in Russia).
11 |
12 | On the one hand, authors try to protect they code with encrypting software (ionCube, etc) and force users to accept their license (for example, license keys check).
13 |
14 | As a user, you cannot modify encrypted source code by yourself. Authors not always can modify their products for your case, so you got in vendor lock.
15 |
16 | Also, enctypted software cannot give you any security guarantees, because you dont know what code do in these encrypted sections.
17 |
18 | Project [Freeocart](http://freeocart.ru) aims to create opensource ecosystem for Opencart.
19 |
20 | ### UNSTABLE VERSION
21 |
22 | At the moment extension is under heavy development, some functions may work incorrect.
23 |
24 | I'm open to any help - ISSUES/PR
25 |
26 | ### Attention!
27 |
28 | Use this software at your own risk!
29 |
30 | Author or contributors are not responsible at data corruption/remove caused by using this software.
31 |
32 | We are strongly recommend you to backup data before using this.
33 |
34 | Source code distributed "as is", all parts of the software is free and opensource and respecting GPLv3 license.
35 |
36 | ## License
37 | [](https://app.fossa.io/projects/git%2Bgithub.com%2FFreeocart%2Ffo-csv?ref=badge_large)
--------------------------------------------------------------------------------
/README_RU.md:
--------------------------------------------------------------------------------
1 | # FO_CSV - гибкий импорт/экспорт данных из CSV для Opencart
2 |
3 | Модуль для Opencart, призванный упростить процедуру импортирования данных в онлайн магазин на Opencart.
4 |
5 | Текущая версия модуля разработана и протестирована под версию Opencart 2.3/3.0.2.0.
6 |
7 | ### Зачем ещё один модуль импорта/экспорта
8 |
9 | Как известно, Opencart распространяется под свободной лицензией GPLv3, однако на многие модули и дополнения это не распространяется - многие авторы модулей ограничивают возможность просмотра/модификации исходных кодов своего ПО.
10 |
11 | С одной стороны, авторы пытаются защитить свой код использованием разного шифрующего ПО (например, ionCube) и заставить пользователей соблюдать лицензионное соглашение.
12 |
13 | Однако, такое отношение к распространению сильно ограничивает свободы пользователя - он не сможет доработать код под свою задачу (авторы модулей неохотно/дорого дорабатывают свои модули), пользователь не может знать что происходит внутри зашифрованного участка кода, а потому автор не может гарантировать пользователю безопасность такого ПО, также пользователь не может поспособствовать улучшению качества ПО.
14 |
15 | Проект [Freeocart](http://freeocart.ru) нацелен на разработку свободной и открытой экосистемы для Opencart.
16 |
17 | ### Нестабильная версия
18 |
19 | На данный момент модуль находится в активной разработке, поэтому некоторые функции ещё не реализованы/могут работать некорректно.
20 |
21 | Приветствуется любая помощь - ISSUES/PR
22 |
23 | ### Внимание!
24 |
25 | Используйте данное ПО на свой страх и риск!
26 |
27 | Ни автор, ни контрибьюторы проекта не отвечают за порчу/удаление данных, возникшие при использовании этого ПО.
28 |
29 | Код распространяется "как есть", все части ПО являются открытым программным обеспечением, распространяемым согласно лицензии GPLv3.
--------------------------------------------------------------------------------
/frontend/foc_csv/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-vue-jsx", "transform-runtime"]
12 | }
13 |
--------------------------------------------------------------------------------
/frontend/foc_csv/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/frontend/foc_csv/.eslintignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /config/
3 | /dist/
4 | /*.js
5 |
--------------------------------------------------------------------------------
/frontend/foc_csv/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // https://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | parserOptions: {
6 | parser: 'babel-eslint'
7 | },
8 | env: {
9 | browser: true,
10 | },
11 | extends: [
12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
14 | 'plugin:vue/essential',
15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md
16 | 'standard'
17 | ],
18 | // required to lint *.vue files
19 | plugins: [
20 | 'vue'
21 | ],
22 | // add your custom rules here
23 | rules: {
24 | // allow async-await
25 | 'generator-star-spacing': 'off',
26 | // allow debugger during development
27 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
28 | "brace-style": [2, "stroustrup", { "allowSingleLine": true }]
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/frontend/foc_csv/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | /dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Editor directories and files
9 | .idea
10 | .vscode
11 | *.suo
12 | *.ntvs*
13 | *.njsproj
14 | *.sln
15 |
--------------------------------------------------------------------------------
/frontend/foc_csv/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | "postcss-import": {},
6 | "postcss-url": {},
7 | // to edit target browsers: use "browserslist" field in package.json
8 | "autoprefixer": {}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/frontend/foc_csv/README.md:
--------------------------------------------------------------------------------
1 | # foc_csv
2 |
3 | > foc_csv frontend
4 |
5 | ## Build Setup
6 |
7 | ``` bash
8 | # install dependencies
9 | npm install
10 |
11 | # serve with hot reload at localhost:8080
12 | npm run dev
13 |
14 | # build for production with minification
15 | npm run build
16 |
17 | # build for production and view the bundle analyzer report
18 | npm run build --report
19 | ```
20 |
21 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).
22 |
--------------------------------------------------------------------------------
/frontend/foc_csv/build/build.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | require('./check-versions')()
3 |
4 | process.env.NODE_ENV = 'production'
5 |
6 | const ora = require('ora')
7 | const rm = require('rimraf')
8 | const path = require('path')
9 | const chalk = require('chalk')
10 | const webpack = require('webpack')
11 | const config = require('../config')
12 | const webpackConfig = require('./webpack.prod.conf')
13 |
14 | const spinner = ora('building for production...')
15 | spinner.start()
16 |
17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
18 | if (err) throw err
19 | webpack(webpackConfig, (err, stats) => {
20 | spinner.stop()
21 | if (err) throw err
22 | process.stdout.write(stats.toString({
23 | colors: true,
24 | modules: false,
25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
26 | chunks: false,
27 | chunkModules: false
28 | }) + '\n\n')
29 |
30 | if (stats.hasErrors()) {
31 | console.log(chalk.red(' Build failed with errors.\n'))
32 | process.exit(1)
33 | }
34 |
35 | console.log(chalk.cyan(' Build complete.\n'))
36 | console.log(chalk.yellow(
37 | ' Tip: built files are meant to be served over an HTTP server.\n' +
38 | ' Opening index.html over file:// won\'t work.\n'
39 | ))
40 | })
41 | })
42 |
--------------------------------------------------------------------------------
/frontend/foc_csv/build/check-versions.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const chalk = require('chalk')
3 | const semver = require('semver')
4 | const packageConfig = require('../package.json')
5 | const shell = require('shelljs')
6 |
7 | function exec (cmd) {
8 | return require('child_process').execSync(cmd).toString().trim()
9 | }
10 |
11 | const versionRequirements = [
12 | {
13 | name: 'node',
14 | currentVersion: semver.clean(process.version),
15 | versionRequirement: packageConfig.engines.node
16 | }
17 | ]
18 |
19 | if (shell.which('npm')) {
20 | versionRequirements.push({
21 | name: 'npm',
22 | currentVersion: exec('npm --version'),
23 | versionRequirement: packageConfig.engines.npm
24 | })
25 | }
26 |
27 | module.exports = function () {
28 | const warnings = []
29 |
30 | for (let i = 0; i < versionRequirements.length; i++) {
31 | const mod = versionRequirements[i]
32 |
33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
34 | warnings.push(mod.name + ': ' +
35 | chalk.red(mod.currentVersion) + ' should be ' +
36 | chalk.green(mod.versionRequirement)
37 | )
38 | }
39 | }
40 |
41 | if (warnings.length) {
42 | console.log('')
43 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
44 | console.log()
45 |
46 | for (let i = 0; i < warnings.length; i++) {
47 | const warning = warnings[i]
48 | console.log(' ' + warning)
49 | }
50 |
51 | console.log()
52 | process.exit(1)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/frontend/foc_csv/build/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Freeocart/fo-csv/6dcd8eaa1b125c873f9637ba93fbfd634622e02e/frontend/foc_csv/build/logo.png
--------------------------------------------------------------------------------
/frontend/foc_csv/build/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const config = require('../config')
4 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
5 | const packageConfig = require('../package.json')
6 |
7 | exports.assetsPath = function (_path) {
8 | const assetsSubDirectory = process.env.NODE_ENV === 'production'
9 | ? config.build.assetsSubDirectory
10 | : config.dev.assetsSubDirectory
11 |
12 | return path.posix.join(assetsSubDirectory, _path)
13 | }
14 |
15 | exports.cssLoaders = function (options) {
16 | options = options || {}
17 |
18 | const cssLoader = {
19 | loader: 'css-loader',
20 | options: {
21 | sourceMap: options.sourceMap
22 | }
23 | }
24 |
25 | const postcssLoader = {
26 | loader: 'postcss-loader',
27 | options: {
28 | sourceMap: options.sourceMap
29 | }
30 | }
31 |
32 | // generate loader string to be used with extract text plugin
33 | function generateLoaders (loader, loaderOptions) {
34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
35 |
36 | if (loader) {
37 | loaders.push({
38 | loader: loader + '-loader',
39 | options: Object.assign({}, loaderOptions, {
40 | sourceMap: options.sourceMap
41 | })
42 | })
43 | }
44 |
45 | // Extract CSS when that option is specified
46 | // (which is the case during production build)
47 | if (options.extract) {
48 | return ExtractTextPlugin.extract({
49 | use: loaders,
50 | fallback: 'vue-style-loader'
51 | })
52 | } else {
53 | return ['vue-style-loader'].concat(loaders)
54 | }
55 | }
56 |
57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
58 | return {
59 | css: generateLoaders(),
60 | postcss: generateLoaders(),
61 | less: generateLoaders('less'),
62 | sass: generateLoaders('sass', { indentedSyntax: true }),
63 | scss: generateLoaders('sass'),
64 | stylus: generateLoaders('stylus'),
65 | styl: generateLoaders('stylus')
66 | }
67 | }
68 |
69 | // Generate loaders for standalone style files (outside of .vue)
70 | exports.styleLoaders = function (options) {
71 | const output = []
72 | const loaders = exports.cssLoaders(options)
73 |
74 | for (const extension in loaders) {
75 | const loader = loaders[extension]
76 | output.push({
77 | test: new RegExp('\\.' + extension + '$'),
78 | use: loader
79 | })
80 | }
81 |
82 | return output
83 | }
84 |
85 | exports.createNotifierCallback = () => {
86 | const notifier = require('node-notifier')
87 |
88 | return (severity, errors) => {
89 | if (severity !== 'error') return
90 |
91 | const error = errors[0]
92 | const filename = error.file && error.file.split('!').pop()
93 |
94 | notifier.notify({
95 | title: packageConfig.name,
96 | message: severity + ': ' + error.name,
97 | subtitle: filename || '',
98 | icon: path.join(__dirname, 'logo.png')
99 | })
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/frontend/foc_csv/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const utils = require('./utils')
3 | const config = require('../config')
4 | const isProduction = process.env.NODE_ENV === 'production'
5 | const sourceMapEnabled = isProduction
6 | ? config.build.productionSourceMap
7 | : config.dev.cssSourceMap
8 |
9 | module.exports = {
10 | loaders: utils.cssLoaders({
11 | sourceMap: sourceMapEnabled,
12 | extract: isProduction
13 | }),
14 | cssSourceMap: sourceMapEnabled,
15 | cacheBusting: config.dev.cacheBusting,
16 | transformToRequire: {
17 | video: ['src', 'poster'],
18 | source: 'src',
19 | img: 'src',
20 | image: 'xlink:href'
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/frontend/foc_csv/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const utils = require('./utils')
4 | const config = require('../config')
5 | const vueLoaderConfig = require('./vue-loader.conf')
6 |
7 | function resolve (dir) {
8 | return path.join(__dirname, '..', dir)
9 | }
10 |
11 | const createLintingRule = () => ({
12 | test: /\.(js|vue)$/,
13 | loader: 'eslint-loader',
14 | enforce: 'pre',
15 | include: [resolve('src'), resolve('test')],
16 | options: {
17 | formatter: require('eslint-friendly-formatter'),
18 | emitWarning: !config.dev.showEslintErrorsInOverlay
19 | }
20 | })
21 |
22 | module.exports = {
23 | context: path.resolve(__dirname, '../'),
24 | entry: {
25 | app: './src/main.js'
26 | },
27 | output: {
28 | path: config.build.assetsRoot,
29 | filename: '[name].js',
30 | publicPath: process.env.NODE_ENV === 'production'
31 | ? config.build.assetsPublicPath
32 | : config.dev.assetsPublicPath
33 | },
34 | resolve: {
35 | extensions: ['.js', '.vue', '.json'],
36 | alias: {
37 | 'vue$': 'vue/dist/vue.esm.js',
38 | '@': resolve('src'),
39 | }
40 | },
41 | module: {
42 | rules: [
43 | ...(config.dev.useEslint ? [createLintingRule()] : []),
44 | {
45 | test: /\.vue$/,
46 | loader: 'vue-loader',
47 | options: vueLoaderConfig
48 | },
49 | {
50 | test: /\.js$/,
51 | loader: 'babel-loader',
52 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
53 | },
54 | {
55 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
56 | loader: 'url-loader',
57 | options: {
58 | limit: 10000,
59 | name: utils.assetsPath('img/[name].[ext]')
60 | }
61 | },
62 | {
63 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
64 | loader: 'url-loader',
65 | options: {
66 | limit: 10000,
67 | name: utils.assetsPath('media/[name].[ext]')
68 | }
69 | },
70 | {
71 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
72 | loader: 'url-loader',
73 | options: {
74 | limit: 10000,
75 | name: utils.assetsPath('fonts/[name].[ext]')
76 | }
77 | }
78 | ]
79 | },
80 | node: {
81 | // prevent webpack from injecting useless setImmediate polyfill because Vue
82 | // source contains it (although only uses it if it's native).
83 | setImmediate: false,
84 | // prevent webpack from injecting mocks to Node native modules
85 | // that does not make sense for the client
86 | dgram: 'empty',
87 | fs: 'empty',
88 | net: 'empty',
89 | tls: 'empty',
90 | child_process: 'empty'
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/frontend/foc_csv/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const utils = require('./utils')
3 | const webpack = require('webpack')
4 | const config = require('../config')
5 | const merge = require('webpack-merge')
6 | const path = require('path')
7 | const baseWebpackConfig = require('./webpack.base.conf')
8 | const CopyWebpackPlugin = require('copy-webpack-plugin')
9 | const HtmlWebpackPlugin = require('html-webpack-plugin')
10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
11 | const portfinder = require('portfinder')
12 |
13 | const HOST = process.env.HOST
14 | const PORT = process.env.PORT && Number(process.env.PORT)
15 |
16 | const devWebpackConfig = merge(baseWebpackConfig, {
17 | module: {
18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
19 | },
20 | // cheap-module-eval-source-map is faster for development
21 | devtool: config.dev.devtool,
22 |
23 | // these devServer options should be customized in /config/index.js
24 | devServer: {
25 | clientLogLevel: 'warning',
26 | historyApiFallback: {
27 | rewrites: [
28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
29 | ],
30 | },
31 | hot: true,
32 | contentBase: false, // since we use CopyWebpackPlugin.
33 | compress: true,
34 | host: HOST || config.dev.host,
35 | port: PORT || config.dev.port,
36 | open: config.dev.autoOpenBrowser,
37 | overlay: config.dev.errorOverlay
38 | ? { warnings: false, errors: true }
39 | : false,
40 | publicPath: config.dev.assetsPublicPath,
41 | proxy: config.dev.proxyTable,
42 | quiet: true, // necessary for FriendlyErrorsPlugin
43 | watchOptions: {
44 | poll: config.dev.poll,
45 | }
46 | },
47 | plugins: [
48 | new webpack.DefinePlugin({
49 | 'process.env': require('../config/dev.env')
50 | }),
51 | new webpack.HotModuleReplacementPlugin(),
52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
53 | new webpack.NoEmitOnErrorsPlugin(),
54 | // https://github.com/ampedandwired/html-webpack-plugin
55 | new HtmlWebpackPlugin({
56 | filename: 'index.html',
57 | template: 'index.html',
58 | inject: true
59 | }),
60 | // copy custom static assets
61 | new CopyWebpackPlugin([
62 | {
63 | from: path.resolve(__dirname, '../static'),
64 | to: config.dev.assetsSubDirectory,
65 | ignore: ['.*']
66 | }
67 | ])
68 | ]
69 | })
70 |
71 | module.exports = new Promise((resolve, reject) => {
72 | portfinder.basePort = process.env.PORT || config.dev.port
73 | portfinder.getPort((err, port) => {
74 | if (err) {
75 | reject(err)
76 | } else {
77 | // publish the new Port, necessary for e2e tests
78 | process.env.PORT = port
79 | // add port to devServer config
80 | devWebpackConfig.devServer.port = port
81 |
82 | // Add FriendlyErrorsPlugin
83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
84 | compilationSuccessInfo: {
85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
86 | },
87 | onErrors: config.dev.notifyOnErrors
88 | ? utils.createNotifierCallback()
89 | : undefined
90 | }))
91 |
92 | resolve(devWebpackConfig)
93 | }
94 | })
95 | })
96 |
--------------------------------------------------------------------------------
/frontend/foc_csv/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const utils = require('./utils')
4 | const webpack = require('webpack')
5 | const config = require('../config')
6 | const merge = require('webpack-merge')
7 | const baseWebpackConfig = require('./webpack.base.conf')
8 | const CopyWebpackPlugin = require('copy-webpack-plugin')
9 | const HtmlWebpackPlugin = require('html-webpack-plugin')
10 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
13 |
14 | const env = require('../config/prod.env')
15 |
16 | const webpackConfig = merge(baseWebpackConfig, {
17 | module: {
18 | rules: utils.styleLoaders({
19 | sourceMap: config.build.productionSourceMap,
20 | extract: true,
21 | usePostCSS: true
22 | })
23 | },
24 | devtool: config.build.productionSourceMap ? config.build.devtool : false,
25 | output: {
26 | path: config.build.assetsRoot,
27 | filename: utils.assetsPath('js/[name].js'),
28 | chunkFilename: utils.assetsPath('js/[id].js')
29 | },
30 | plugins: [
31 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
32 | new webpack.DefinePlugin({
33 | 'process.env': env
34 | }),
35 | new UglifyJsPlugin({
36 | uglifyOptions: {
37 | compress: {
38 | warnings: false
39 | }
40 | },
41 | sourceMap: config.build.productionSourceMap,
42 | parallel: true
43 | }),
44 | // extract css into its own file
45 | new ExtractTextPlugin({
46 | filename: utils.assetsPath('css/[name].css'),
47 | // Setting the following option to `false` will not extract CSS from codesplit chunks.
48 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
49 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
50 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
51 | allChunks: true,
52 | }),
53 | // Compress extracted CSS. We are using this plugin so that possible
54 | // duplicated CSS from different components can be deduped.
55 | new OptimizeCSSPlugin({
56 | cssProcessorOptions: config.build.productionSourceMap
57 | ? { safe: true, map: { inline: false } }
58 | : { safe: true }
59 | }),
60 | // generate dist index.html with correct asset hash for caching.
61 | // you can customize output by editing /index.html
62 | // see https://github.com/ampedandwired/html-webpack-plugin
63 | new HtmlWebpackPlugin({
64 | filename: config.build.index,
65 | template: 'index.html',
66 | inject: false,
67 | minify: {
68 | removeComments: true,
69 | collapseWhitespace: true,
70 | removeAttributeQuotes: false
71 | // more options:
72 | // https://github.com/kangax/html-minifier#options-quick-reference
73 | },
74 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin
75 | chunksSortMode: 'dependency'
76 | }),
77 | // keep module.id stable when vendor modules does not change
78 | new webpack.HashedModuleIdsPlugin(),
79 | // enable scope hoisting
80 | new webpack.optimize.ModuleConcatenationPlugin(),
81 | // split vendor js into its own file
82 | new webpack.optimize.CommonsChunkPlugin({
83 | name: 'vendor',
84 | minChunks (module) {
85 | // any required modules inside node_modules are extracted to vendor
86 | return (
87 | module.resource &&
88 | /\.js$/.test(module.resource) &&
89 | module.resource.indexOf(
90 | path.join(__dirname, '../node_modules')
91 | ) === 0
92 | )
93 | }
94 | }),
95 | // extract webpack runtime and module manifest to its own file in order to
96 | // prevent vendor hash from being updated whenever app bundle is updated
97 | new webpack.optimize.CommonsChunkPlugin({
98 | name: 'manifest',
99 | minChunks: Infinity
100 | }),
101 | // This instance extracts shared chunks from code splitted chunks and bundles them
102 | // in a separate chunk, similar to the vendor chunk
103 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
104 | new webpack.optimize.CommonsChunkPlugin({
105 | name: 'app',
106 | async: 'vendor-async',
107 | children: true,
108 | minChunks: 3
109 | }),
110 |
111 | // copy custom static assets
112 | new CopyWebpackPlugin([
113 | {
114 | from: path.resolve(__dirname, '../static'),
115 | to: config.build.assetsSubDirectory,
116 | ignore: ['.*']
117 | }
118 | ])
119 | ]
120 | })
121 |
122 | if (config.build.productionGzip) {
123 | const CompressionWebpackPlugin = require('compression-webpack-plugin')
124 |
125 | webpackConfig.plugins.push(
126 | new CompressionWebpackPlugin({
127 | asset: '[path].gz[query]',
128 | algorithm: 'gzip',
129 | test: new RegExp(
130 | '\\.(' +
131 | config.build.productionGzipExtensions.join('|') +
132 | ')$'
133 | ),
134 | threshold: 10240,
135 | minRatio: 0.8
136 | })
137 | )
138 | }
139 |
140 | if (config.build.bundleAnalyzerReport) {
141 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
142 | webpackConfig.plugins.push(new BundleAnalyzerPlugin())
143 | }
144 |
145 | module.exports = webpackConfig
146 |
--------------------------------------------------------------------------------
/frontend/foc_csv/config/dev.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const merge = require('webpack-merge')
3 | const prodEnv = require('./prod.env')
4 |
5 | module.exports = merge(prodEnv, {
6 | NODE_ENV: '"development"'
7 | })
8 |
--------------------------------------------------------------------------------
/frontend/foc_csv/config/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // Template version: 1.3.1
3 | // see http://vuejs-templates.github.io/webpack for documentation.
4 |
5 | const path = require('path')
6 |
7 | module.exports = {
8 | dev: {
9 |
10 | // // Paths
11 | assetsSubDirectory: 'static',
12 | assetsPublicPath: '/',
13 |
14 | proxyTable: {
15 | '/': {
16 | target: process.env.PROXY_URL || 'http://oc2.local/',
17 | changeOrigin: true
18 | }
19 | },
20 |
21 | // Various Dev Server settings
22 | host: 'localhost', // can be overwritten by process.env.HOST
23 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
24 | autoOpenBrowser: true,
25 | errorOverlay: true,
26 | notifyOnErrors: true,
27 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
28 |
29 | // Use Eslint Loader?
30 | // If true, your code will be linted during bundling and
31 | // linting errors and warnings will be shown in the console.
32 | useEslint: true,
33 | // If true, eslint errors and warnings will also be shown in the error overlay
34 | // in the browser.
35 | showEslintErrorsInOverlay: false,
36 |
37 | /**
38 | * Source Maps
39 | */
40 |
41 | // https://webpack.js.org/configuration/devtool/#development
42 | devtool: 'cheap-module-eval-source-map',
43 |
44 | // If you have problems debugging vue-files in devtools,
45 | // set this to false - it *may* help
46 | // https://vue-loader.vuejs.org/en/options.html#cachebusting
47 | cacheBusting: true,
48 |
49 | cssSourceMap: true
50 | },
51 |
52 | build: {
53 | // Template for index.html
54 | index: path.resolve(__dirname, '../dist/index.html'),
55 |
56 | // Paths
57 | assetsRoot: path.resolve(__dirname, '../../../upload/admin/view/javascript/foc_csv'),
58 | assetsSubDirectory: '',
59 | assetsPublicPath: '../../../upload/admin/view/javascript/foc_csv',
60 |
61 | /**
62 | * Source Maps
63 | */
64 |
65 | productionSourceMap: true,
66 | // https://webpack.js.org/configuration/devtool/#production
67 | devtool: '#source-map',
68 |
69 | // Gzip off by default as many popular static hosts such as
70 | // Surge or Netlify already gzip all static assets for you.
71 | // Before setting to `true`, make sure to:
72 | // npm install --save-dev compression-webpack-plugin
73 | productionGzip: false,
74 | productionGzipExtensions: ['js', 'css'],
75 |
76 | // Run the build command with an extra argument to
77 | // View the bundle analyzer report after build finishes:
78 | // `npm run build --report`
79 | // Set to `true` or `false` to always turn it on or off
80 | bundleAnalyzerReport: process.env.npm_config_report
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/frontend/foc_csv/config/prod.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | NODE_ENV: '"production"'
4 | }
5 |
--------------------------------------------------------------------------------
/frontend/foc_csv/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | foc_csv
7 |
8 |
9 |
10 |
11 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/frontend/foc_csv/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "foc_csv",
3 | "version": "1.0.0",
4 | "description": "foc_csv frontend",
5 | "author": "ikenfin ",
6 | "private": true,
7 | "scripts": {
8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
9 | "start": "yarn run dev",
10 | "lint": "eslint --ext .js,.vue src",
11 | "build": "node build/build.js"
12 | },
13 | "dependencies": {
14 | "autocomplete-vue": "^1.1.0",
15 | "papaparse": "^5.3.0",
16 | "vue": "^2.6.12",
17 | "vue-i18n": "^8.22.4",
18 | "vue-resource": "1.5.3",
19 | "vue-router": "^3.5.1",
20 | "vuex": "^3.6.2",
21 | "vuex-models": "^1.0.5"
22 | },
23 | "devDependencies": {
24 | "autoprefixer": "^7.1.2",
25 | "babel-core": "^6.22.1",
26 | "babel-eslint": "^8.2.1",
27 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
28 | "babel-loader": "^7.1.1",
29 | "babel-plugin-syntax-jsx": "^6.18.0",
30 | "babel-plugin-transform-runtime": "^6.22.0",
31 | "babel-plugin-transform-vue-jsx": "^3.5.0",
32 | "babel-preset-env": "^1.3.2",
33 | "babel-preset-stage-2": "^6.22.0",
34 | "chalk": "^2.0.1",
35 | "copy-webpack-plugin": "^4.0.1",
36 | "css-loader": "^0.28.0",
37 | "eslint": "^4.19.1",
38 | "eslint-config-standard": "^10.2.1",
39 | "eslint-friendly-formatter": "^3.0.0",
40 | "eslint-loader": "^1.7.1",
41 | "eslint-plugin-import": "^2.22.1",
42 | "eslint-plugin-node": "^5.2.0",
43 | "eslint-plugin-promise": "^3.4.0",
44 | "eslint-plugin-standard": "^3.0.1",
45 | "eslint-plugin-vue": "^4.0.0",
46 | "extract-text-webpack-plugin": "^3.0.0",
47 | "file-loader": "^1.1.4",
48 | "friendly-errors-webpack-plugin": "^1.6.1",
49 | "html-webpack-plugin": "^2.30.1",
50 | "node-notifier": "^5.1.2",
51 | "optimize-css-assets-webpack-plugin": "^3.2.0",
52 | "ora": "^1.2.0",
53 | "portfinder": "^1.0.28",
54 | "postcss-import": "^11.0.0",
55 | "postcss-loader": "^2.0.8",
56 | "postcss-url": "^7.2.1",
57 | "rimraf": "^2.6.0",
58 | "semver": "^5.3.0",
59 | "shelljs": "^0.7.6",
60 | "uglifyjs-webpack-plugin": "^1.1.1",
61 | "url-loader": "^0.5.8",
62 | "vue-loader": "^13.3.0",
63 | "vue-style-loader": "^3.0.1",
64 | "vue-template-compiler": "^2.6.12",
65 | "webpack": "^3.6.0",
66 | "webpack-bundle-analyzer": "^3.3.2",
67 | "webpack-dev-server": "^3.11.2",
68 | "webpack-merge": "^4.1.0"
69 | },
70 | "engines": {
71 | "node": ">= 6.0.0",
72 | "npm": ">= 3.0.0"
73 | },
74 | "browserslist": [
75 | "> 1%",
76 | "last 2 versions",
77 | "not ie <= 8"
78 | ]
79 | }
80 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
25 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/api/index.js:
--------------------------------------------------------------------------------
1 | import modules from './modules'
2 | /*
3 | ApiProvider
4 | vue plugin to simplify api calling
5 |
6 | it creates routes like this:
7 | Vue.$api.exporter.loadProfile()
8 | in the handler you have additional argument - mkUrl - module specific version of mkUrl:
9 | // exporter module:
10 | async saveProfile (mkUrl, options) {
11 | mkUrl('test') // --> 'test?__DEFAULT_PARAMS__&type=exporter
12 | }
13 |
14 | You can use default version of mkUrl by Vue.$api.mkUrl || this.$api.mkUrl (in components code)
15 | */
16 | class ApiProvider {
17 | constructor (modules) {
18 | this.modules = modules
19 | this._prepared = false
20 | }
21 |
22 | prepare (options) {
23 | if (!this._prepared) {
24 | this._config = options
25 |
26 | let proxied = {
27 | mkUrl: this.mkUrl.bind(this)
28 | }
29 |
30 | let modules = this.modules
31 |
32 | Object.keys(modules).forEach(type => {
33 | proxied[type] = {}
34 |
35 | Object.keys(modules[type]).forEach(method => {
36 | let moduleCallback = this._typedMkUrl(type)
37 | proxied[type][method] = (options) => modules[type][method](moduleCallback, options)
38 | })
39 | })
40 |
41 | this.proxy = proxied
42 | this._prepared = true
43 |
44 | return this
45 | }
46 | else {
47 | throw new Error('Trying to prepare already prepared $api!')
48 | }
49 | }
50 |
51 | install (Vue, options) {
52 | if (!this._prepared) {
53 | this.prepare(options)
54 | }
55 |
56 | Vue.$api = this.proxy
57 |
58 | Vue.mixin({
59 | created () {
60 | this.$api = Vue.$api
61 | }
62 | })
63 | }
64 |
65 | _typedMkUrl (type) {
66 | return (action, params) => {
67 | return this.mkUrl(action, { ...params, type })
68 | }
69 | }
70 |
71 | mkUrl (action, getParams = {}) {
72 | let getString = Object.keys(getParams)
73 | .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(getParams[key]))
74 | .join('&')
75 |
76 | if (getString.length > 0) {
77 | getString = `&${getString}`
78 | }
79 |
80 | return `${this._config.baseUrl}${this._config.baseRoute}/${action}&${this._config.tokenName}=${this._config.token}${getString}`
81 | }
82 | }
83 |
84 | const provider = new ApiProvider(modules)
85 | export default provider
86 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/api/modules/exporter.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import {
3 | EXPORT_URL,
4 | SAVE_PROFILE_URL,
5 | SAVE_ALL_PROFILES_URL
6 | } from '@/api/routes'
7 |
8 | export default {
9 | async saveProfile (mkUrl, options) {
10 | return Vue.http.post(mkUrl(SAVE_PROFILE_URL), options)
11 | },
12 |
13 | async saveProfiles (mkUrl, profiles) {
14 | return Vue.http.post(mkUrl(SAVE_ALL_PROFILES_URL), {
15 | profiles
16 | })
17 | },
18 |
19 | async submitData (mkUrl, profile) {
20 | let request = new FormData()
21 | request.append('profile-json', JSON.stringify(profile))
22 |
23 | return Vue.http.post(mkUrl(EXPORT_URL), request, {
24 | headers: {
25 | 'Content-Type': 'multipart/form-data'
26 | }
27 | })
28 | },
29 |
30 | async submitPart (_mkUrl, { callbackUrl, options }) {
31 | return Vue.http.post(callbackUrl, options)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/api/modules/importer.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | import {
4 | IMPORT_URL,
5 | SAVE_PROFILE_URL,
6 | SAVE_ALL_PROFILES_URL
7 | } from '@/api/routes'
8 |
9 | export default {
10 |
11 | async saveProfile (mkUrl, options) {
12 | return Vue.http.post(mkUrl(SAVE_PROFILE_URL), options)
13 | },
14 |
15 | async saveProfiles (mkUrl, profiles) {
16 | return Vue.http.post(mkUrl(SAVE_ALL_PROFILES_URL), {
17 | profiles
18 | })
19 | },
20 |
21 | async submitData (mkUrl, { data, profile }) {
22 | let request = new FormData()
23 | request.append('csv-file', data.csvFileRef.files[0])
24 |
25 | if (data.imagesZipFileRef && data.imagesZipFileRef.files.length > 0) {
26 | request.append('images-zip', data.imagesZipFileRef.files[0])
27 | }
28 |
29 | request.append('profile-json', JSON.stringify(profile))
30 |
31 | return Vue.http.post(mkUrl(IMPORT_URL), request, {
32 | headers: {
33 | 'Content-Type': 'multipart/form-data'
34 | }
35 | })
36 | },
37 |
38 | async submitPart (_mkUrl, { callbackUrl, options }) {
39 | return Vue.http.post(callbackUrl, options)
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/api/modules/index.js:
--------------------------------------------------------------------------------
1 | import importer from './importer'
2 | import exporter from './exporter'
3 |
4 | export default {
5 | importer,
6 | exporter
7 | }
8 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/api/routes.js:
--------------------------------------------------------------------------------
1 | /*
2 | Backend routes
3 | please keep in mind, that baseUrl provided by Application config (main.js, requestConfig)
4 | */
5 |
6 | const SAVE_PROFILE_URL = 'saveProfile'
7 | const SAVE_ALL_PROFILES_URL = 'saveProfiles'
8 | const IMPORT_URL = 'import'
9 | const EXPORT_URL = 'export'
10 | const ATTRIBUTES_GROUP_AUTOCOMPLETE_URL = 'attributesGroupAutocomplete'
11 |
12 | export {
13 | SAVE_PROFILE_URL,
14 | SAVE_ALL_PROFILES_URL,
15 | IMPORT_URL,
16 | EXPORT_URL,
17 | ATTRIBUTES_GROUP_AUTOCOMPLETE_URL
18 | }
19 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Freeocart/fo-csv/6dcd8eaa1b125c873f9637ba93fbfd634622e02e/frontend/foc_csv/src/assets/logo.png
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/common/ErrorMessage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{ $t('There is errors while doing job!') }}
5 |
{{ messages }}
6 |
{{ $t('Errors count') }} - {{ errors }}
7 |
8 |
9 |
10 |
11 |
22 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/common/Navigation.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/common/ProgressBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{ parseInt(progressValue) }}%
5 |
6 |
7 |
8 |
30 |
31 |
62 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/common/attributeWidgets/TextAttribute.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/common/attributeWidgets/TextareaAttribute.vue:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
15 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/common/attributeWidgets/index.js:
--------------------------------------------------------------------------------
1 | import TextAttribute from './TextAttribute'
2 | import TextareaAttribute from './TextareaAttribute'
3 |
4 | export default {
5 | TextAttribute,
6 | TextareaAttribute
7 | }
8 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/exporter/AttributesEncoder.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
20 |
21 |
22 |
23 |
24 |
77 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/exporter/Export.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ $t('Export in progress') }}
7 |
8 |
11 |
12 |
13 |
14 |
15 | {{ $t('During export we catched some errors, please check foc logs!') }}
16 |
17 |
18 |
19 |
20 | {{ $t('Complete') }}
21 |
22 |
23 |
{{ $t('Check your download links:') }}
24 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
{{ $t('Export submodule') }}
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | {{ $t('Fields settings') }}
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
200 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/exporter/ExportFields.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
100 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/exporter/ImagesExportSettings.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ $t('Images settings') }}
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
45 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/exporter/LeftSidebar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ $t('Main settings') }}
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
18 |
19 |
20 |
21 |
22 |
24 |
25 |
26 |
27 |
28 |
29 |
32 |
33 |
34 |
35 |
36 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | {{ $t('Attributes export') }}
48 |
49 |
50 |
53 |
54 |
55 |
56 |
57 |
104 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/exporter/RightSidebar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ $t('Processing settings') }}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
74 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/exporter/attributeWidgets/index.js:
--------------------------------------------------------------------------------
1 | import CommonWidgets from '../../common/attributeWidgets'
2 |
3 | export default {
4 | ...CommonWidgets
5 | }
6 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/importer/AdditionalProcessingSettings.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ $t('Additional processing settings') }}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
21 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/importer/AttributesParser.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
20 |
21 |
22 |
23 |
29 |
30 |
31 |
32 |
33 |
38 |
39 |
40 |
50 |
51 |
52 |
53 |
117 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/importer/CsvColumnNameBinder.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
21 |
22 |
23 |
62 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/importer/CsvFileUpload.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
{{ $t('Something wrong with your file! Please choose another.') }}
18 |
{{ $t(errorMessage) }}
19 |
20 |
21 |
22 |
23 |
106 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/importer/CsvToDbMatcher.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{ $t('CSV field') }} |
9 | {{ $t('DB field') }} |
10 |
11 |
12 |
13 |
14 |
15 | {{ field }}
16 | |
17 |
18 |
19 | |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
73 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/importer/DbFieldsSelect.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
42 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/importer/ImagesImportSettings.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
32 |
33 |
34 |
35 |
39 |
40 |
41 |
42 |
43 |
62 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/importer/ImagesZipUpload.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
23 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/importer/Import.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ $t('Import in progress') }}
7 |
8 |
11 |
12 |
13 |
14 |
15 |
16 |
{{ $t('We catched some errors during import, please check foc logs!') }}
17 |
18 |
19 |
20 |
21 |
22 | {{ $t('Complete') }}
23 |
24 |
25 |
{{ $t('Import task successfully complete!') }}
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
{{ $t('Import submodule') }}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | {{ $t('Fields settings') }}
49 |
50 |
51 |
52 |
53 |
54 |
64 |
65 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
99 |
100 |
101 |
102 |
103 |
262 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/importer/LeftSidebar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ $t('Main settings') }}
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 |
31 |
32 |
33 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | {{ $t('Images settings') }}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | {{ $t('Attributes import') }}
51 |
52 |
53 |
57 |
58 |
59 |
60 |
61 |
108 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/importer/LineSkipSettings.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | -
7 | {{ item.name }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
72 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/importer/MultiCsvFieldsSelector.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
45 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/importer/MulticolumnExtractor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ $t('Template variables') }} |
8 | {{ $t('Preprocess value template') }} |
9 | {{ $t('DB field') }} |
10 | {{ $t('Mode') }} |
11 | {{ $t('Control') }} |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
32 | |
33 |
34 |
36 | |
37 |
38 |
39 | |
40 |
41 |
46 | |
47 |
48 |
51 | |
52 |
53 |
54 | |
55 |
56 |
59 | |
60 |
61 |
62 |
63 |
64 |
65 | {{ $t('Unavailable') }}
66 | {{ $t('CSV file not selected') }}
67 | |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
124 |
125 |
136 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/importer/RightSidebar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ $t('Processing settings') }}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
27 |
28 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
77 |
78 |
79 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
94 |
95 |
96 |
101 |
102 |
103 |
107 |
108 |
109 |
110 |
111 |
156 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/importer/StatusRewrites.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ $t('Opencart status') }} |
7 | {{ $t('CSV status') }} |
8 |
9 |
10 |
11 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
43 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/importer/StatusRewritesItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ name }}
5 | |
6 |
7 |
8 | |
9 |
10 |
11 |
12 |
40 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/importer/attributeWidgets/ColumnAttribute.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
37 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/importer/attributeWidgets/DBColumnAttribute.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
32 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/importer/attributeWidgets/index.js:
--------------------------------------------------------------------------------
1 | import ColumnAttribute from './ColumnAttribute'
2 | import DBColumnAttribute from './DBColumnAttribute'
3 |
4 | import CommonWidgets from '../../common/attributeWidgets'
5 |
6 | export default {
7 | ColumnAttribute,
8 | DBColumnAttribute,
9 | ...CommonWidgets
10 | }
11 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/info/Info.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
{{ $t('Disclaimer') }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | {{ $t('How to use') }}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
69 |
70 |
71 |
72 |
73 |
77 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/util/BackupRestore.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ $t('Import utils') }}
6 |
7 |
8 |
9 |
10 |
11 |
12 | {{ $t('Export utils') }}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
30 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/util/ExportProfileData.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ $t('Backup utils') }}
7 |
8 |
9 |
10 |
11 | {{ $t('Show current profile data') }}
12 |
13 |
14 |
15 | {{ $t('Show profiles data') }}
16 |
17 |
18 |
19 | {{ $t('Show all data state') }}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | {{ $t('Restore utils') }}
28 |
29 |
30 |
31 |
32 | {{ $t('Restore state to profile') }}
33 |
34 |
35 |
36 | {{ $t('Restore profiles') }}
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | {{ $t('Remove utils') }}
45 |
46 |
49 |
50 |
51 |
52 |
53 |
54 |
73 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/util/ImportProfileData.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ $t('Backup utils') }}
7 |
8 |
9 |
10 |
11 | {{ $t('Show current profile data') }}
12 |
13 |
14 |
15 | {{ $t('Show profiles data') }}
16 |
17 |
18 |
19 | {{ $t('Show all data state') }}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {{ $t('Restore utils') }}
29 |
30 |
31 |
32 |
33 | {{ $t('Restore state to profile') }}
34 |
35 |
36 |
37 | {{ $t('Restore profiles') }}
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | {{ $t('Remove utils') }}
47 |
48 |
49 |
54 |
55 |
56 |
57 |
58 |
59 |
78 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/util/ProfilesControlList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{ name }}
9 | |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
39 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/util/RestoreProfile.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
24 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/util/RestoreProfiles.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
41 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/components/util/SerializedDataToggler.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
28 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/config.js:
--------------------------------------------------------------------------------
1 | /*
2 | Application config
3 | */
4 | const DEFAULT_PROFILE_NAME = 'default'
5 |
6 | export {
7 | DEFAULT_PROFILE_NAME
8 | }
9 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/helpers.js:
--------------------------------------------------------------------------------
1 | import papaparse from 'papaparse'
2 |
3 | /*
4 | Validate required fields in app config
5 | */
6 | const validateAppConfig = (config) => {
7 | const requiredFields = ['token', 'baseRoute', 'baseUrl']
8 |
9 | const keys = Object.keys(config)
10 | const values = Object.values(config)
11 |
12 | if (requiredFields.every(key => keys.includes(key)) &&
13 | values.every(value => !!value)) {
14 | return true
15 | }
16 |
17 | return false
18 | }
19 |
20 | /*
21 | Read first string from blob
22 | */
23 | class FirstLineReader {
24 | constructor () {
25 | this.events = {}
26 |
27 | this.chunkSize = 256
28 | this.readPos = 0
29 | this.reader = new FileReader()
30 | this.lines = []
31 | this.chunk = ''
32 | this.file = null
33 | this.readedLines = 0
34 | this.skippedBytes = 0
35 |
36 | this.reader.onload = () => {
37 | // it seems that broken symbols automaticaly converts to \uFFFD
38 | // so we just check line endings and try to read again
39 | // one byte more
40 | if (/\uFFFD$/.test(this.reader.result)) {
41 | this.skippedBytes++
42 | this.step()
43 | }
44 | else {
45 | this.readPos += this.chunkSize + this.skippedBytes
46 | this.skippedBytes = 0
47 | this.chunk += this.reader.result
48 | this.process()
49 | }
50 | }
51 | }
52 |
53 | on (event, cb) {
54 | this.events[event] = cb
55 | }
56 |
57 | _emit (event, args) {
58 | if (typeof this.events[event] === 'function') {
59 | this.events[event].apply(this, args)
60 | }
61 | }
62 |
63 | process () {
64 | /*
65 | Firstly check if file have line breaks at all
66 | Unix: \n
67 | Windows: \r\n
68 | Old macs: \r
69 | */
70 | if (/(\r\n|\r|\n)/.test(this.chunk)) {
71 | const lines = this.chunk.split(/(\r\n|\r|\n)/)
72 | const line = lines.shift()
73 |
74 | this.readedLines++
75 |
76 | if (this.readedLines === this.skipLines) {
77 | this._emit('line', [line])
78 | }
79 | else {
80 | this.chunk = lines.join('\n')
81 | this.step()
82 | }
83 | }
84 | else {
85 | if (this.readPos < this.file.size) {
86 | this.step()
87 | }
88 | else {
89 | this._emit('error', [
90 | `Seems that file is empty or has invalid newline characters. Please make sure to convert file into valid format.`
91 | ])
92 | }
93 | }
94 | }
95 |
96 | read (file, encoding, skipLines = 1) {
97 | this.file = file
98 | this.lines = []
99 | this.chunk = ''
100 | this.readPos = 0
101 | this.encoding = encoding || 'UTF8'
102 | this.skipLines = parseInt(skipLines)
103 |
104 | // minimum 1
105 | if (this.skipLines <= 0) {
106 | this.skipLines = 1
107 | }
108 |
109 | if (isNaN(this.skipLines)) {
110 | this._emit('error', [
111 | 'Please make sure if skip lines option is number!'
112 | ])
113 | }
114 | else {
115 | this.step()
116 | }
117 | }
118 |
119 | step () {
120 | let blob = this.file.slice(
121 | this.readPos,
122 | this.readPos + this.chunkSize + this.skippedBytes
123 | )
124 |
125 | this.reader.readAsText(blob, this.encoding)
126 | }
127 | }
128 |
129 | /*
130 | Valid mimetypes for csv
131 | https://stackoverflow.com/questions/7076042/what-mime-type-should-i-use-for-csv#answer-42140178
132 | */
133 | const VALID_CSV_MIMETYPES = [
134 | 'text/csv',
135 | 'text/x-csv',
136 | 'text/comma-separated-values',
137 | 'text/x-comma-separated-values',
138 | 'text/tab-separated-values',
139 | 'application/vnd.ms-excel',
140 | 'application/csv',
141 | 'application/x-csv'
142 | ]
143 |
144 | /*
145 | Simple csv mime-type or filename check
146 | */
147 | const isFileSeemsLikeCsv = (file) => {
148 | const validMime = VALID_CSV_MIMETYPES.includes(file.type)
149 | const validExt = /^.*\.csv$/.test(file.name)
150 | return validMime || (file.type === '' && validExt) || false
151 | }
152 |
153 | /*
154 | Parse csv headers - stupid split function:)
155 | */
156 | const parseCsvHeaders = (raw, delimiter = ';') => {
157 | const result = papaparse.parse(raw, { delimiter })
158 | return result.data.length > 0 ? result.data[0] : []
159 | }
160 |
161 | /*
162 | Validate profile
163 | for now - just check keyField exists
164 | */
165 | const validateProfile = (profile) => {
166 | // validate key field
167 | return profile.keyField && profile.bindings[profile.keyField] != null
168 | }
169 |
170 | export {
171 | validateAppConfig,
172 | FirstLineReader,
173 | parseCsvHeaders,
174 | validateProfile,
175 | isFileSeemsLikeCsv
176 | }
177 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/i18n.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueI18n from 'vue-i18n'
3 |
4 | Vue.use(VueI18n)
5 |
6 | export default (locale) => {
7 | return new VueI18n({
8 | locale,
9 | messages: {
10 | en: require('./i18n/en.json'),
11 | ru: require('./i18n/ru.json')
12 | }
13 | })
14 | }
15 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/i18n/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "Module {name} targets to be a maximum flexible for import/export CSV data, without any restrictions in CSV format.": "Module {name} targets to be a maximum flexible for import/export CSV data, without any restrictions in CSV format.",
3 | "Thank you for choosing {name} opencart module!": "Thank you for choosing {name} opencart module!",
4 | "focsv-support@freeocart.ru": "support@freeocart.ru",
5 | "Please make sure if skip lines option is number!": "Please make sure that headers line number option is valid number!",
6 | "Please, look for instructions below": "For additional information, please visit project website - freeocart.ru"
7 | }
8 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/i18n/ru.json:
--------------------------------------------------------------------------------
1 | {
2 | "Not selected": "Не выбрано",
3 | "Import submodule": "Импорт из CSV",
4 | "Export submodule": "Экспорт в CSV",
5 | "Import": "Импорт",
6 | "Export": "Экспорт",
7 | "Util": "Утилиты",
8 | "Mode": "Режим",
9 | "Import utils": "Утилиты импортера",
10 | "Export utils": "Утилиты экспортера",
11 | "Add": "Добавить",
12 | "Delete": "Удалить",
13 | "Unavailable": "Недоступно",
14 | "soon": "скоро",
15 | "Show/hide": "Показать/скрыть",
16 | "Import in progress": "Импорт данных в процессе",
17 | "Export in progress": "Экспорт данных в процессе",
18 | "Start import!": "Начинаем импорт!",
19 | "Start export!": "Начинаем экспорт!",
20 | "Main settings": "Основные настройки",
21 | "Profile": "Профиль",
22 | "Save profile as": "Сохранить профиль как",
23 | "Save profile": "Сохранить профиль как",
24 | "Store": "Магазин",
25 | "Language": "Язык",
26 | "Encoding": "Кодировка",
27 | "Images settings": "Настройки изображений",
28 | "Images ZIP file": "ZIP Архив картинок",
29 | "Images list delimiter": "Разделитель в поле изображений",
30 | "If no preview - set it from gallery": "Если нет превью - установить из галереи",
31 | "Clear gallery before import": "Удалить изображения из галереи перед импортом",
32 | "Download images with URL": "Подкачивать картинки с URL",
33 | "Images import mode": "Режим установки изображений",
34 | "Add images": "Добавить загруженные",
35 | "Skip if gallery has images": "Не добавлять если галерея не пуста",
36 | "Fields settings": "Управление полями",
37 | "CSV file": "CSV файл",
38 | "Process lines per query": "Сколько обновлять записей за раз",
39 | "CSV headers control": "Управление заголовками CSV",
40 | "CSV without headers": "CSV без заголовков",
41 | "CSV headers line number": "Номер строки с заголовками",
42 | "Skip lines": "Пропустить строки",
43 | "Key field": "Ключевое поле (по нему идет сличение)",
44 | "Fields matching": "Сопоставление полей",
45 | "CSV field": "CSV поле",
46 | "DB field": "DB поле",
47 | "Field delimiter": "Разделитель полей",
48 | "Clear manufacturers before import": "Удалить производителей перед импортом",
49 | "Import mode": "Режим импорта",
50 | "Only update existing": "Только обновить существующие",
51 | "Force add all as new": "Добавить все как новые",
52 | "Update existing and add new": "Обновить существующие и добавить новые",
53 | "Only add missing as new": "Только добавить отсутствующие",
54 | "Remove all matched": "Удалить совпавшие",
55 | "Remove all unmatched": "Удалить несовпавшие",
56 | "Category level delimiter": "Разделитель вложенности категорий",
57 | "Categories delimiter": "Разделитель категорий",
58 | "Fill parent categories": "Заполнить родительские категории",
59 | "Remove chars from category fields": "Удалить символы из поля категорий",
60 | "Opencart status": "Статус в Opencart",
61 | "CSV status": "Статус в CSV",
62 | "Processing settings": "Настройки обработки",
63 | "Replace existing attributes": "Заменить существующие атрибуты",
64 | "Default attributes group": "Группа по умолчанию для атрибутов",
65 | "Attributes import": "Импорт атрибутов",
66 | "Attributes field": "Поле для извлечения атрибутов",
67 | "Attributes parser": "Парсер атрибутов",
68 |
69 | "Backup utils": "Утилиты бэкапирования",
70 | "Restore utils": "Утилиты восстановления",
71 |
72 | "There is errors while doing job!": "Во время выполнения возникли ошибки!",
73 |
74 | "Show current profile data": "Показать данные текущего профиля",
75 | "Show profiles data": "Показать данные всех профилей",
76 | "Show all data state": "Показать состояние",
77 |
78 | "Restore profiles": "Восстановить профили",
79 | "Restore state to profile": "Восстановить в профиль",
80 | "Profile name": "Название профиля",
81 | "Restore": "Восстановить",
82 | "Are you sure? This will remove all profiles before trying to add new ones!": "Вы уверены? Это действие удалит все существующие профили перед тем как добавить новые!",
83 |
84 | "Remove utils": "Утилиты удаления",
85 | "Profile control": "Управление профилями",
86 | "Are you sure you want remove this item?": "Вы уверены что хотите удалить этот элемент?",
87 | "Add new column binding": "Добавить новую привязку к столбцу",
88 | "Attribute name": "Название атрибута",
89 | "Attribute value": "Значение атрибута",
90 | "Skip line if fields empty": "Пропустить строку если поля пустые",
91 | "Default status": "Статус по умолчанию",
92 | "Default stock status": "Складской статус",
93 |
94 | "We catched some errors during import, please check foc logs!": "Во время импорта произошли ошибки, пожалуйста, проверьте логи foc!",
95 | "During export we catched some errors, please check foc logs!": "Во время экспорта произошли ошибки, пожалуйста, проверьте логи foc!",
96 | "Errors count": "Количество ошибок",
97 |
98 | "Error during sending!": "Ошибка во время отправки данных!",
99 | "Invalid profile!": "Ошибки в профиле!",
100 | "Import canceled!": "Импорт отменён!",
101 | "Re-read CSV": "Перечитать CSV",
102 | "Something wrong with your file! Please choose another.": "С выбранным файлом что-то не так! Пожалуйста, попробуйте другой.",
103 | "Seems that file is empty or has invalid newline characters. Please make sure to convert file into valid format.": "Похоже что вы пытаетесь использовать файл с неподдерживаемыми разделителями строк. Пожалуйста, сконвертируйте файл корректно",
104 | "Please make sure if skip lines option is number!": "Пожалуйста, убедитесь что параметр Номер строки с заголовками является числом!",
105 | "Error while reading file": "Ошибка чтения файла",
106 | "Wrong file format": "Некорректный формат файла.",
107 |
108 | "Attention! You chosed a danger import mode!": "Внимание! Вы выбрали опасный режим импорта!",
109 | "Before import from CSV, we will remove all product related data from your database!": "Перед импортом данных из CSV, будут удалены все данные о продуктах из вашей базы данных!",
110 | "Please, make sure you have working backup before you continue!": "Пожалуйста, убедитесь что у вас есть рабочий бэкап перед тем как продолжать!",
111 | "Are you totally sure you want do this???! With this import method you can lose your data!": "Вы абсолютно уверены, что хотите продолжить???! Этот режим импорта может привести к потере всех ваших данных!",
112 |
113 | "Complete": "Завершено",
114 | "Import task successfully complete!": "Импорт данных успешно завершён!",
115 |
116 | "Dump parent categories": "Выгружать родительские категории",
117 | "Export images mode": "Режим экспорта изображений",
118 | "Create images ZIP": "Создать ZIP архив с изображениями",
119 | "Render CSV header": "Добавить CSV заголовки",
120 | "CSV header": "CSV заголовок",
121 | "Control": "Управление",
122 | "Add new db field binding": "Добавить новую привязку к полю БД",
123 | "There is no created bindings": "Нет добавленных привязок",
124 | "Reset db field bindings": "Сбросить привязки к полям",
125 | "Any status": "Любой статус",
126 | "Export with status": "Выгружать со статусом",
127 | "Attributes encoder": "Кодировщик атрибутов",
128 | "Attributes export": "Экспорт атрибутов",
129 |
130 | "Check your download links:": "Ваши ссылки для скачивания файлов:",
131 | "Additional processing settings": "Настройки дополнительной обработки",
132 | "Multicolumn fields settings": "Несколько колонок в одно поле",
133 | "Preprocess value template": "Шаблон преобразования значения",
134 | "CSV file not selected": "Файл CSV не выбран",
135 | "Template variables": "Переменные шаблона",
136 | "Template variable name": "Имя переменной в шаблоне",
137 | "Add new template variable": "Добавить новую переменную шаблона",
138 | "Remove template variable": "Удалить переменную шаблона",
139 |
140 | "Replace CSV field data": "Заменить значение из CSV поля",
141 | "Add after CSV field data": "Добавить после значения CSV поля",
142 | "Add before CSV field data": "Добавить до значения CSV поля",
143 |
144 | "Info": "Информация",
145 | "Application information": "Информация о ПО",
146 | "How to use": "Как пользоваться",
147 | "Disclaimer": "Дисклеймер",
148 | "Module {name} targets to be a maximum flexible for import/export CSV data, without any restrictions in CSV format.": "Модуль {name} задумывался как максимально гибкое решение для импорта/экспорта данных. Нет каких-либо ограничений на формат входных CSV файлов.",
149 | "However, more flexibility costs more complexity, so if you not sure what you do, please, entrust the work to more qualified employee!": "Однако, такая гибкость сопряжена с довольно высокой сложностью использования, поэтому, если вы не уверены в своей квалификации, лучше доверьте работу с модулем более квалифицированному сотруднику!",
150 | "Please, look for instructions below": "Для получения дополнительной информации - заходите на страницу проекта - freeocart.ru",
151 | "Thank you for choosing {name} opencart module!": "Спасибо за выбор модуля {name}!",
152 | "I hope, my work will be useful for you": "Я надеюсь, моя работа была полезна для вас",
153 | "If you have any questions, found bugs, or want request a features, you can send me email, or use github issues to inform me.": "Если у вас есть вопросы, вы нашли ошибки, или хотите новые возможности, пожалуйста, пришлите мне email, или отпишитесь в issues на github.",
154 | "focsv-support@freeocart.ru": "support@freeocart.ru",
155 | "Use this software at your own risk!": "Используйте данное ПО на свой страх и риск!",
156 | "Author or contributors are not responsible at data corruption/remove caused by using this software.": "Ни автор, ни контрибьюторы проекта не отвечают за порчу/удаление данных, возникшие при использовании этого ПО.",
157 | "We are strongly recommend you to backup data before using this.": "Мы настоятельно рекомендуем делать бэкап перед использованием модуля.",
158 | "Source code distributed 'as is', all parts of the software is free and opensource and respecting GPLv3 license.": "Код распространяется 'как есть', все части ПО являются открытым программным обеспечением, распространяемым согласно лицензии GPLv3."
159 | }
160 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/main.js:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 | import Vue from 'vue'
4 | import resource from 'vue-resource'
5 | import App from './App'
6 | import router from './router'
7 | import i18n from './i18n'
8 |
9 | import store from './store'
10 | import ApiProvider from './api'
11 | import { validateAppConfig } from './helpers'
12 |
13 | Vue.use(resource)
14 | Vue.config.productionTip = false
15 |
16 | let AppConfig = {}
17 |
18 | if (window.FOC_CSV_PARAMS) {
19 | AppConfig = Object.assign(AppConfig, window.FOC_CSV_PARAMS)
20 | }
21 |
22 | if (validateAppConfig(AppConfig.requestConfig)) {
23 | // configure api provider
24 | let api = ApiProvider.prepare(AppConfig.requestConfig)
25 | Vue.use(api)
26 |
27 | // set initial data in vuex modules
28 | store.dispatch('importer/setInitialData', Object.assign({}, AppConfig.initial.importer, AppConfig.initial.common))
29 | store.dispatch('exporter/setInitialData', Object.assign({}, AppConfig.initial.exporter, AppConfig.initial.common))
30 |
31 | const language = AppConfig.language || 'en'
32 | /* eslint-disable no-new */
33 | Vue.prototype.$appName = AppConfig.appName
34 | Vue.prototype.$appVersion = AppConfig.appVersion
35 |
36 | new Vue({
37 | el: '#foc_csv',
38 | router,
39 | store,
40 | i18n: i18n(language),
41 | components: { App },
42 | template: ''
43 | })
44 | }
45 | else {
46 | console.error('Failed to initialize! Wrong AppConfig!')
47 | }
48 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/mixins/util.js:
--------------------------------------------------------------------------------
1 | /*
2 | Util import/export mixin
3 | used in util components both by export and import tools
4 | */
5 | import { createNamespacedHelpers } from 'vuex'
6 |
7 | export default function (module) {
8 | const { mapState, mapGetters, mapActions } = createNamespacedHelpers(module)
9 |
10 | return {
11 | computed: {
12 | ...mapState([
13 | 'data'
14 | ]),
15 | ...mapGetters([
16 | 'profile',
17 | 'profiles'
18 | ])
19 | },
20 | methods: {
21 | restoreToProfile ({ name, profile }) {
22 | this.applyProfile({ name, profile })
23 | this.saveNewProfile(name)
24 | },
25 | deleteProfile (name) {
26 | if (confirm(this.$t('Are you sure you want remove this item?'))) {
27 | if (this.currentProfileName === name) {
28 | this.setCurrentProfileName('default')
29 | }
30 |
31 | this.storeDeleteProfile(name)
32 | this.saveAllProfiles(this.profiles)
33 | }
34 | },
35 | restoreProfiles (profiles) {
36 | if (confirm(this.$t('Are you sure? This will remove all profiles before trying to add new ones!'))) {
37 | this.saveAllProfiles(profiles)
38 | }
39 | },
40 | ...mapActions([
41 | 'saveAllProfiles',
42 | 'setCurrentProfileName',
43 | 'applyProfile',
44 | 'saveNewProfile'
45 | ]),
46 | ...mapActions({
47 | storeDeleteProfile: 'deleteProfile'
48 | })
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import Import from '@/components/importer/Import'
4 | import Export from '@/components/exporter/Export'
5 | import BackupRestore from '@/components/util/BackupRestore'
6 | import Info from '@/components/info/Info'
7 |
8 | Vue.use(Router)
9 |
10 | export default new Router({
11 | routes: [
12 | {
13 | path: '/',
14 | redirect: '/import'
15 | },
16 | {
17 | path: '/import',
18 | name: 'Import',
19 | component: Import
20 | },
21 | {
22 | path: '/export',
23 | name: 'Export',
24 | component: Export
25 | },
26 | {
27 | path: '/util',
28 | name: 'Backup/Restore',
29 | component: BackupRestore
30 | },
31 | {
32 | path: '/info',
33 | name: 'Info',
34 | component: Info
35 | }
36 | ]
37 | })
38 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/store/common/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | Actions for both importer and exporter
3 | */
4 | import { DEFAULT_PROFILE_NAME } from '@/config'
5 |
6 | export default {
7 | setInitialData ({ commit, getters }, data) {
8 | commit('SET_INITIAL_DATA', data)
9 | commit('SET_CURRENT_PROFILE_NAME', DEFAULT_PROFILE_NAME)
10 | commit('SET_CURRENT_PROFILE', getters.currentProfile)
11 | },
12 | setCurrentProfileName ({ commit, getters }, profile) {
13 | commit('SET_CURRENT_PROFILE_NAME', profile)
14 | commit('SET_CURRENT_PROFILE', getters.currentProfile)
15 | },
16 | applyProfile ({ commit }, { name, profile }) {
17 | commit('ADD_PROFILE', { name, profile })
18 | commit('SET_CURRENT_PROFILE_NAME', name)
19 | },
20 | deleteProfile ({ commit }, name) {
21 | commit('DELETE_PROFILE', name)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/store/common/getters.js:
--------------------------------------------------------------------------------
1 | /*
2 | Getters for both importer and exporter
3 | */
4 | import { DEFAULT_PROFILE_NAME } from '@/config'
5 |
6 | export default {
7 | currentProfileName (state) {
8 | return state.currentProfile
9 | },
10 | currentProfile (state) {
11 | let profileData = state.data.profiles[state.currentProfile]
12 |
13 | if (!profileData) {
14 | profileData = state.data.profiles[DEFAULT_PROFILE_NAME]
15 | }
16 |
17 | return profileData
18 | },
19 | profile (state) {
20 | return state.profile
21 | },
22 | encodings (state) {
23 | return state.data.encodings
24 | },
25 | dbFields (state) {
26 | return state.data.dbFields
27 | },
28 | languages (state) {
29 | return state.data.languages
30 | },
31 | stores (state) {
32 | return state.data.stores
33 | },
34 | profiles (state) {
35 | return state.data.profiles
36 | },
37 | statuses (state) {
38 | return state.data.statuses
39 | },
40 | stock_statuses (state) {
41 | return state.data.stock_statuses
42 | },
43 | statusRewrites (state) {
44 | return state.profile.statusRewrites || {}
45 | },
46 | stockStatusRewrites (state) {
47 | return state.profile.stockStatusRewrites || {}
48 | },
49 | submittableData (state) {
50 | return {
51 | profile: state.profile,
52 | data: state.data
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/store/common/mutations.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | export default {
4 | SET_INITIAL_DATA (state, initial) {
5 | Vue.set(state, 'data', initial)
6 | },
7 | SET_CURRENT_PROFILE_NAME (state, profileName) {
8 | state.currentProfile = profileName
9 | },
10 | SET_CURRENT_PROFILE (state, profile) {
11 | state.profile = profile
12 | },
13 | SAVE_NEW_PROFILE (state, name) {
14 | let profileSettings = Object.assign({}, state.profile)
15 | Vue.set(state.data.profiles, name, profileSettings)
16 | },
17 | ADD_PROFILE (state, { name, profile }) {
18 | Vue.set(state.data.profiles, name, profile)
19 | },
20 | DELETE_PROFILE (state, name) {
21 | if (state.data.profiles[name]) {
22 | Vue.delete(state.data.profiles, name)
23 | }
24 | },
25 | SET_PROFILES (state, profiles) {
26 | Vue.set(state.data, 'profiles', profiles)
27 | },
28 | CLEAR_ALL_PROFILES (state) {
29 | Vue.set(state.data, 'profiles', {})
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/store/exporter/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | Actions for exporter module
3 | */
4 |
5 | import Vue from 'vue'
6 | import commonActions from '@/store/common/actions'
7 |
8 | export default {
9 | ...commonActions,
10 | setAttributeEncoder ({ commit }, encoder) {
11 | commit('SET_ATTRIBUTE_ENCODER', encoder)
12 | },
13 | setAttributeEncoderData ({ commit }, data) {
14 | commit('SET_ATTRIBUTE_ENCODER_DATA', data)
15 | },
16 | async saveNewProfile ({ commit, state }, name) {
17 | try {
18 | await Vue.$api.exporter.saveProfile({
19 | name,
20 | profile: state.profile
21 | })
22 |
23 | commit('SAVE_NEW_PROFILE', name)
24 | commit('SET_CURRENT_PROFILE_NAME', name)
25 | }
26 | catch (e) {
27 | console.log(e)
28 | alert('error on profile saving!')
29 | }
30 | },
31 | async saveAllProfiles ({ commit }, profiles) {
32 | await Vue.$api.exporter.saveProfiles(profiles)
33 |
34 | commit('CLEAR_ALL_PROFILES')
35 | commit('SET_PROFILES', profiles)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/store/exporter/getters.js:
--------------------------------------------------------------------------------
1 | import commonGetters from '@/store/common/getters'
2 |
3 | export default {
4 | attributeEncoders (state) {
5 | return state.data.attributeEncoders
6 | },
7 | currentAttributeEncoder (state) {
8 | return state.profile.attributeEncoder
9 | },
10 | attributeEncoderOptions (state) {
11 | let encoder = state.profile.attributeEncoder
12 |
13 | if (!encoder || !state.data.attributeEncoders[encoder]) {
14 | return []
15 | }
16 |
17 | if (state.data.attributeEncoders[encoder].options) {
18 | return state.data.attributeEncoders[encoder].options
19 | }
20 |
21 | return []
22 | },
23 | attributeEncoderOptionData (state) {
24 | let encoder = state.profile.attributeEncoder
25 | return state.profile.attributeEncoderData[encoder] || []
26 | },
27 | ...commonGetters
28 | }
29 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/store/exporter/index.js:
--------------------------------------------------------------------------------
1 | import state from './state'
2 | import mutations from './mutations'
3 | import actions from './actions'
4 | import getters from './getters'
5 |
6 | import { genVuexModels } from 'vuex-models'
7 |
8 | let fields = genVuexModels([
9 | 'entriesPerQuery',
10 | 'encoding',
11 | 'dumpParentCategories',
12 | 'categoriesNestingDelimiter',
13 | 'categoriesDelimiter',
14 | 'exportImagesMode',
15 | 'createImagesZIP',
16 | 'galleryImagesDelimiter',
17 | 'csvFieldDelimiter',
18 | 'store',
19 | 'language',
20 | 'csvFieldDelimiter',
21 | 'csvHeader',
22 | 'bindings',
23 | 'exportWithStatus'
24 | ], 'profile')
25 |
26 | let exportProgress = genVuexModels([
27 | 'exportJobTotal',
28 | 'exportJobCurrent',
29 | 'exportJobWorking'
30 | ], 'exportJob')
31 |
32 | export default {
33 | namespaced: true,
34 | state,
35 | mutations: {
36 | ...mutations,
37 | ...fields.mutations,
38 | ...exportProgress.mutations
39 | },
40 | actions: {
41 | ...actions,
42 | ...fields.actions,
43 | ...exportProgress.actions
44 | },
45 | getters: {
46 | ...getters,
47 | ...fields.getters,
48 | ...exportProgress.getters
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/store/exporter/mutation-types.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Freeocart/fo-csv/6dcd8eaa1b125c873f9637ba93fbfd634622e02e/frontend/foc_csv/src/store/exporter/mutation-types.js
--------------------------------------------------------------------------------
/frontend/foc_csv/src/store/exporter/mutations.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import commonMutations from '@/store/common/mutations'
3 |
4 | export default {
5 | SET_ATTRIBUTE_ENCODER (state, encoder) {
6 | Vue.set(state.profile, 'attributeEncoder', encoder)
7 |
8 | if (!encoder) {
9 | return
10 | }
11 |
12 | if (!state.profile.attributeEncoderData || !state.profile.attributeEncoderData[encoder]) {
13 | Vue.set(state.profile, 'attributeEncoderData', {
14 | [encoder]: {}
15 | })
16 | }
17 |
18 | let encoderObj = state.data.attributeEncoders[encoder]
19 |
20 | if (encoderObj.options) {
21 | for (let key in encoderObj.options) {
22 | if (!state.profile.attributeEncoderData[encoder][key] && encoderObj.options[key].default) {
23 | Vue.set(state.profile.attributeEncoderData[encoder], key, encoderObj.options[key].default)
24 | }
25 | }
26 | }
27 | },
28 | SET_ATTRIBUTE_ENCODER_DATA (state, [ key, value ]) {
29 | let encoder = state.profile.attributeEncoder
30 | Vue.set(state.profile.attributeEncoderData[encoder], key, value)
31 | },
32 | ...commonMutations
33 | }
34 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/store/exporter/state.js:
--------------------------------------------------------------------------------
1 | import { DEFAULT_PROFILE_NAME } from '@/config'
2 |
3 | export default {
4 | data: {},
5 | profile: {
6 | exportWithStatus: -1
7 | },
8 | exportJob: {
9 | exportJobWorking: false,
10 | exportJobCurrent: 0,
11 | exportJobTotal: 0
12 | },
13 | currentProfile: DEFAULT_PROFILE_NAME
14 | }
15 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/store/importer/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | Actions for importer module
3 | */
4 |
5 | import Vue from 'vue'
6 | import commonActions from '@/store/common/actions'
7 |
8 | export default {
9 | ...commonActions,
10 | setCsvFieldNames ({ commit }, fieldNames) {
11 | commit('SET_CSV_FIELD_NAMES', fieldNames)
12 | },
13 | setBindings ({ commit }, bindings) {
14 | commit('SET_DB_TO_CSV_BINDINGS', bindings)
15 | },
16 | setCsvFileRef ({ commit }, ref) {
17 | commit('SET_CSV_FILE_REF', ref)
18 | },
19 | setImagesZipRef ({ commit }, ref) {
20 | commit('SET_IMAGES_ZIP_FILE_REF', ref)
21 | },
22 | async saveNewProfile ({ commit, state }, name) {
23 | try {
24 | await Vue.$api.importer.saveProfile({
25 | name,
26 | profile: state.profile
27 | })
28 |
29 | commit('SAVE_NEW_PROFILE', name)
30 | commit('SET_CURRENT_PROFILE_NAME', name)
31 | }
32 | catch (e) {
33 | console.log(e)
34 | alert('error on profile saving!')
35 | }
36 | },
37 | setStockStatusRewriteRule ({ commit }, rule) {
38 | commit('SET_STOCK_STATUS_REWRITE_RULE', rule)
39 | },
40 | setStatusRewriteRule ({ commit }, rule) {
41 | commit('SET_STATUS_REWRITE_RULE', rule)
42 | },
43 | setAttributeParser ({ commit }, parser) {
44 | commit('SET_ATTRIBUTE_PARSER', parser)
45 | },
46 | setAttributeParserData ({ commit }, data) {
47 | commit('SET_ATTRIBUTE_PARSER_DATA', data)
48 | },
49 | async saveAllProfiles ({ commit }, profiles) {
50 | await Vue.$api.importer.saveProfiles(profiles)
51 |
52 | commit('CLEAR_ALL_PROFILES')
53 | commit('SET_PROFILES', profiles)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/store/importer/getters.js:
--------------------------------------------------------------------------------
1 | /*
2 | Getters for exporter module
3 | */
4 |
5 | import commonGetters from '@/store/common/getters'
6 |
7 | export default {
8 | ...commonGetters,
9 | csvFields (state) {
10 | return state.data.csvFields
11 | },
12 | csvFileRef (state) {
13 | return state.data.csvFileRef
14 | },
15 | keyFields (state) {
16 | return state.data.keyFields
17 | },
18 | attributeParsers (state) {
19 | return state.data.attributeParsers
20 | },
21 | currentAttributeParser (state) {
22 | return state.profile.attributeParser
23 | },
24 | attributeParserOptions (state) {
25 | let parser = state.profile.attributeParser
26 |
27 | if (!parser || !state.data.attributeParsers[parser]) {
28 | return []
29 | }
30 |
31 | if (state.data.attributeParsers[parser].options) {
32 | return state.data.attributeParsers[parser].options
33 | }
34 |
35 | return []
36 | },
37 | attributeParserOptionData (state) {
38 | let parser = state.profile.attributeParser
39 | return state.profile.attributeParserData[parser] || []
40 | },
41 | categoryDelimiter (state) {
42 | return state.profile.categoryDelimiter
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/store/importer/index.js:
--------------------------------------------------------------------------------
1 | import state from './state'
2 | import mutations from './mutations'
3 | import actions from './actions'
4 | import getters from './getters'
5 |
6 | import { genVuexModels } from 'vuex-models'
7 |
8 | /*
9 | Generate mutation/action/getters for profile fields
10 | */
11 | let fields = genVuexModels([
12 | 'keyField',
13 | 'fillParentCategories',
14 | 'skipLines',
15 | 'csvWithoutHeaders',
16 | 'csvHeadersLineNumber',
17 | 'store',
18 | 'language',
19 | 'importMode',
20 | 'encoding',
21 | 'defaultStatus',
22 | 'defaultStockStatus',
23 | 'categoryDelimiter',
24 | 'imagesImportMode',
25 | 'categoryLevelDelimiter',
26 | 'processAtStepNum',
27 | 'downloadImages',
28 | 'clearGalleryBeforeImport',
29 | 'previewFromGallery',
30 | 'csvImageFieldDelimiter',
31 | 'csvFieldDelimiter',
32 | 'removeCharsFromCategory',
33 | 'removeManufacturersBeforeImport',
34 | 'defaultAttributesGroup',
35 | 'attributesCSVField',
36 | 'skipLineOnEmptyFields',
37 | 'multicolumnFields',
38 | 'replaceAttributes'
39 | ], 'profile')
40 |
41 | let importProgress = genVuexModels([
42 | 'importJobTotal',
43 | 'importJobCurrent',
44 | 'importJobWorking'
45 | ], 'importJob')
46 |
47 | export default {
48 | namespaced: true,
49 | state,
50 | mutations: {
51 | ...fields.mutations,
52 | ...importProgress.mutations,
53 | ...mutations
54 | },
55 | actions: {
56 | ...fields.actions,
57 | ...importProgress.actions,
58 | ...actions
59 | },
60 | getters: {
61 | ...fields.getters,
62 | ...importProgress.getters,
63 | ...getters
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/store/importer/mutation-types.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Freeocart/fo-csv/6dcd8eaa1b125c873f9637ba93fbfd634622e02e/frontend/foc_csv/src/store/importer/mutation-types.js
--------------------------------------------------------------------------------
/frontend/foc_csv/src/store/importer/mutations.js:
--------------------------------------------------------------------------------
1 | /*
2 | Mutations for exporter module
3 | */
4 |
5 | import Vue from 'vue'
6 | import commonMutations from '@/store/common/mutations'
7 |
8 | export default {
9 | ...commonMutations,
10 | SET_STOCK_STATUS_REWRITE_RULE (state, { value, id }) {
11 | if (!state.profile.stockStatusRewrites) {
12 | Vue.set(state.profile, 'stockStatusRewrites', {})
13 | }
14 | Vue.set(state.profile.stockStatusRewrites, id, value)
15 | },
16 | SET_STATUS_REWRITE_RULE (state, { value, id }) {
17 | if (!state.profile.statusRewrites) {
18 | Vue.set(state.profile, 'statusRewrites', {})
19 | }
20 | Vue.set(state.profile.statusRewrites, id, value)
21 | },
22 | SET_CSV_FIELD_NAMES (state, fields) {
23 | Vue.set(state.data, 'csvFields', fields)
24 | },
25 | SET_CATEGORY_DELIMITER (state, delimiter) {
26 | Vue.set(state.profile, 'categoryDelimiter', delimiter)
27 | },
28 | SET_DB_TO_CSV_BINDINGS (state, bindings) {
29 | Vue.set(state.profile, 'bindings', bindings)
30 | },
31 | SET_CSV_FILE_REF (state, ref) {
32 | Vue.set(state.data, 'csvFileRef', ref)
33 | },
34 | SET_IMAGES_ZIP_FILE_REF (state, ref) {
35 | Vue.set(state.data, 'imagesZipFileRef', ref)
36 | },
37 | SET_ATTRIBUTE_PARSER (state, parser) {
38 | Vue.set(state.profile, 'attributeParser', parser)
39 |
40 | if (!parser) {
41 | return
42 | }
43 |
44 | if (!state.profile.attributeParserData || !state.profile.attributeParserData[parser]) {
45 | Vue.set(state.profile, 'attributeParserData', {
46 | [parser]: {}
47 | })
48 | }
49 |
50 | let parserObj = state.data.attributeParsers[parser]
51 |
52 | if (parserObj.options) {
53 | for (let key in parserObj.options) {
54 | if (!state.profile.attributeParserData[parser][key] && parserObj.options[key].default) {
55 | Vue.set(state.profile.attributeParserData[parser], key, parserObj.options[key].default)
56 | }
57 | }
58 | }
59 | },
60 | SET_ATTRIBUTE_PARSER_DATA (state, [ key, value ]) {
61 | let parser = state.profile.attributeParser
62 | Vue.set(state.profile.attributeParserData[parser], key, value)
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/store/importer/state.js:
--------------------------------------------------------------------------------
1 | import { DEFAULT_PROFILE_NAME } from '@/config'
2 |
3 | export default {
4 | importJob: {
5 | importJobWorking: false,
6 | importJobCurrent: 0,
7 | importJobTotal: 0
8 | },
9 | urls: {
10 | import: ''
11 | },
12 | currentProfile: DEFAULT_PROFILE_NAME,
13 | data: {},
14 | profile: {
15 | statusRewrites: {},
16 | stockStatusRewrites: {},
17 | multicolumnFields: []
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import importer from './importer'
4 | import exporter from './exporter'
5 |
6 | Vue.use(Vuex)
7 |
8 | const store = new Vuex.Store({
9 | modules: {
10 | importer,
11 | exporter
12 | }
13 | })
14 |
15 | export default store
16 |
--------------------------------------------------------------------------------
/frontend/foc_csv/src/test.json:
--------------------------------------------------------------------------------
1 | {
2 | "requestConfig": {
3 | "token": "jjj",
4 | "baseRoute": "ii",
5 | "baseUrl": "kk"
6 | },
7 | "initial": {
8 | "data": {
9 | "keyFields": [
10 | "product_id",
11 | "sku",
12 | "model"
13 | ],
14 | "profiles": {
15 | "default": {
16 | "encoding": "UTF8",
17 | "csvFieldDelimiter": ";",
18 | "categoryDelimiter": "/",
19 | "keyField": "product_id",
20 | "skipFirstLine": 1,
21 | "csvToDbBindings": {
22 | "Idx": "product.product_id"
23 | },
24 | "bindings": {
25 | "5": "nothing",
26 | "6": "name",
27 | "10": "sku"
28 | },
29 | "importMode": "updateCreate",
30 | "csvImageFieldDelimiter": ";"
31 | }
32 | },
33 | "encodings": ["UTF8", "CP1251"],
34 | "csvFields": [],
35 | "dbFields": {
36 | "other": ["nothing"],
37 | "product": ["product_id", "name"],
38 | "product_description": ["product_description"]
39 | },
40 | "fileRef": {}
41 | },
42 | "csvFields": ["_CATEGORY_", "_NAME_", "_MODEL_", "_SKU_", "_MANUFACTURER_", "_PRICE_", "_QUANTITY_", "_STOCK_STATUS_", "_META_TITLE_", "_META_KEYWORDS_", "_META_DESCRIPTION_", "_DESCRIPTION_", "_PRODUCT_TAG_", "_IMAGE_", "_SORT_ORDER_", "_STATUS_", "_SEO_KEYWORD_", "_ATTRIBUTES_", "_IMAGES_", "_URL_"],
43 | "csvFileRef": {}
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/frontend/foc_csv/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Freeocart/fo-csv/6dcd8eaa1b125c873f9637ba93fbfd634622e02e/frontend/foc_csv/static/.gitkeep
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const { task, series, src, dest, watch } = require('gulp')
2 | const zip = require('gulp-zip')
3 | const { name } = require('./package.json')
4 |
5 | const BUILD_ZIP_NAME = `${name}.ocmod.zip`
6 | const SITE_PATH = process.env.SITE_DIR || './compiled-files'
7 |
8 | // globs:
9 | const GLOB_INSTALL_XML = './install.xml'
10 | const GLOB_SOURCE_BASE = './upload/**'
11 | // we dont need it at now as distribution is universal
12 | // const SOURCE_TEMPLATES_EXCLUDE_GLOB = process.env.TARGET_PLATFORM === '2' ? `!${GLOB_SOURCE_BASE}/*.twig` : `!${GLOB_SOURCE_BASE}/*.tpl`
13 | const GLOB_EXCLUDES = [
14 | '!**/.DS_Store',
15 | '!**/.git',
16 | ]
17 |
18 | const copyFiles = () =>
19 | src([ GLOB_SOURCE_BASE, ...GLOB_EXCLUDES ])
20 | .pipe(dest(SITE_PATH))
21 |
22 | const initWatcher = () =>
23 | watch([ GLOB_SOURCE_BASE, ...GLOB_EXCLUDES ], copyFiles)
24 |
25 | const packFiles = () =>
26 | src([ GLOB_SOURCE_BASE, GLOB_INSTALL_XML, ...GLOB_EXCLUDES ], { base: '.' })
27 | .pipe(zip(BUILD_ZIP_NAME))
28 | .pipe(dest('./'))
29 |
30 | /*
31 | Tasks:
32 | */
33 | task('pack:files', packFiles)
34 |
35 | task('copy:files', copyFiles)
36 |
37 | task('watch:files', series(copyFiles, initWatcher))
38 |
39 | task('build', packFiles)
--------------------------------------------------------------------------------
/install.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | fo_csv_menu_link
4 | fo_csv_menu_link_id
5 | 2.1.0.0
6 | ikenfin
7 | http://freeocart.ru
8 |
9 |
10 | user->hasPermission('access', 'tool/backup')) {]]>
11 | user->hasPermission('access', 'extension/module/foc_csv')) {
13 | $item = array(
14 | 'name' => $this->language->get('text_foc_csv'),
15 | 'children' => array()
16 | );
17 |
18 | // oc 2
19 | if (isset($tool) && is_array($tool)) {
20 | $item['href'] = $this->url->link('extension/module/foc_csv', 'token=' . $this->session->data['token'], true);
21 | $tool[] = $item;
22 | }
23 | // oc 3
24 | elseif (isset($maintenance) && is_array($maintenance)) {
25 | $item['href'] = $this->url->link('extension/module/foc_csv', 'user_token=' . $this->session->data['user_token'], true);
26 | $maintenance[] = $item;
27 | }
28 | }
29 | ]]>
30 |
31 |
32 |
33 |
34 |
35 |
36 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "foc_csv",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "dev": "gulp watch:files",
8 | "dev:build": "npm run build:frontend && gulp copy:files",
9 | "pack": "gulp pack:files",
10 | "prebuild": "yarn run --cwd=./frontend/foc_csv build --prod",
11 | "build": "gulp build",
12 | "build:frontend": "yarn run --cwd=./frontend/foc_csv build"
13 | },
14 | "dependencies": {
15 | "gulp": "^4.0.2",
16 | "gulp-zip": "^5.0.2"
17 | },
18 | "devDependencies": {
19 | "yarn": "^1.22.10"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/upload/admin/controller/extension/module/foc_csv.php:
--------------------------------------------------------------------------------
1 | load->model('extension/module/foc_csv_common');
11 | $this->load->model('extension/module/foc_csv');
12 | $this->load->model('extension/module/foc_csv_exporter');
13 |
14 | // Remove unnecessary template version
15 | $templatePath = DIR_APPLICATION . 'view/template/extension/module/';
16 | $viewFile = 'foc_csv.twig';
17 |
18 | if ($this->model_extension_module_foc_csv_common->isOpencart3()) {
19 | $viewFile = 'foc_csv.php';
20 | }
21 |
22 | if (is_file($templatePath . $viewFile)) {
23 | unlink($templatePath . $viewFile);
24 | }
25 |
26 | $this->model_extension_module_foc_csv->install();
27 | $this->model_extension_module_foc_csv_exporter->install();
28 | }
29 |
30 | private function sendOk ($message = '') {
31 | echo json_encode(array(
32 | 'status' => 'ok',
33 | 'message' => $message
34 | ), JSON_HEX_AMP);
35 | die;
36 | }
37 |
38 | private function sendFail ($message = '') {
39 | echo json_encode(array(
40 | 'status' => 'fail',
41 | 'message' => $message
42 | ));
43 | die;
44 | }
45 |
46 | private function getTokenName () {
47 | $this->load->model('extension/module/foc_csv_common');
48 | if ($this->model_extension_module_foc_csv_common->isOpencart3()) {
49 | return 'user_token';
50 | }
51 | return 'token';
52 | }
53 |
54 | private function getToken () {
55 | return $this->session->data[$this->getTokenName()];
56 | }
57 |
58 | private function createUrl ($path, $params = '') {
59 | return $this->url->link($path, $this->getTokenName() . '=' . $this->getToken() . '&' . $params, 'ssl');
60 | }
61 |
62 | public function index () {
63 | $data = array();
64 |
65 | $this->language->load('extension/module/foc_csv');
66 | $this->document->setTitle($this->language->get('heading_title'));
67 | $data['heading_title'] = $this->language->get('heading_title');
68 |
69 | $data['foc_app_preload_title'] = $this->language->get('foc_app_preload_title');
70 | $data['foc_app_preload_description'] = $this->language->get('foc_app_preload_description');
71 |
72 | $data['tokenName'] = $this->getTokenName();
73 | $data['token'] = $this->getToken();
74 | $data['baseRoute'] = 'extension/module/foc_csv';
75 | $data['baseUrl'] = $this->url->link('', '', 'ssl');
76 |
77 | $this->load->model('extension/module/foc_csv_common');
78 | $this->load->model('extension/module/foc_csv');
79 | $this->load->model('extension/module/foc_csv_exporter');
80 | $this->load->model('setting/store');
81 | $this->load->model('localisation/language');
82 | $this->load->model('localisation/stock_status');
83 |
84 | $data['language'] = $this->model_extension_module_foc_csv_common->getLanguageCode();
85 |
86 | $data['breadcrumbs'] = $this->breadcrumbs();
87 |
88 | $this->document->addStyle('view/javascript/foc_csv/css/app.css');
89 | $this->document->addScript('view/javascript/foc_csv/js/manifest.js', 'foc_csv_js');
90 | $this->document->addScript('view/javascript/foc_csv/js/vendor.js', 'foc_csv_js');
91 | $this->document->addScript('view/javascript/foc_csv/js/app.js', 'foc_csv_js');
92 |
93 | $data['header'] = $this->load->controller('common/header');
94 | $data['footer'] = $this->load->controller('common/footer');
95 | $data['column_left'] = $this->load->controller('common/column_left');
96 |
97 | $data['scripts'] = $this->document->getScripts('foc_csv_js');
98 |
99 | $common = array();
100 | $importer = array();
101 | $exporter = array();
102 |
103 | $data['foc_version'] = $this->model_extension_module_foc_csv->getVersion();
104 |
105 | $importer['profiles'] = $this->model_extension_module_foc_csv->loadProfiles();
106 | $importer['keyFields'] = $this->model_extension_module_foc_csv->getKeyFields();
107 | $common['encodings'] = array('none', 'UTF8', 'cp1251');
108 | $common['dbFields'] = $this->model_extension_module_foc_csv->getDbFields();
109 |
110 | $exporter['profiles'] = $this->model_extension_module_foc_csv_exporter->loadProfiles();
111 |
112 | /* stores */
113 | $common['stores'] = array();
114 | $common['stores'][] = array(
115 | 'name' => $this->config->get('config_name'),
116 | 'id' => $this->config->get('config_store_id')
117 | );
118 |
119 | $stores = $this->model_setting_store->getStores();
120 | foreach ($stores as $store) {
121 | $common['stores'][] = array(
122 | 'name' => $store['name'],
123 | 'id' => $store['store_id']
124 | );
125 | }
126 |
127 | /* available statuses */
128 | $common['stock_statuses'] = array();
129 | $statuses = $this->model_localisation_stock_status->getStockStatuses();
130 |
131 | foreach ($statuses as $status) {
132 | $common['stock_statuses'][] = array(
133 | 'id' => $status['stock_status_id'],
134 | 'name' => $status['name']
135 | );
136 | }
137 |
138 | $common['statuses'] = array(
139 | array(
140 | 'id' => 0,
141 | 'name' => $this->language->get('text_disabled')
142 | ),
143 | array(
144 | 'id' => 1,
145 | 'name' => $this->language->get('text_enabled')
146 | )
147 | );
148 |
149 | $common['languages'] = array();
150 | $languages = $this->model_localisation_language->getLanguages();
151 | foreach ($languages as $lang) {
152 | $common['languages'][] = array(
153 | 'name' => $lang['name'],
154 | 'id' => $lang['language_id']
155 | );
156 | }
157 |
158 | $importer['attributeParsers'] = $this->model_extension_module_foc_csv->getAttributeParsers();
159 |
160 | $exporter['attributeEncoders'] = $this->model_extension_module_foc_csv_exporter->getAttributeEncoders();
161 |
162 | $data['initial'] = json_encode(array(
163 | 'importer' => $importer,
164 | 'exporter' => $exporter,
165 | 'common' => $common
166 | ));
167 |
168 | return $this->response->setOutput($this->load->view('extension/module/foc_csv', $data));
169 | }
170 |
171 | /*
172 | Load profile settings from DB
173 | */
174 | public function loadProfile () {
175 | $type = null;
176 |
177 | if (isset($this->request->get['type'])) {
178 | $type = $this->request->get['type'];
179 | }
180 | else {
181 | $this->sendFail('Profile type not specified!');
182 | }
183 |
184 | $this->load->model('extension/module/foc_csv_common');
185 |
186 | $name = 'default';
187 |
188 | if (isset($this->request->get['profile'])) {
189 | $name = $this->request->get['profile'];
190 | }
191 |
192 | $profile = null;
193 |
194 | if ($type === 'importer') {
195 | $this->load->model('extension/module/foc_csv');
196 | $profile = $this->extension_module_foc_csv->loadProfile($name);
197 | }
198 | else {
199 | $this->load->model('extension/module/foc_csv_exporter');
200 | $profile = $this->extension_module_foc_csv_exporter->loadProfile($name);
201 | }
202 |
203 | if (is_null($profile)) {
204 | $this->sendFail('Profile [' . $name . '] not found!');
205 | }
206 |
207 | echo json_encode($profile);
208 | die;
209 | }
210 |
211 | /*
212 | Save profile to DB
213 | */
214 | public function saveProfile () {
215 | $type = null;
216 | if (isset($this->request->get['type'])) {
217 | $type = $this->request->get['type'];
218 | }
219 | else {
220 | $this->sendFail('Profile type not specified!');
221 | }
222 |
223 | if ($this->request->server['REQUEST_METHOD'] == 'POST') {
224 | $json = json_decode(file_get_contents('php://input'), true);
225 |
226 | if (isset($json['name']) && isset($json['profile'])) {
227 | $this->load->model('extension/module/foc_csv_common');
228 |
229 | $name = $json['name'];
230 | $profile = $json['profile'];
231 |
232 | if ($type == 'importer') {
233 | $this->load->model('extension/module/foc_csv');
234 | $this->model_extension_module_foc_csv->setProfile($name, $profile);
235 | }
236 | else {
237 | $this->load->model('extension/module/foc_csv_exporter');
238 | $this->model_extension_module_foc_csv_exporter->setProfile($name, $profile);
239 | }
240 |
241 | $this->sendOk();
242 | }
243 |
244 | $this->sendFail('No required data present!');
245 | }
246 | }
247 |
248 | public function saveProfiles () {
249 | $type = null;
250 | if (isset($this->request->get['type'])) {
251 | $type = $this->request->get['type'];
252 | }
253 | else {
254 | $this->sendFail('Profile type not specified!');
255 | }
256 |
257 | if ($this->request->server['REQUEST_METHOD'] == 'POST') {
258 | $json = json_decode(file_get_contents('php://input'), true);
259 |
260 | if (isset($json['profiles'])) {
261 | $this->load->model('extension/module/foc_csv_common');
262 | }
263 | else {
264 | $this->sendFail('Profiles to save is not presented!');
265 | }
266 |
267 | $profiles = array();
268 |
269 | if ($type == 'importer') {
270 | $this->load->model('extension/module/foc_csv');
271 | $this->model_extension_module_foc_csv->saveProfiles($json['profiles']);
272 | $profiles = $this->model_extension_module_foc_csv->loadProfiles();
273 | }
274 | else {
275 | $this->load->model('extension/module/foc_csv_exporter');
276 | $this->model_extension_module_foc_csv_exporter->saveProfiles($json['profiles']);
277 | $profiles = $this->model_extension_module_foc_csv_exporter->loadProfiles();
278 | }
279 |
280 | $this->sendOk(json_encode($profiles));
281 | }
282 |
283 | $this->sendFail();
284 | }
285 |
286 | public function export () {
287 | if (isset($_POST['profile-json'])) {
288 | $this->load->model('extension/module/foc_csv_common');
289 | $this->load->model('extension/module/foc_csv_exporter');
290 |
291 | $profile = $this->model_extension_module_foc_csv_exporter->fillProfileEmptyValues(json_decode($_POST['profile-json'], true));
292 |
293 | $this->model_extension_module_foc_csv_exporter->applyProfile($profile);
294 |
295 | $key = $this->model_extension_module_foc_csv_exporter->prepareUploadPath();
296 | $exportFile = $this->model_extension_module_foc_csv_exporter->getExportCsvFilePath($key);
297 | $exportImagesFile = null;
298 |
299 | $encoderEnabled = isset($profile['attributeEncoder']) && !is_null($profile['attributeEncoder']);
300 |
301 | if ($profile['createImagesZIP']) {
302 | $exportImagesFile = $this->model_extension_module_foc_csv_exporter->getExportImagesZipFilePath($key);
303 | }
304 |
305 | // put csv headers if need
306 | if ($profile['csvHeader']) {
307 | $csv_fid = fopen($exportFile, 'w');
308 | $headers = array();
309 |
310 | foreach ($profile['bindings'] as $binding) {
311 | $headers[] = $binding['header'];
312 | }
313 |
314 | // reserving columns for attributes
315 | // there is some tricky solution, based on keys order, but it seems to be ok
316 | // also we do it only if encoder is multicolumn!
317 | // Otherwise - just push attributes after last column
318 | if ($encoderEnabled) {
319 | // csvIdx => group_id:attribute_id
320 | $attributeHeaders = $this->model_extension_module_foc_csv_exporter->encodeAttributeHeaders($profile, count($headers));
321 |
322 | // if encoder is multicolumn we get array
323 | // also using += to prevent keys reindexing
324 | if (is_array($attributeHeaders)) {
325 | $headers += $attributeHeaders;
326 | }
327 | else {
328 | $headers[] = $attributeHeaders;
329 | }
330 | }
331 |
332 | fputcsv($csv_fid, $headers, $profile['csvFieldDelimiter']);
333 | fclose($csv_fid);
334 | }
335 |
336 | $total = $this->model_extension_module_foc_csv_exporter->getProductTotal($profile);
337 |
338 | $export_url = $this->createUrl('extension/module/foc_csv/exportPart');
339 |
340 | $response = array(
341 | 'total' => $total,
342 | 'key' => $key,
343 | 'exportUrl' => html_entity_decode($export_url),
344 | 'csvFileUrl' => html_entity_decode($this->createUrl('extension/module/foc_csv/download', 'key=' . $key . '&mode=' . self::KEY_FOR_CSV_FILE)),
345 | 'position' => 0
346 | );
347 |
348 | if (!is_null($exportImagesFile)) {
349 | $response['imagesZipUrl'] = html_entity_decode($this->createUrl('extension/module/foc_csv/download', 'key=' . $key . '&mode=' . self::KEY_FOR_IMAGES_ZIP_FILE));
350 | }
351 |
352 | $this->sendOk($response);
353 | }
354 | }
355 |
356 | public function exportPart () {
357 | if ($this->request->server['REQUEST_METHOD'] == 'POST') {
358 | $json = json_decode(file_get_contents('php://input'), true);
359 |
360 | $this->load->model('extension/module/foc_csv_common');
361 | $this->load->model('extension/module/foc_csv_exporter');
362 |
363 | if (isset($json['position'])
364 | && isset($json['key'])
365 | && isset($json['profile'])
366 | ) {
367 | $offset = $json['position'];
368 | $profile = $json['profile'];
369 | $key = $json['key'];
370 | $errors = isset($json['errors']) ? intval($json['errors']) : 0;
371 | $limit = $profile['entriesPerQuery'];
372 |
373 | // TODO: create initilize method and pass profile/key/other state data to it
374 | // also, refactor all file api to use state uploadKey, instead of passing as argument
375 | $this->model_extension_module_foc_csv_exporter->applyProfile($profile);
376 | $this->model_extension_module_foc_csv_exporter->setUploadKey($key);
377 |
378 | $exportFile = $this->model_extension_module_foc_csv_exporter->getExportCsvFilePath($key);
379 |
380 | $exportable = $this->model_extension_module_foc_csv_exporter->export($profile, $offset, $limit);
381 |
382 | $csv_fid = fopen($exportFile, 'a');
383 |
384 | $db_charset = strtolower($this->model_extension_module_foc_csv_exporter->getDBCharset());
385 | $charset = strtolower($this->model_extension_module_foc_csv_exporter->dbToIconvCharset($db_charset));
386 | $encoding = strtolower($profile['encoding']);
387 |
388 | foreach ($exportable as $csvLine) {
389 | // try change file encoding
390 | if ($encoding !== 'none' && $charset !== $encoding) {
391 | $this->model_extension_module_foc_csv_exporter->writeLog('Trying to convert character encoding from [' . $charset . '] to [' . $encoding . ']');
392 |
393 | if (!function_exists('iconv')) {
394 | $this->model_extension_module_foc_csv_exporter->writeLog('Please install iconv or convert csv file to [' . $encoding . ']', 'error');
395 | $this->sendFail('Please install iconv or convert csv file to [' . $encoding . ']');
396 | }
397 |
398 | foreach ($csvLine as $part_idx => $part) {
399 | $encoded = iconv($charset, $encoding . '//TRANSLIT//IGNORE', $part);
400 |
401 | if ($encoded) {
402 | $csvLine[$part_idx] = $encoded;
403 | }
404 | else {
405 | $this->model_extension_module_foc_csv_exporter->writeLog('Failed iconv from [' . $charset . '] to [' . $encoding . ']', 'error');
406 | }
407 | }
408 | }
409 |
410 | fputcsv($csv_fid, $csvLine, $profile['csvFieldDelimiter']);
411 | }
412 | fclose($csv_fid);
413 |
414 | if ($profile['createImagesZIP']
415 | && $this->model_extension_module_foc_csv_exporter->hasCollectedImages()
416 | ) {
417 | $exportImagesFile = $this->model_extension_module_foc_csv_exporter->getExportImagesZipFilePath($key);
418 | $zip = new ZipArchive();
419 | $zip->open($exportImagesFile, ZipArchive::CREATE);
420 |
421 | foreach($this->model_extension_module_foc_csv_exporter->getCollectedImages() as $image) {
422 | $path = DIR_IMAGE . $image;
423 | $zip->addFile($path, $image);
424 | }
425 |
426 | $zip->close();
427 | }
428 |
429 | $position = $offset + $limit;
430 |
431 | $response = array(
432 | 'key' => $key,
433 | 'position' => $position,
434 | 'errors' => $errors,
435 | 'collected_images' => $this->model_extension_module_foc_csv_exporter->getCollectedImagesCount()
436 | );
437 |
438 | $this->sendOk($response);
439 | }
440 | }
441 | }
442 |
443 | /*
444 | Upload files and starting import
445 | */
446 | public function import () {
447 | if (!empty($_FILES) && isset($_POST['profile-json'])) {
448 | $this->load->model('extension/module/foc_csv_common');
449 | $this->load->model('extension/module/foc_csv');
450 |
451 | // first call - generate upload key
452 | $key = $this->model_extension_module_foc_csv->prepareUploadPath();
453 | $importFile = $this->model_extension_module_foc_csv->getImportCsvFilePath($key);
454 | $imagesFile = $this->model_extension_module_foc_csv->getImportImagesZipPath($key);
455 |
456 | // $profile = json_decode($_POST['profile-json'], true);
457 | $profile = $this->model_extension_module_foc_csv->fillProfileEmptyValues(json_decode($_POST['profile-json'], true));
458 | $this->model_extension_module_foc_csv->applyProfile($profile);
459 |
460 | // csv file operations
461 | if (isset($_FILES['csv-file'])) {
462 |
463 | $this->model_extension_module_foc_csv->writeLog('CSV file uploaded');
464 |
465 | $db_charset = strtolower($this->model_extension_module_foc_csv->getDBCharset());
466 | $charset = $this->model_extension_module_foc_csv->dbToIconvCharset($db_charset);
467 | $encoding = strtolower($profile['encoding']);
468 |
469 | // try change file encoding
470 | if ($encoding !== 'none' && $charset !== $encoding) {
471 | $this->model_extension_module_foc_csv->writeLog('Trying to convert character encoding from [' . $encoding . '] to [' . $charset . ']');
472 |
473 | if (!function_exists('iconv')) {
474 | $this->model_extension_module_foc_csv->writeLog('Please install iconv or convert csv file to [' . $charset . ']', 'error');
475 | $this->sendFail('Please install iconv or convert csv file to [' . $charset . ']');
476 | }
477 |
478 | $src = fopen($_FILES['csv-file']['tmp_name'], 'r');
479 | $out = fopen($importFile, 'w');
480 |
481 | while ($line = fgets($src)) {
482 | fwrite($out, iconv($encoding, $charset . '//TRANSLIT//IGNORE', $line));
483 | }
484 |
485 | fclose($src);
486 | fclose($out);
487 |
488 | $this->model_extension_module_foc_csv->writeLog('File [' . $src . '] encoded successfully!');
489 | }
490 | else {
491 | move_uploaded_file($_FILES['csv-file']['tmp_name'], $importFile);
492 | }
493 | }
494 | // images zip - save and unpack
495 | if (isset($_FILES['images-zip'])) {
496 | $this->model_extension_module_foc_csv->writeLog('Images ZIP file uploaded');
497 |
498 | move_uploaded_file($_FILES['images-zip']['tmp_name'], $imagesFile);
499 |
500 | $zip = new ZipArchive();
501 | $can_open = $zip->open($imagesFile);
502 |
503 | if ($can_open === true) {
504 | $zip->extractTo($this->model_extension_module_foc_csv->getImportImagesPath($key));
505 | $this->model_extension_module_foc_csv->moveUploadedImages($key);
506 | $zip->close();
507 | }
508 | }
509 |
510 | // read csv bytes count
511 | $csv_file = new SplFileObject($importFile, 'r');
512 | $csv_file->seek(PHP_INT_MAX);
513 | $csv_total = $csv_file->ftell();
514 |
515 | $import_url = $this->createUrl('extension/module/foc_csv/importPart');
516 |
517 | // remove manufacturers if necessary
518 | if (isset($profile['removeManufacturersBeforeImport'])
519 | && $profile['removeManufacturersBeforeImport']
520 | ) {
521 | $this->model_extension_module_foc_csv->writeLog('Clearing manufacturers table');
522 | $this->model_extension_module_foc_csv->clearManufacturers();
523 | }
524 |
525 | // removeOthers importMode handler:
526 | // before importData we remove all products from database
527 | // on importPart this mode === updateCreate mode
528 | if ($profile['importMode'] == 'removeOthers') {
529 | $this->model_extension_module_foc_csv->clearProducts();
530 | }
531 |
532 | $this->sendOk(array(
533 | 'csvTotal' => $csv_total,
534 | 'key' => $key,
535 | 'importUrl' => html_entity_decode($import_url),
536 | // position - last ftell file position
537 | 'position' => 0
538 | ));
539 | }
540 | }
541 |
542 | /*
543 | Import partition from CSV
544 | */
545 | public function importPart ($key = null, $position = null, $profile = null) {
546 | if ($key === null && $position === null && $profile === null) {
547 | if ($this->request->server['REQUEST_METHOD'] == 'POST') {
548 | $json = json_decode(file_get_contents('php://input'), true);
549 |
550 | $this->load->model('extension/module/foc_csv_common');
551 | $this->load->model('extension/module/foc_csv');
552 |
553 | if (isset($json['position'])
554 | && isset($json['key'])
555 | && isset($json['profile'])
556 | ) {
557 | $import_key = $json['key'];
558 | $position = $json['position'];
559 | $errors = isset($json['errors']) ? intval($json['errors']) : 0;
560 | $lines = isset($json['lines']) ? $json['lines'] : 0;
561 | $profile = $json['profile'];
562 |
563 | $this->model_extension_module_foc_csv->writeLog('Import part start [' . $lines . ']');
564 |
565 | $profile = $this->model_extension_module_foc_csv->fillProfileEmptyValues($profile);
566 | $this->model_extension_module_foc_csv->applyProfile($profile);
567 |
568 | $this->model_extension_module_foc_csv->setUploadKey($import_key);
569 |
570 | $skipLines = $profile['skipLines'];
571 |
572 | if (!$profile['csvWithoutHeaders'] && $profile['csvHeadersLineNumber'] > $skipLines) {
573 | $skipLines = $profile['csvHeadersLineNumber'];
574 | }
575 |
576 | $delimiter = empty($profile['csvFieldDelimiter']) ? ';' : $profile['csvFieldDelimiter'];
577 | $importAtOnce = empty($profile['processAtStepNum']) ? 10 : $profile['processAtStepNum'];
578 |
579 | $mode = $profile['importMode'];
580 | $this->model_extension_module_foc_csv->setImportMode($mode);
581 |
582 | $key_field = $profile['keyField'];
583 | list($table, $key) = explode(':', $key_field);
584 | $this->model_extension_module_foc_csv->toggleKeyField($table, $key);
585 |
586 | $path = $this->model_extension_module_foc_csv->getImportCsvFilePath($import_key);
587 | $csv_fid = fopen($path, 'r');
588 |
589 | if ($position > 0) {
590 | fseek($csv_fid, $position);
591 | }
592 |
593 | $i = 0;
594 |
595 | while ($i < $importAtOnce && ($line = fgetcsv($csv_fid, 0, $delimiter)) !== false) {
596 | $i++;
597 |
598 | if (($lines + $i) <= $skipLines) {
599 | $this->model_extension_module_foc_csv->writeLog('Skip line:' . ($lines + $i) . ', lines to skip:' . $skipLines);
600 | continue;
601 | }
602 | // import stuff..
603 | try {
604 | if (!$this->model_extension_module_foc_csv->import($profile, $line, $lines + $i)) {
605 | $errors++;
606 | }
607 | }
608 | catch (Exception $e) {
609 | $this->model_extension_module_foc_csv->writeLog('Error on import at [' . ($lines + $i) . '] (' . $e->getMessage() . ')', 'error');
610 | $this->sendFail($e->getMessage());
611 | }
612 | }
613 |
614 | $position = ftell($csv_fid);
615 | fclose($csv_fid);
616 |
617 | $this->model_extension_module_foc_csv->writeLog('Import part end at [' . ($lines + $i) . ']');
618 |
619 | $this->sendOk(array(
620 | 'key' => $import_key,
621 | 'position' => $position,
622 | 'errors' => $errors,
623 | 'lines' => $i + $lines
624 | ));
625 | }
626 | }
627 | }
628 |
629 | $this->model_extension_module_foc_csv->writeLog('Missing required fields! Fields are $key: [' . $key .'], $position: [' . $position . '] and $profile: [' . print_r($profile, true) . ']', 'error');
630 | $this->sendFail();
631 | }
632 |
633 | /*
634 | Download CSV or Images.zip
635 | */
636 | public function download () {
637 | $this->load->model('extension/module/foc_csv_common');
638 | $this->load->model('extension/module/foc_csv_exporter');
639 | $key = null;
640 | $mode = 0;
641 |
642 | if (isset($this->request->get['key'])) {
643 | $key = $this->request->get['key'];
644 | }
645 |
646 | if (isset($this->request->get['mode'])) {
647 | $mode = $this->request->get['mode'];
648 | }
649 |
650 | $file_path = null;
651 |
652 | switch ($mode) {
653 | case self::KEY_FOR_CSV_FILE:
654 | $file_path = $this->model_extension_module_foc_csv_exporter->getExportCsvFilePath($key);
655 | break;
656 | case self::KEY_FOR_IMAGES_ZIP_FILE:
657 | $file_path = $this->model_extension_module_foc_csv_exporter->getExportImagesZipFilePath($key);
658 | break;
659 | }
660 |
661 | /*
662 | TODO:
663 | make option for auto remove files after download
664 | */
665 | if ($file_path !== null && is_file($file_path)) {
666 | header('Content-Type: application/octet-stream');
667 | header('Content-Description: File Transfer');
668 | header('Content-Disposition: attachment; filename="' . basename($file_path) . '"');
669 | header('Content-Transfer-Encoding: binary');
670 | header('Expires: 0');
671 | header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
672 | header('Pragma: public');
673 | header('Content-Length: ' . filesize($file_path));
674 |
675 | readfile($file_path, 'rb');
676 | }
677 | else {
678 | exit('Error: Could not find file ' . $file_path . '!');
679 | }
680 | }
681 |
682 | /*
683 | Autocomplete to choose attributes default group
684 | */
685 | public function attributesGroupAutocomplete () {
686 | $this->load->model('catalog/attribute_group');
687 | $groups = $this->model_catalog_attribute_group->getAttributeGroups();
688 | $response = array();
689 | foreach ($groups as $group) {
690 | $response[] = array(
691 | 'name' => $group['name']
692 | );
693 | }
694 |
695 | echo json_encode($response);
696 | die;
697 | }
698 |
699 | private function breadcrumbs () {
700 | $breadcrumbs = array();
701 |
702 | $breadcrumbs[] = array(
703 | 'text' => $this->language->get('text_home'),
704 | 'href' => $this->createUrl('common/home'),
705 | 'separator' => false
706 | );
707 | $breadcrumbs[] = array(
708 | 'text' => $this->language->get('text_extension'),
709 | 'href' => $this->createUrl('extension/extension'),
710 | 'separator' => ' :: '
711 | );
712 | $breadcrumbs[] = array(
713 | 'text' => $this->language->get('heading_title'),
714 | 'href' => $this->createUrl('extension/module/foc_csv'),
715 | 'separator' => ' :: '
716 | );
717 |
718 | return $breadcrumbs;
719 | }
720 |
721 | }
--------------------------------------------------------------------------------
/upload/admin/language/en-gb/extension/module/foc_attribute_encoders.php:
--------------------------------------------------------------------------------
1 | site, e-mail';
7 | $_['foc_app_preload_nojs'] = 'Please enable JavaScript to use module!';
8 |
9 | $_['foc_default_attributes_group'] = 'Specification';
--------------------------------------------------------------------------------
/upload/admin/language/ru-ru/extension/module/foc_attribute_encoders.php:
--------------------------------------------------------------------------------
1 | сайт, e-mail';
7 | $_['foc_app_preload_nojs'] = 'Пожалуйста, включите JavaScript в вашем браузере!';
8 |
9 | $_['foc_default_attributes_group'] = 'Характеристики';
--------------------------------------------------------------------------------
/upload/admin/model/extension/module/foc_csv_common.php:
--------------------------------------------------------------------------------
1 | array(
15 | 'sort_order',
16 | 'language_id',
17 | 'date_added',
18 | 'date_modified'
19 | ),
20 | 'product' => array(
21 | 'location',
22 | 'manufacturer_id',
23 | 'shipping',
24 | 'points',
25 | 'tax_class_id',
26 | 'weight_class_id',
27 | 'length_class_id',
28 | 'subtract',
29 | 'minimum'
30 | ),
31 | 'product_special' => array(
32 | 'product_special_id',
33 | 'customer_group_id',
34 | 'product_id',
35 | 'priority'
36 | ),
37 | 'product_description' => array(
38 | 'product_id'
39 | ),
40 | 'product_image' => array(
41 | 'product_id',
42 | 'product_image_id'
43 | ),
44 | 'category_description' => array(
45 | 'category_id'
46 | ),
47 | 'category' => array(
48 | 'parent_id',
49 | 'top',
50 | 'column'
51 | )
52 | );
53 |
54 |
55 | // db encoding -> iconv encoding
56 | protected $charsetMap = array(
57 | 'armscii8' => 'ARMSCII-8',
58 | 'ascii' => 'ASCII',
59 | 'big5' => 'BIG-5',
60 | 'binary'=> null, // Not sure what is it
61 | 'cp1250'=> 'CP1250',
62 | 'cp1251'=> 'CP1251',
63 | 'cp1256'=> 'CP1256',
64 | 'cp1257'=> 'CP1257',
65 | 'cp850'=> 'CP850',
66 | 'cp852'=> 'CP852',
67 | 'cp866'=> 'CP866',
68 | 'cp932'=> 'CP932',
69 | 'dec8'=> 'ISO-8859-1',
70 | 'eucjpms'=> 'EUC-JISX0213',
71 | 'euckr'=> 'EUC-KR',
72 | 'gb18030'=> 'GB18030',
73 | 'gb2312'=> 'GB_2312-80',
74 | 'gbk'=> 'GBK',
75 | 'geostd8'=> 'GEORGIAN-PS',
76 | 'greek'=> 'GREEK',
77 | 'hebrew'=> 'HEBREW',
78 | 'hp8'=> 'R8',
79 | 'keybcs2'=> null, // Not sure here
80 | 'koi8r'=> 'KOI8-R',
81 | 'koi8u'=> 'KOI8-U',
82 | 'latin1'=> 'LATIN1',
83 | 'latin2'=> 'LATIN2',
84 | 'latin5'=> 'LATIN5',
85 | 'latin7'=> 'LATIN7',
86 | 'macce'=> 'MACCENTRALEUROPE',
87 | 'macroman'=> 'MACROMANIA',
88 | 'sjis'=> 'SJIS',
89 | 'swe7'=> null, // Not sure here, please make PR if there is error
90 | 'tis620'=> 'TIS-620',
91 | 'ucs2'=> 'UCS-2',
92 | 'ujis'=> 'EUC-JP',
93 | 'utf16'=> 'UTF-16',
94 | 'utf16le'=> 'UTF-16LE',
95 | 'utf32'=> 'UTF-32',
96 | 'utf8mb3'=> 'UTF-8',
97 | 'utf8mb4'=> 'UTF-8',
98 | );
99 |
100 | public function __construct ($registry) {
101 | parent::__construct($registry);
102 | $this->scanOpencartVersion();
103 | $this->log = new Log('foc_csv_' . $this->type . '.txt');
104 | $this->load->library('FocSimpleTemplater');
105 | }
106 |
107 | public function install () {
108 | $this->load->model('setting/setting');
109 | $this->model_setting_setting->editSetting($this->profiles_code, array($this->profiles_key => array()));
110 | $this->saveProfiles($this->getDefaultProfiles());
111 | }
112 |
113 | /*
114 |
115 | */
116 | public function writeLog ($msg, $group = 'info') {
117 | switch ($group) {
118 | case 'error': $msg = '[ERROR] ' . $msg;
119 | break;
120 | case 'warn' : $msg = '[WARN] ' . $msg;
121 | break;
122 | default : $msg = '[INFO] ' . $msg;
123 | break;
124 | }
125 |
126 | $this->log->write($msg);
127 | }
128 |
129 | /*
130 | Key fields
131 | */
132 | public function getKeyFields () {
133 | return array(
134 | 'product:product_id',
135 | 'product:sku',
136 | 'product:model',
137 | 'product_description:name',
138 | 'product:ean',
139 | 'product:mpn',
140 | 'product:jan',
141 | 'product:isbn'
142 | );
143 | }
144 |
145 | /*
146 | Remove unwanted fields from select
147 | */
148 | public function filterTableFields ($table, array $fields) {
149 | if (isset($this->unwantedTableFields[$table])) {
150 | $fields = array_diff($fields, $this->unwantedTableFields[$table]);
151 | }
152 |
153 | return array_diff($fields, $this->unwantedTableFields['common']);
154 | }
155 |
156 | /*
157 | Generate DB fields list
158 | */
159 | public function getDbFields () {
160 | $tables = array(
161 | 'product',
162 | 'product_description',
163 | 'product_image',
164 | 'product_special',
165 | 'manufacturer',
166 | 'category',
167 | 'category_description'
168 | );
169 |
170 | $result = array();
171 |
172 | foreach ($tables as $table) {
173 | $data = $this->db->query('SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = "' . DB_DATABASE . '" AND TABLE_NAME = "' . DB_PREFIX . $table . '"');
174 | $result[$table] = array_column($data->rows, 'COLUMN_NAME');
175 | $result[$table] = $this->filterTableFields($table, $result[$table]);
176 | }
177 |
178 | return $result;
179 | }
180 |
181 | public function fillProfileEmptyValues ($profile) {
182 | return array_replace_recursive($this->getDefaultProfile(), $profile);
183 | }
184 |
185 | public function getDefaultProfile () {
186 | return array();
187 | }
188 |
189 | /*
190 | Default profiles list
191 | */
192 | public function getDefaultProfiles () {
193 | return array(
194 | 'default' => $this->getDefaultProfile()
195 | );
196 | }
197 |
198 | /*
199 | Load all profiles
200 | */
201 | public function loadProfiles () {
202 | $this->load->model('setting/setting');
203 |
204 | $profiles = json_decode($this->model_setting_setting->getSettingValue($this->profiles_key), true);
205 |
206 | if (count($profiles) === 0) {
207 | $profiles = $this->getDefaultProfiles();
208 | }
209 | else {
210 | foreach ($profiles as $key => $profile) {
211 | $profiles[$key] = $this->fillProfileEmptyValues($profile);
212 | }
213 | }
214 |
215 | return $profiles;
216 | }
217 |
218 | /*
219 | Load profile by name
220 | */
221 | public function loadProfile ($name) {
222 | $profiles = $this->loadProfiles();
223 |
224 | if (isset($profiles[$name])) {
225 | return $profiles[$name];
226 | }
227 |
228 | return null;
229 | }
230 |
231 | /*
232 | Save profiles list
233 | */
234 | public function saveProfiles ($profiles) {
235 | $this->load->model('setting/setting');
236 |
237 | foreach ($profiles as $key => $profile) {
238 | $profiles[$key] = $this->fillProfileEmptyValues($profile);
239 | }
240 |
241 | $this->model_setting_setting->editSettingValue(
242 | $this->profiles_code,
243 | $this->profiles_key,
244 | $profiles
245 | );
246 | }
247 |
248 | /*
249 | Save profile by name
250 | */
251 | public function setProfile ($name, $data) {
252 | $profiles = $this->loadProfiles();
253 | $profiles[$name] = $data;
254 | $this->saveProfiles($profiles);
255 | }
256 |
257 | /*
258 | FILE MANIPULATION METHODS
259 | */
260 |
261 | /*
262 | Return path to file by key
263 | */
264 | public function getUploadPath ($key) {
265 | return DIR_CACHE . $this->profiles_code . DIRECTORY_SEPARATOR . $key . DIRECTORY_SEPARATOR . $this->type . DIRECTORY_SEPARATOR;
266 | }
267 |
268 | /*
269 | Prepare import storage
270 | Returns storage key
271 | */
272 | public function prepareUploadPath () {
273 | $key = md5(rand() . time());
274 | $path = $this->getUploadPath($key);
275 |
276 | if (is_dir($path)) {
277 | return $this->prepareUploadPath();
278 | }
279 |
280 | $this->writeLog('Trying to create import path [' . $path . ']');
281 | mkdir($path, 0755, true);
282 |
283 | $this->setUploadKey($key);
284 |
285 | return $key;
286 | }
287 |
288 | public function setUploadKey ($key) {
289 | $this->uploadKey = $key;
290 | }
291 |
292 | /* UTILS */
293 |
294 | /*
295 | Set state data from frontend profile
296 | */
297 | public function applyProfile ($profile) {
298 | $this->language_id = (int) $this->config->get('config_language_id');
299 | if (isset($profile['language'])) {
300 | $this->language_id = (int) $profile['language'];
301 | }
302 |
303 | $this->store_id = $this->config->get('config_store_id');
304 | if (isset($profile['store'])) {
305 | $this->store_id = $profile['store'];
306 | }
307 | }
308 | /*
309 | Convert FS path to URL
310 | */
311 | public function pathToUrl ($path) {
312 | return str_replace(DIR_CACHE, HTTPS_CATALOG . 'system/storage/cache/', $path);
313 | }
314 |
315 | public function basename ($path) {
316 | $parts = explode(DIRECTORY_SEPARATOR, $path);
317 | return end($parts);
318 | }
319 |
320 | /*
321 | Check if url is url:)
322 | */
323 | public function isUrl ($url) {
324 | return preg_match('/^https?\:\/\//', $url);
325 | }
326 |
327 | /*
328 | Get database charset
329 | */
330 | public function getDBCharset () {
331 | return $this->db->query('SELECT @@character_set_database AS `charset`')->row['charset'];
332 | }
333 |
334 | public function dbToIconvCharset ($charset) {
335 | if (isset($this->charsetMap[$charset])) {
336 | return $this->charsetMap[$charset];
337 | }
338 | return $charset;
339 | }
340 |
341 | public function getLanguageCode () {
342 | $lang = $this->language->get('code');
343 | return strtolower(substr($lang, 0, 2));
344 | }
345 |
346 | /*
347 | Scan version
348 | */
349 | public function scanOpencartVersion () {
350 | $digits = explode('.', VERSION);
351 |
352 | // ocstore uses 5-digits versions
353 | if (count($digits) > 4) {
354 | $this->is_ocstore = true;
355 | array_pop($digits);
356 | }
357 |
358 | $this->normalized_version = intval(implode('', $digits));
359 | }
360 |
361 | /*
362 | Version checkers
363 | */
364 | public function getOpencartMajorVersion () {
365 | return floor($this->normalized_version / 1000);
366 | }
367 | public function isOpencart15 () {
368 | return $this->normalized_version < 2000;
369 | }
370 |
371 | public function isOpencart2 () {
372 | return $this->normalized_version >= 2000 && $this->normalized_version < 2300;
373 | }
374 | public function isOpencart23 () {
375 | return $this->normalized_version >= 2300 && $this->normalized_version < 3000;
376 | }
377 |
378 | public function isOpencart3 () {
379 | return $this->normalized_version >= 3000;
380 | }
381 |
382 | /*
383 | Check is this a ocStore
384 | */
385 | public function isOcstore () {
386 | return $this->is_ocstore;
387 | }
388 |
389 | }
390 |
--------------------------------------------------------------------------------
/upload/admin/view/javascript/.gitignore:
--------------------------------------------------------------------------------
1 | foc_csv/css
2 | foc_csv/js
3 |
--------------------------------------------------------------------------------
/upload/admin/view/template/extension/module/foc_csv.tpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
19 |
20 |
21 |
22 |
23 |
24 |
33 |
34 |
35 |
36 | :
37 |
38 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/upload/admin/view/template/extension/module/foc_csv.twig:
--------------------------------------------------------------------------------
1 | {{ header }}
2 | {{ column_left }}
3 |
4 |
5 |
6 |
19 |
20 |
21 |
22 |
23 |
24 |
33 |
34 |
35 |
36 | {{ heading_title }}: {{ foc_version }}
37 |
38 |
39 |
40 | {{ foc_app_preload_description }}
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
66 | {% for script in scripts %}
67 |
68 | {% endfor %}
69 |
70 | {{ footer }}
--------------------------------------------------------------------------------
/upload/system/library/FocSimpleTemplater.php:
--------------------------------------------------------------------------------
1 | load->library('FocSimpleTemplater');
9 | // after that, you can call FinSimpleTemplater directly:
10 | FinSimpleTemplater::render($tpl, array())
11 |
12 |
13 | Templates language:
14 |
15 | Variable interpolation:
16 | {{ variable_interpolation }}
17 |
18 | Loops:
19 | [@each (value, index) <= source]
20 | loop content here
21 | {{ loop.index }} or {{ index }} <- current iteration index (starting from 1)
22 | {{ value.name }}
23 | [@endeach]
24 |
25 | [@each value <= source]
26 | {{ value }}
27 | [@endeach]
28 |
29 | Functions (see $enabled_functions to list available):
30 | [@fn FUNCTION_NAME | ARGUMENT]
31 |
32 | [@date | 'Y-m-d'] - date('Y-m-d')
33 | [@md5 | @variable] - md5($variables['variable'])
34 | */
35 | class FocSimpleTemplater {
36 |
37 | // available functions
38 | protected static $enabled_functions = array(
39 | 'nl2br',
40 | 'date',
41 | 'htmlspecialchars',
42 | 'htmlentities',
43 | 'html_entity_decode',
44 | 'md5',
45 | 'lcfirst',
46 | 'ucfirst',
47 | 'strtolower',
48 | 'strtoupper',
49 | 'ucwords',
50 | 'trim',
51 | 'time',
52 | 'microtime',
53 | 'money_format',
54 | 'number_format'
55 | );
56 |
57 | // multiline to single line
58 | protected static function normalize ($template) {
59 | return trim(preg_replace("/\r?\n/", "", preg_replace("/[^\S\t]+/", " ", $template)));
60 | }
61 |
62 | // make arguments array from string
63 | protected static function process_function_args ($args_raw, $vars = array()) {
64 | $args = array_map('trim', preg_split('/,/', $args_raw, -1, PREG_SPLIT_NO_EMPTY));
65 | $result = array();
66 |
67 | foreach ($args as $arg) {
68 | if (preg_match("/^\@(.*)/", $arg, $matches)) {
69 | $result[] = isset($vars[$matches[1]]) ? $vars[$matches[1]] : null;
70 | }
71 | else {
72 | $result[] = trim($arg, "\"\'");
73 | }
74 | }
75 |
76 | return $result;
77 | }
78 |
79 | // parse and execute function code
80 | protected static function execute_function_code ($code, $vars = array()) {
81 | $result = '';
82 |
83 | $f_name = null;
84 | $f_args_raw = '';
85 |
86 | $parts = array_map('trim', explode('|', $code));
87 |
88 | if (count($parts) > 1) {
89 | list ($f_name, $f_args_raw) = $parts;
90 | }
91 | else {
92 | $f_name = $parts[0];
93 | }
94 |
95 | $f_args = self::process_function_args($f_args_raw, $vars);
96 |
97 | if (in_array($f_name, self::$enabled_functions)) {
98 | $result = call_user_func_array($f_name, $f_args);
99 | }
100 |
101 | return $result;
102 | }
103 |
104 | /*
105 | render functions:
106 | [@fn | ]
107 | */
108 | public static function render_functions ($template, $vars = array()) {
109 | return preg_replace_callback(
110 | "/\[\@fn ([^\]]+)\]/ium",
111 | function ($matches) use ($vars) {
112 | if (count($matches) === 2) {
113 | $fn = $matches[1];
114 | return self::execute_function_code($fn, $vars);
115 | }
116 | return '';
117 | }, $template);
118 | }
119 |
120 | // replace variables with values
121 | public static function render_vars ($template, $vars = array()) {
122 | $result = $template;
123 |
124 | foreach ($vars as $name => $value) {
125 | $replacement = $value;
126 | // we do not support nested loops, so just replace with json string
127 | if (is_array($replacement)) {
128 | $replacement = '[' . json_encode($replacement) . ']';
129 | }
130 |
131 | $result = preg_replace('/{{ ' . preg_quote($name) . ' }}/', $replacement, $result);
132 | }
133 | return $result;
134 | }
135 |
136 | // render loop
137 | public static function render_loop ($loop_cond, $loop_body, $data = array()) {
138 | $result = '';
139 | list($condition, $source_name) = explode('<=', $loop_cond);
140 | $loop_vars = explode(',', str_replace(array('(', ')', ' '), '', $condition));
141 |
142 | if (count($loop_vars) > 1) {
143 | list($l_value, $l_key) = $loop_vars;
144 | }
145 | else {
146 | $l_value = $loop_vars[0];
147 | $l_key = 'loop.index';
148 | }
149 |
150 | $source_name = trim($source_name);
151 |
152 | if (!isset($data[$source_name]) || empty($data[$source_name])) {
153 | return $result;
154 | }
155 |
156 | $index = 1;
157 | foreach ($data[$source_name] as $key => $value) {
158 | $local_vars = $data;
159 | $local_vars[$l_key] = $index++;
160 |
161 | $local_vars[$l_value] = $value;
162 |
163 | if (!is_numeric($key)) {
164 | $local_vars[$l_value . '.' . $key] = $value;
165 | }
166 | else {
167 | if (is_array($value)) {
168 | foreach ($value as $attrName => $attrValue) {
169 | $local_vars[$l_value . '.' . $attrName] = $attrValue;
170 | }
171 | }
172 | }
173 | $result .= self::render_vars($loop_body, $local_vars);
174 | }
175 |
176 | return self::render_functions($result, $local_vars);
177 | }
178 |
179 | // render template
180 | public static function render ($template, $vars = array()) {
181 | /*
182 |
183 | [@each (field,iter) <= source]
184 | values
185 | [@endeach]
186 | something other
187 |
188 | becomes single line
189 | */
190 | $normalized_template = self::normalize($template);
191 | $loops = array_filter(explode('[@endeach]', $normalized_template));
192 |
193 | $result = '';
194 |
195 | /*
196 | [@each (field,iter) <= source]values
197 | processed as:
198 | 0: whole match
199 | 1: pre:
200 | 2: loop_cond: (field,iter) <= source
201 | 3: loop_body: values
202 | */
203 | foreach ($loops as $loop) {
204 | if (preg_match("/((?!\[\@each).*)?\[\@each ([^\]]+)\]\s*(.*)/iu", $loop, $matches)) {
205 | if (count($matches) === 4) {
206 | $pre = $matches[1];
207 | $loop_cond = $matches[2];
208 | $loop_body = $matches[3];
209 | $pre = trim(self::render_vars($pre, $vars));
210 | $result .= trim(self::render_functions($pre, $vars));
211 | $result .= trim(self::render_loop($loop_cond, $loop_body, $vars));
212 | }
213 | }
214 | else {
215 | $fns_executed = self::render_functions($loop, $vars);
216 | $result .= trim(self::render_vars($fns_executed, $vars));
217 | }
218 | }
219 | return $result;
220 | }
221 | }
--------------------------------------------------------------------------------