diff --git a/.github/frappe_linter/translation.py b/.github/frappe_linter/translation.py index d9fc98c76e..5d33355a1b 100644 --- a/.github/frappe_linter/translation.py +++ b/.github/frappe_linter/translation.py @@ -7,22 +7,28 @@ start_pattern = re.compile(r"_{1,2}\([\"']{1,3}") # skip first argument files = sys.argv[1:] -for _file in files: - if not _file.endswith(('.py', '.js')): - continue +files_to_scan = [_file for _file in files if _file.endswith(('.py', '.js'))] + +for _file in files_to_scan: with open(_file, 'r') as f: print(f'Checking: {_file}') - for num, line in enumerate(f, 1): - all_matches = start_pattern.finditer(line) - if all_matches: - for match in all_matches: - verify = pattern.search(line) - if not verify: - errors_encounter += 1 - print(f'A syntax error has been discovered at line number: {num}') - print(f'Syntax error occurred with: {line}') + file_lines = f.readlines() + for line_number, line in enumerate(file_lines, 1): + start_matches = start_pattern.search(line) + if start_matches: + match = pattern.search(line) + if not match and line.endswith(',\n'): + # concat remaining text to validate multiline pattern + line = "".join(file_lines[line_number - 1:]) + line = line[start_matches.start() + 1:] + match = pattern.match(line) + + if not match: + errors_encounter += 1 + print(f'\nTranslation syntax error at line number: {line_number + 1}\n{line.strip()[:100]}') + if errors_encounter > 0: - print('You can visit "https://frappeframework.com/docs/user/en/translations" to resolve this error.') - assert 1+1 == 3 + print('\nYou can visit "https://frappeframework.com/docs/user/en/translations" to resolve this error.') + sys.exit(1) else: - print('Good To Go!') + print('\nGood To Go!') diff --git a/.github/workflows/publish-assets-develop.yml b/.github/workflows/publish-assets-develop.yml new file mode 100644 index 0000000000..ee633ef039 --- /dev/null +++ b/.github/workflows/publish-assets-develop.yml @@ -0,0 +1,43 @@ +name: Build and Publish Assets for Development + +on: + push: + branches: [ develop ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + path: 'frappe' + - uses: actions/setup-node@v1 + with: + python-version: '12.x' + - uses: actions/setup-python@v2 + with: + python-version: '3.6' + - name: Set up bench for current push + run: | + npm install -g yarn + pip3 install -U frappe-bench + bench init frappe-bench --no-procfile --no-backups --skip-assets --skip-redis-config-generation --python $(which python) --frappe-path $GITHUB_WORKSPACE/frappe + cd frappe-bench && bench build + + - name: Package assets + run: | + mkdir -p $GITHUB_WORKSPACE/build + tar -cvpzf $GITHUB_WORKSPACE/build/$GITHUB_SHA.tar.gz ./frappe-bench/sites/assets/js ./frappe-bench/sites/assets/css + + - name: Publish assets to S3 + uses: jakejarvis/s3-sync-action@master + with: + args: --acl public-read + env: + AWS_S3_BUCKET: 'assets.frappeframework.com' + AWS_ACCESS_KEY_ID: ${{ secrets.S3_ASSETS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_ASSETS_SECRET_ACCESS_KEY }} + AWS_S3_ENDPOINT: 'http://s3.fr-par.scw.cloud' + AWS_REGION: 'fr-par' + SOURCE_DIR: '$GITHUB_WORKSPACE/build' diff --git a/.github/workflows/publish-assets-releases.yml b/.github/workflows/publish-assets-releases.yml new file mode 100644 index 0000000000..5c412ea1b0 --- /dev/null +++ b/.github/workflows/publish-assets-releases.yml @@ -0,0 +1,47 @@ +name: Build and Publish Assets built for Releases + +on: + release: + types: [ created ] + +env: + GITHUB_TOKEN: ${{ github.token }} + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + path: 'frappe' + - uses: actions/setup-node@v1 + with: + python-version: '12.x' + - uses: actions/setup-python@v2 + with: + python-version: '3.6' + - name: Set up bench for current push + run: | + npm install -g yarn + pip3 install -U frappe-bench + bench init frappe-bench --no-procfile --no-backups --skip-assets --skip-redis-config-generation --python $(which python) --frappe-path $GITHUB_WORKSPACE/frappe + cd frappe-bench && bench build + + - name: Package assets + run: | + mkdir -p $GITHUB_WORKSPACE/build + tar -cvpzf $GITHUB_WORKSPACE/build/assets.tar.gz ./frappe-bench/sites/assets/js ./frappe-bench/sites/assets/css + + - name: Get release + id: get_release + uses: bruceadams/get-release@v1.2.0 + + - name: Upload built Assets to Release + uses: actions/upload-release-asset@v1.0.2 + with: + upload_url: ${{ steps.get_release.outputs.upload_url }} + asset_path: build/assets.tar.gz + asset_name: assets.tar.gz + asset_content_type: application/octet-stream + diff --git a/.snyk b/.snyk index 6e7bb44986..6c6555a819 100644 --- a/.snyk +++ b/.snyk @@ -65,3 +65,37 @@ patch: patched: '2020-04-30T23:02:32.330Z' - quill-image-resize > lodash: patched: '2020-08-24T23:06:37.710Z' + - node-sass > lodash: + patched: '2020-09-15T23:06:41.931Z' + - node-sass > sass-graph > lodash: + patched: '2020-09-15T23:06:41.931Z' + - node-sass > gaze > globule > lodash: + patched: '2020-09-15T23:06:41.931Z' + - snyk > graphlib > lodash: + patched: '2020-09-16T23:06:38.881Z' + - snyk > @snyk/snyk-cocoapods-plugin > @snyk/dep-graph > graphlib > lodash: + patched: '2020-09-16T23:06:38.881Z' + - snyk > snyk-cpp-plugin > @snyk/dep-graph > graphlib > lodash: + patched: '2020-09-16T23:06:38.881Z' + - snyk > snyk-go-plugin > @snyk/dep-graph > graphlib > lodash: + patched: '2020-09-16T23:06:38.881Z' + - snyk > snyk-gradle-plugin > @snyk/dep-graph > graphlib > lodash: + patched: '2020-09-16T23:06:38.881Z' + - snyk > snyk-docker-plugin > snyk-nodejs-lockfile-parser > graphlib > lodash: + patched: '2020-09-16T23:06:38.881Z' + - snyk > snyk-mvn-plugin > @snyk/java-call-graph-builder > graphlib > lodash: + patched: '2020-09-16T23:06:38.881Z' + - snyk > @snyk/snyk-cocoapods-plugin > @snyk/cocoapods-lockfile-parser > @snyk/dep-graph > graphlib > lodash: + patched: '2020-09-16T23:06:38.881Z' + - snyk > snyk-php-plugin > @snyk/cli-interface > @snyk/dep-graph > graphlib > lodash: + patched: '2020-09-16T23:06:38.881Z' + - snyk > snyk-gradle-plugin > @snyk/cli-interface > @snyk/dep-graph > graphlib > lodash: + patched: '2020-09-16T23:06:38.881Z' + - snyk > snyk-mvn-plugin > @snyk/cli-interface > @snyk/dep-graph > graphlib > lodash: + patched: '2020-09-16T23:06:38.881Z' + - snyk > @snyk/dep-graph > graphlib > lodash: + patched: '2020-09-16T23:06:38.881Z' + - snyk > snyk-nodejs-lockfile-parser > graphlib > lodash: + patched: '2020-09-16T23:06:38.881Z' + - snyk > snyk-go-plugin > graphlib > lodash: + patched: '2020-09-16T23:06:38.881Z' diff --git a/cypress/integration/recorder.js b/cypress/integration/recorder.js index ed2a9c86ba..8a4aeddd0a 100644 --- a/cypress/integration/recorder.js +++ b/cypress/integration/recorder.js @@ -61,10 +61,10 @@ context('Recorder', () => { cy.visit('/desk#recorder'); - cy.get('.list-row-container span').contains('frappe.desk.reportview.get').click(); + cy.get('.list-row-container span').contains('/api/method/frappe').click(); cy.location('hash').should('contain', '#recorder/request/'); - cy.get('form').should('contain', 'frappe.desk.reportview.get'); + cy.get('form').should('contain', '/api/method/frappe'); cy.get('#page-recorder .primary-action').should('contain', 'Stop').click(); cy.get('#page-recorder .btn-secondary').should('contain', 'Clear').click(); diff --git a/frappe/__init__.py b/frappe/__init__.py index 36a8b48ecd..e1fe790b45 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -182,6 +182,7 @@ def init(site, sites_path=None, new_site=False): local.meta_cache = {} local.form_dict = _dict() local.session = _dict() + local.dev_server = os.environ.get('DEV_SERVER', False) setup_module_map() @@ -513,12 +514,15 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message whitelisted = [] guest_methods = [] xss_safe_methods = [] -def whitelist(allow_guest=False, xss_safe=False): +allowed_http_methods_for_whitelisted_func = {} + +def whitelist(allow_guest=False, xss_safe=False, methods=None): """ Decorator for whitelisting a function and making it accessible via HTTP. Standard request will be `/api/method/[path.to.method]` :param allow_guest: Allow non logged-in user to access this method. + :param methods: Allowed http method to access the method. Use as: @@ -526,10 +530,16 @@ def whitelist(allow_guest=False, xss_safe=False): def myfunc(param1, param2): pass """ + + if not methods: + methods = ['GET', 'POST', 'PUT', 'DELETE'] + def innerfn(fn): - global whitelisted, guest_methods, xss_safe_methods + global whitelisted, guest_methods, xss_safe_methods, allowed_http_methods_for_whitelisted_func whitelisted.append(fn) + allowed_http_methods_for_whitelisted_func[fn] = methods + if allow_guest: guest_methods.append(fn) @@ -1109,8 +1119,8 @@ def get_newargs(fn, kwargs): if (a in fnargs) or varkw: newargs[a] = kwargs.get(a) - if "flags" in newargs: - del newargs["flags"] + newargs.pop("ignore_permissions", None) + newargs.pop("flags", None) return newargs diff --git a/frappe/automation/doctype/auto_repeat/auto_repeat.py b/frappe/automation/doctype/auto_repeat/auto_repeat.py index c09e347e71..a9a358ce5f 100644 --- a/frappe/automation/doctype/auto_repeat/auto_repeat.py +++ b/frappe/automation/doctype/auto_repeat/auto_repeat.py @@ -403,6 +403,7 @@ def update_reference(docname, reference): @frappe.whitelist() def generate_message_preview(reference_dt, reference_doc, message=None, subject=None): + frappe.has_permission("Auto Repeat", "write", throw=True) doc = frappe.get_doc(reference_dt, reference_doc) subject_preview = _("Please add a subject to your email") msg_preview = frappe.render_template(message, {'doc': doc}) diff --git a/frappe/build.py b/frappe/build.py index 761541f7a9..767217a9b9 100644 --- a/frappe/build.py +++ b/frappe/build.py @@ -11,24 +11,141 @@ import warnings import tempfile from distutils.spawn import find_executable -from six import iteritems, text_type - import frappe from frappe.utils.minify import JavascriptMinify +import click +from requests import get +from six import iteritems, text_type +from six.moves.urllib.parse import urlparse + timestamps = {} app_paths = None +sites_path = os.path.abspath(os.getcwd()) + + +def download_file(url, prefix): + filename = urlparse(url).path.split("/")[-1] + local_filename = os.path.join(prefix, filename) + with get(url, stream=True, allow_redirects=True) as r: + r.raise_for_status() + with open(local_filename, "wb") as f: + for chunk in r.iter_content(chunk_size=8192): + f.write(chunk) + return local_filename + + +def build_missing_files(): + # check which files dont exist yet from the build.json and tell build.js to build only those! + missing_assets = [] + current_asset_files = [] + + for type in ["css", "js"]: + current_asset_files.extend( + [ + "{0}/{1}".format(type, name) + for name in os.listdir(os.path.join(sites_path, "assets", type)) + ] + ) + + with open(os.path.join(sites_path, "assets", "frappe", "build.json")) as f: + all_asset_files = json.load(f).keys() + + for asset in all_asset_files: + if asset.replace("concat:", "") not in current_asset_files: + missing_assets.append(asset) + + if missing_assets: + from subprocess import check_call + from shlex import split + + click.secho("\nBuilding missing assets...\n", fg="yellow") + command = split( + "node rollup/build.js --files {0} --no-concat".format(",".join(missing_assets)) + ) + check_call(command, cwd=os.path.join("..", "apps", "frappe")) + + +def get_assets_link(frappe_head): + from subprocess import getoutput + from requests import head + + tag = getoutput( + "cd ../apps/frappe && git show-ref --tags -d | grep %s | sed -e 's,.*" + " refs/tags/,,' -e 's/\^{}//'" + % frappe_head + ) + + if tag: + # if tag exists, download assets from github release + url = "https://github.com/frappe/frappe/releases/download/{0}/assets.tar.gz".format(tag) + else: + url = "http://assets.frappeframework.com/{0}.tar.gz".format(frappe_head) + + if not head(url): + raise ValueError("URL {0} doesn't exist".format(url)) + + return url + + +def download_frappe_assets(verbose=True): + """Downloads and sets up Frappe assets if they exist based on the current + commit HEAD. + Returns True if correctly setup else returns False. + """ + from simple_chalk import green + from subprocess import getoutput + from tempfile import mkdtemp + + assets_setup = False + frappe_head = getoutput("cd ../apps/frappe && git rev-parse HEAD") + + if frappe_head: + try: + url = get_assets_link(frappe_head) + click.secho("Retreiving assets...", fg="yellow") + prefix = mkdtemp(prefix="frappe-assets-", suffix=frappe_head) + assets_archive = download_file(url, prefix) + print("\n{0} Downloaded Frappe assets from {1}".format(green('✔'), url)) + + if assets_archive: + import tarfile + + click.secho("\nExtracting assets...\n", fg="yellow") + with tarfile.open(assets_archive) as tar: + for file in tar: + if not file.isdir(): + dest = "." + file.name.replace("./frappe-bench/sites", "") + show = dest.replace("./assets/", "") + tar.makefile(file, dest) + print("{0} Restored {1}".format(green('✔'), show)) + + build_missing_files() + return True + else: + raise + except Exception: + # TODO: log traceback in bench.log + click.secho("An Error occurred while downloading assets...", fg="red") + assets_setup = False + finally: + try: + shutil.rmtree(os.path.dirname(assets_archive)) + except Exception: + pass + + return assets_setup def symlink(target, link_name, overwrite=False): - ''' + """ Create a symbolic link named link_name pointing to target. If link_name exists then FileExistsError is raised, unless overwrite=True. When trying to overwrite a directory, IsADirectoryError is raised. Source: https://stackoverflow.com/a/55742015/10309266 - ''' + """ if not overwrite: return os.symlink(target, link_name) @@ -76,27 +193,28 @@ def setup(): def get_node_pacman(): - pacmans = ['yarn', 'npm'] - for exec_ in pacmans: - exec_ = find_executable(exec_) - if exec_: - return exec_ - raise ValueError('No Node.js Package Manager found.') + exec_ = find_executable("yarn") + if exec_: + return exec_ + raise ValueError("Yarn not found") -def bundle(no_compress, app=None, make_copy=False, restore=False, verbose=False): +def bundle(no_compress, app=None, make_copy=False, restore=False, verbose=False, skip_frappe=False): """concat / minify js files""" setup() make_asset_dirs(make_copy=make_copy, restore=restore) pacman = get_node_pacman() - mode = 'build' if no_compress else 'production' - command = '{pacman} run {mode}'.format(pacman=pacman, mode=mode) + mode = "build" if no_compress else "production" + command = "{pacman} run {mode}".format(pacman=pacman, mode=mode) if app: - command += ' --app {app}'.format(app=app) + command += " --app {app}".format(app=app) - frappe_app_path = os.path.abspath(os.path.join(app_paths[0], '..')) + if skip_frappe: + command += " --skip_frappe" + + frappe_app_path = os.path.abspath(os.path.join(app_paths[0], "..")) check_yarn() frappe.commands.popen(command, cwd=frappe_app_path) @@ -107,22 +225,22 @@ def watch(no_compress): pacman = get_node_pacman() - frappe_app_path = os.path.abspath(os.path.join(app_paths[0], '..')) + frappe_app_path = os.path.abspath(os.path.join(app_paths[0], "..")) check_yarn() - frappe_app_path = frappe.get_app_path('frappe', '..') - frappe.commands.popen('{pacman} run watch'.format(pacman=pacman), cwd=frappe_app_path) + frappe_app_path = frappe.get_app_path("frappe", "..") + frappe.commands.popen("{pacman} run watch".format(pacman=pacman), cwd=frappe_app_path) def check_yarn(): - if not find_executable('yarn'): - print('Please install yarn using below command and try again.\nnpm install -g yarn') + if not find_executable("yarn"): + print("Please install yarn using below command and try again.\nnpm install -g yarn") def make_asset_dirs(make_copy=False, restore=False): # don't even think of making assets_path absolute - rm -rf ahead. assets_path = os.path.join(frappe.local.sites_path, "assets") - for dir_path in [os.path.join(assets_path, 'js'), os.path.join(assets_path, 'css')]: + for dir_path in [os.path.join(assets_path, "js"), os.path.join(assets_path, "css")]: if not os.path.exists(dir_path): os.makedirs(dir_path) @@ -131,24 +249,27 @@ def make_asset_dirs(make_copy=False, restore=False): app_base_path = os.path.abspath(os.path.dirname(pymodule.__file__)) symlinks = [] - app_public_path = os.path.join(app_base_path, 'public') + app_public_path = os.path.join(app_base_path, "public") # app/public > assets/app symlinks.append([app_public_path, os.path.join(assets_path, app_name)]) # app/node_modules > assets/app/node_modules if os.path.exists(os.path.abspath(app_public_path)): - symlinks.append([os.path.join(app_base_path, '..', 'node_modules'), os.path.join( - assets_path, app_name, 'node_modules')]) + symlinks.append( + [ + os.path.join(app_base_path, "..", "node_modules"), + os.path.join(assets_path, app_name, "node_modules"), + ] + ) app_doc_path = None - if os.path.isdir(os.path.join(app_base_path, 'docs')): - app_doc_path = os.path.join(app_base_path, 'docs') + if os.path.isdir(os.path.join(app_base_path, "docs")): + app_doc_path = os.path.join(app_base_path, "docs") - elif os.path.isdir(os.path.join(app_base_path, 'www', 'docs')): - app_doc_path = os.path.join(app_base_path, 'www', 'docs') + elif os.path.isdir(os.path.join(app_base_path, "www", "docs")): + app_doc_path = os.path.join(app_base_path, "www", "docs") if app_doc_path: - symlinks.append([app_doc_path, os.path.join( - assets_path, app_name + '_docs')]) + symlinks.append([app_doc_path, os.path.join(assets_path, app_name + "_docs")]) for source, target in symlinks: source = os.path.abspath(source) @@ -162,7 +283,7 @@ def make_asset_dirs(make_copy=False, restore=False): shutil.copytree(source, target) elif make_copy: if os.path.exists(target): - warnings.warn('Target {target} already exists.'.format(target=target)) + warnings.warn("Target {target} already exists.".format(target=target)) else: shutil.copytree(source, target) else: @@ -174,7 +295,7 @@ def make_asset_dirs(make_copy=False, restore=False): try: symlink(source, target, overwrite=True) except OSError: - print('Cannot link {} to {}'.format(source, target)) + print("Cannot link {} to {}".format(source, target)) else: # warnings.warn('Source {source} does not exist.'.format(source = source)) pass @@ -193,7 +314,7 @@ def get_build_maps(): build_maps = {} for app_path in app_paths: - path = os.path.join(app_path, 'public', 'build.json') + path = os.path.join(app_path, "public", "build.json") if os.path.exists(path): with open(path) as f: try: @@ -202,8 +323,7 @@ def get_build_maps(): source_paths = [] for source in sources: if isinstance(source, list): - s = frappe.get_pymodule_path( - source[0], *source[1].split("/")) + s = frappe.get_pymodule_path(source[0], *source[1].split("/")) else: s = os.path.join(app_path, source) source_paths.append(s) @@ -211,36 +331,42 @@ def get_build_maps(): build_maps[target] = source_paths except ValueError as e: print(path) - print('JSON syntax error {0}'.format(str(e))) + print("JSON syntax error {0}".format(str(e))) return build_maps def pack(target, sources, no_compress, verbose): from six import StringIO - outtype, outtxt = target.split(".")[-1], '' + outtype, outtxt = target.split(".")[-1], "" jsm = JavascriptMinify() for f in sources: suffix = None - if ':' in f: - f, suffix = f.split(':') + if ":" in f: + f, suffix = f.split(":") if not os.path.exists(f) or os.path.isdir(f): print("did not find " + f) continue timestamps[f] = os.path.getmtime(f) try: - with open(f, 'r') as sourcefile: - data = text_type(sourcefile.read(), 'utf-8', errors='ignore') + with open(f, "r") as sourcefile: + data = text_type(sourcefile.read(), "utf-8", errors="ignore") extn = f.rsplit(".", 1)[1] - if outtype == "js" and extn == "js" and (not no_compress) and suffix != "concat" and (".min." not in f): - tmpin, tmpout = StringIO(data.encode('utf-8')), StringIO() + if ( + outtype == "js" + and extn == "js" + and (not no_compress) + and suffix != "concat" + and (".min." not in f) + ): + tmpin, tmpout = StringIO(data.encode("utf-8")), StringIO() jsm.minify(tmpin, tmpout) minified = tmpout.getvalue() if minified: - outtxt += text_type(minified or '', 'utf-8').strip('\n') + ';' + outtxt += text_type(minified or "", "utf-8").strip("\n") + ";" if verbose: print("{0}: {1}k".format(f, int(len(minified) / 1024))) @@ -248,27 +374,27 @@ def pack(target, sources, no_compress, verbose): # add to frappe.templates outtxt += html_to_js_template(f, data) else: - outtxt += ('\n/*\n *\t%s\n */' % f) - outtxt += '\n' + data + '\n' + outtxt += "\n/*\n *\t%s\n */" % f + outtxt += "\n" + data + "\n" except Exception: print("--Error in:" + f + "--") print(frappe.get_traceback()) - with open(target, 'w') as f: + with open(target, "w") as f: f.write(outtxt.encode("utf-8")) - print("Wrote %s - %sk" % (target, str(int(os.path.getsize(target)/1024)))) + print("Wrote %s - %sk" % (target, str(int(os.path.getsize(target) / 1024)))) def html_to_js_template(path, content): - '''returns HTML template content as Javascript code, adding it to `frappe.templates`''' + """returns HTML template content as Javascript code, adding it to `frappe.templates`""" return """frappe.templates["{key}"] = '{content}';\n""".format( key=path.rsplit("/", 1)[-1][:-5], content=scrub_html_template(content)) def scrub_html_template(content): - '''Returns HTML content with removed whitespace and comments''' + """Returns HTML content with removed whitespace and comments""" # remove whitespace to a single space content = re.sub("\s+", " ", content) @@ -281,12 +407,12 @@ def scrub_html_template(content): def files_dirty(): for target, sources in iteritems(get_build_maps()): for f in sources: - if ':' in f: - f, suffix = f.split(':') + if ":" in f: + f, suffix = f.split(":") if not os.path.exists(f) or os.path.isdir(f): continue if os.path.getmtime(f) != timestamps.get(f): - print(f + ' dirty') + print(f + " dirty") return True else: return False diff --git a/frappe/client.py b/frappe/client.py index 0db18421ef..2217b53673 100644 --- a/frappe/client.py +++ b/frappe/client.py @@ -107,7 +107,7 @@ def get_single_value(doctype, field): value = frappe.db.get_single_value(doctype, field) return value -@frappe.whitelist() +@frappe.whitelist(methods=['POST', 'PUT']) def set_value(doctype, name, fieldname, value=None): '''Set a value using get_doc, group of values @@ -142,7 +142,7 @@ def set_value(doctype, name, fieldname, value=None): return doc.as_dict() -@frappe.whitelist() +@frappe.whitelist(methods=['POST', 'PUT']) def insert(doc=None): '''Insert a document @@ -160,7 +160,7 @@ def insert(doc=None): doc = frappe.get_doc(doc).insert() return doc.as_dict() -@frappe.whitelist() +@frappe.whitelist(methods=['POST', 'PUT']) def insert_many(docs=None): '''Insert multiple documents @@ -186,7 +186,7 @@ def insert_many(docs=None): return out -@frappe.whitelist() +@frappe.whitelist(methods=['POST', 'PUT']) def save(doc): '''Update (save) an existing document @@ -199,7 +199,7 @@ def save(doc): return doc.as_dict() -@frappe.whitelist() +@frappe.whitelist(methods=['POST', 'PUT']) def rename_doc(doctype, old_name, new_name, merge=False): '''Rename document @@ -209,7 +209,7 @@ def rename_doc(doctype, old_name, new_name, merge=False): new_name = frappe.rename_doc(doctype, old_name, new_name, merge=merge) return new_name -@frappe.whitelist() +@frappe.whitelist(methods=['POST', 'PUT']) def submit(doc): '''Submit a document @@ -222,7 +222,7 @@ def submit(doc): return doc.as_dict() -@frappe.whitelist() +@frappe.whitelist(methods=['POST', 'PUT']) def cancel(doctype, name): '''Cancel a document @@ -233,7 +233,7 @@ def cancel(doctype, name): return wrapper.as_dict() -@frappe.whitelist() +@frappe.whitelist(methods=['DELETE', 'POST']) def delete(doctype, name): '''Delete a remote document @@ -241,13 +241,13 @@ def delete(doctype, name): :param name: name of the document to be deleted''' frappe.delete_doc(doctype, name, ignore_missing=False) -@frappe.whitelist() +@frappe.whitelist(methods=['POST', 'PUT']) def set_default(key, value, parent=None): """set a user default value""" frappe.db.set_default(key, value, parent or frappe.session.user) frappe.clear_cache(user=frappe.session.user) -@frappe.whitelist() +@frappe.whitelist(methods=['POST', 'PUT']) def make_width_property_setter(doc): '''Set width Property Setter @@ -257,7 +257,7 @@ def make_width_property_setter(doc): if doc["doctype"]=="Property Setter" and doc["property"]=="width": frappe.get_doc(doc).insert(ignore_permissions = True) -@frappe.whitelist() +@frappe.whitelist(methods=['POST', 'PUT']) def bulk_update(docs): '''Bulk update documents @@ -333,7 +333,7 @@ def get_time_zone(): '''Returns default time zone''' return {"time_zone": frappe.defaults.get_defaults().get("time_zone")} -@frappe.whitelist() +@frappe.whitelist(methods=['POST', 'PUT']) def attach_file(filename=None, filedata=None, doctype=None, docname=None, folder=None, decode_base64=False, is_private=None, docfield=None): '''Attach a file to Document (POST) diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 721376016c..cf99cc914e 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -1,17 +1,17 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, absolute_import, print_function -import click -import json, os, sys, subprocess +import json +import os +import subprocess +import sys from distutils.spawn import find_executable + +import click + import frappe -from frappe.commands import pass_context, get_site +from frappe.commands import get_site, pass_context from frappe.exceptions import SiteNotSpecifiedError -from frappe.utils import update_progress_bar, get_bench_path -from frappe.utils.response import json_handler -from coverage import Coverage -import cProfile, pstats -from six import StringIO +from frappe.utils import get_bench_path, update_progress_bar @click.command('build') @@ -19,14 +19,22 @@ from six import StringIO @click.option('--make-copy', is_flag=True, default=False, help='Copy the files instead of symlinking') @click.option('--restore', is_flag=True, default=False, help='Copy the files instead of symlinking with force') @click.option('--verbose', is_flag=True, default=False, help='Verbose') -def build(app=None, make_copy=False, restore = False, verbose=False): +@click.option('--force', is_flag=True, default=False, help='Force build assets instead of downloading available') +def build(app=None, make_copy=False, restore=False, verbose=False, force=False): "Minify + concatenate JS and CSS files, build translations" import frappe.build - import frappe frappe.init('') # don't minify in developer_mode for faster builds no_compress = frappe.local.conf.developer_mode or False - frappe.build.bundle(no_compress, app=app, make_copy=make_copy, restore = restore, verbose=verbose) + + # dont try downloading assets if force used, app specified or running via CI + if not (force or app or os.environ.get('CI')): + # skip building frappe if assets exist remotely + skip_frappe = frappe.build.download_frappe_assets(verbose=verbose) + else: + skip_frappe = False + + frappe.build.bundle(no_compress, app=app, make_copy=make_copy, restore=restore, verbose=verbose, skip_frappe=skip_frappe) @click.command('watch') @@ -133,6 +141,7 @@ def reset_perms(context): def execute(context, method, args=None, kwargs=None, profile=False): "Execute a function" for site in context.sites: + ret = "" try: frappe.init(site=site) frappe.connect() @@ -151,12 +160,19 @@ def execute(context, method, args=None, kwargs=None, profile=False): kwargs = {} if profile: + import cProfile pr = cProfile.Profile() pr.enable() - ret = frappe.get_attr(method)(*args, **kwargs) + try: + ret = frappe.get_attr(method)(*args, **kwargs) + except Exception: + ret = frappe.safe_eval(method + "(*args, **kwargs)", eval_globals=globals(), eval_locals=locals()) if profile: + import pstats + from six import StringIO + pr.disable() s = StringIO() pstats.Stats(pr, stream=s).sort_stats('cumulative').print_stats(.5) @@ -167,6 +183,7 @@ def execute(context, method, args=None, kwargs=None, profile=False): finally: frappe.destroy() if ret: + from frappe.utils.response import json_handler print(json.dumps(ret, default=json_handler)) if not context.sites: @@ -492,6 +509,8 @@ def run_tests(context, app=None, module=None, doctype=None, test=(), frappe.flags.skip_test_records = skip_test_records if coverage: + from coverage import Coverage + # Generate coverage report only for app that is being tested source_path = os.path.join(get_bench_path(), 'apps', app or 'frappe') cov = Coverage(source=[source_path], omit=[ diff --git a/frappe/core/doctype/activity_log/activity_log.json b/frappe/core/doctype/activity_log/activity_log.json index 580882968c..a1ee4dafdb 100644 --- a/frappe/core/doctype/activity_log/activity_log.json +++ b/frappe/core/doctype/activity_log/activity_log.json @@ -1,731 +1,184 @@ - { - "allow_copy": 0, - "allow_guest_to_view": 0, +{ + "actions": [], "allow_import": 1, - "allow_rename": 0, - "autoname": "", - "beta": 0, "creation": "2017-10-05 11:10:38.780133", - "custom": 0, "description": "Keep track of all update feeds", - "docstatus": 0, "doctype": "DocType", "document_type": "Setup", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "subject", + "section_break_8", + "content", + "column_break_5", + "additional_info", + "communication_date", + "column_break_7", + "operation", + "status", + "reference_section", + "reference_doctype", + "reference_name", + "reference_owner", + "column_break_14", + "timeline_doctype", + "timeline_name", + "link_doctype", + "link_name", + "user", + "full_name" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "subject", "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, "in_list_view": 1, - "in_standard_filter": 0, "label": "Subject", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "section_break_8", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "content", "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Message", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "400" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_5", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "additional_info", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "More Information", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "More Information" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Now", "fieldname": "communication_date", "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Date" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_7", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "operation", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Operation", - "length": 0, - "no_copy": 0, - "options": "\nLogin\nLogout", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "\nLogin\nLogout" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "status", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Status", - "length": 0, - "no_copy": 0, - "options": "\nSuccess\nFailed\nLinked\nClosed", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "\nSuccess\nFailed\nLinked\nClosed" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "reference_section", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Reference", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Reference" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "reference_doctype", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Reference Document Type", - "length": 0, - "no_copy": 0, - "options": "DocType", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "DocType" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "reference_name", "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Reference Name", - "length": 0, - "no_copy": 0, - "options": "reference_doctype", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "reference_doctype" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "reference_name.owner", "fieldname": "reference_owner", "fieldtype": "Read Only", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Reference Owner", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_14", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "timeline_doctype", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Timeline DocType", - "length": 0, - "no_copy": 0, - "options": "DocType", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "DocType" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "timeline_name", "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Timeline Name", - "length": 0, - "no_copy": 0, - "options": "timeline_doctype", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "timeline_doctype" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "link_doctype", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Link DocType", - "length": 0, - "no_copy": 0, "options": "DocType", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "link_name", "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Link Name", - "length": 0, - "no_copy": 0, "options": "link_doctype", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "__user", "fieldname": "user", "fieldtype": "Link", - "hidden": 0, "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "User", - "length": 0, - "no_copy": 0, "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "full_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, - "label": "Full Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Full Name" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "icon": "fa fa-comment", - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-09-05 14:22:27.664645", + "index_web_pages_for_search": 1, + "links": [], + "modified": "2020-08-28 11:43:57.504565", "modified_by": "Administrator", "module": "Core", "name": "Activity Log", - "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, "read": 1, "report": 1, "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 + "share": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 1, + "if_owner": 1, "print": 1, "read": 1, "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 1, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, "role": "All", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "share": 1 } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, "search_fields": "subject", - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", "title_field": "subject", diff --git a/frappe/core/doctype/activity_log/activity_log.py b/frappe/core/doctype/activity_log/activity_log.py index 8b7941c086..27a2892ca8 100644 --- a/frappe/core/doctype/activity_log/activity_log.py +++ b/frappe/core/doctype/activity_log/activity_log.py @@ -25,9 +25,6 @@ class ActivityLog(Document): if self.reference_doctype and self.reference_name: self.status = "Linked" - def on_trash(self): # pylint: disable=no-self-use - frappe.throw(_("Sorry! You cannot delete auto-generated comments")) - def on_doctype_update(): """Add indexes in `tabActivity Log`""" frappe.db.add_index("Activity Log", ["reference_doctype", "reference_name"]) diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index 232d485f36..fc929351d4 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -455,18 +455,18 @@ def update_parent_document_on_communication(doc): # update the modified date for document parent.update_modified() - update_mins_to_first_communication(parent, doc) + update_first_response_time(parent, doc) set_avg_response_time(parent, doc) parent.run_method("notify_communication", doc) parent.notify_update() -def update_mins_to_first_communication(parent, communication): - if parent.meta.has_field("mins_to_first_response") and not parent.get("mins_to_first_response"): +def update_first_response_time(parent, communication): + if parent.meta.has_field("first_response_time") and not parent.get("first_response_time"): if is_system_user(communication.sender): first_responded_on = communication.creation if parent.meta.has_field("first_responded_on") and communication.sent_or_received == "Sent": parent.db_set("first_responded_on", first_responded_on) - parent.db_set("mins_to_first_response", round(time_diff_in_seconds(first_responded_on, parent.creation) / 60), 2) + parent.db_set("first_response_time", round(time_diff_in_seconds(first_responded_on, parent.creation), 2)) def set_avg_response_time(parent, communication): if parent.meta.has_field("avg_response_time") and communication.sent_or_received == "Sent": diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 5c558f3bd6..78d59e8ae4 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -234,6 +234,8 @@ class DocType(Document): if not autoname and self.get("fields", {"fieldname":"naming_series"}): self.autoname = "naming_series:" + elif self.autoname == "naming_series:" and not self.get("fields", {"fieldname":"naming_series"}): + frappe.throw(_("Invalid fieldname '{0}' in autoname").format(self.autoname)) # validate field name if autoname field:fieldname is used # Create unique index on autoname field automatically. diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 316915e43a..b8bed89a4d 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -278,25 +278,26 @@ class File(Document): base_url = os.path.dirname(self.file_url) files = [] - with zipfile.ZipFile(zip_path) as zf: - zf.extractall(os.path.dirname(zip_path)) - for info in zf.infolist(): - if not info.filename.startswith('__MACOSX'): - file_url = file_url = base_url + '/' + info.filename - file_name = frappe.db.get_value('File', dict(file_url=file_url)) - if file_name: - file_doc = frappe.get_doc('File', file_name) - else: - file_doc = frappe.new_doc("File") - file_doc.file_name = info.filename - file_doc.file_size = info.file_size - file_doc.folder = self.folder - file_doc.is_private = self.is_private - file_doc.file_url = file_url - file_doc.attached_to_doctype = self.attached_to_doctype - file_doc.attached_to_name = self.attached_to_name - file_doc.save() - files.append(file_doc) + with zipfile.ZipFile(zip_path) as z: + for file in z.filelist: + if file.is_dir() or file.filename.startswith('__MACOSX/'): + # skip directories and macos hidden directory + continue + + filename = os.path.basename(file.filename) + if filename.startswith('.'): + # skip hidden files + continue + + file_doc = frappe.new_doc('File') + file_doc.content = z.read(file.filename) + file_doc.file_name = filename + file_doc.folder = self.folder + file_doc.is_private = self.is_private + file_doc.attached_to_doctype = self.attached_to_doctype + file_doc.attached_to_name = self.attached_to_name + file_doc.save() + files.append(file_doc) frappe.delete_doc('File', self.name) return files @@ -359,6 +360,9 @@ class File(Document): """write file to disk with a random name (to compare)""" file_path = get_files_path(is_private=self.is_private) + if os.path.sep in self.file_name: + frappe.throw(_('File name cannot have {0}').format(os.path.sep)) + # create directory (if not exists) frappe.create_folder(file_path) # write the file @@ -938,7 +942,7 @@ def attach_files_to_document(doc, event): # we dont want the update to fail if file cannot be attached for some reason try: value = doc.get(df.fieldname) - if not value.startswith(("/files", "/private/files")): + if not (value or '').startswith(("/files", "/private/files")): return if frappe.db.exists("File", { diff --git a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py index 765ae5fe93..fa854f579e 100644 --- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py @@ -109,12 +109,14 @@ class ScheduledJobType(Document): def on_trash(self): frappe.db.sql('delete from `tabScheduled Job Log` where scheduled_job_type=%s', self.name) + @frappe.whitelist() def execute_event(doc): frappe.only_for('System Manager') doc = json.loads(doc) frappe.get_doc('Scheduled Job Type', doc.get('name')).enqueue() + def run_scheduled_job(job_type): '''This is a wrapper function that runs a hooks.scheduler_events method''' try: @@ -122,44 +124,62 @@ def run_scheduled_job(job_type): except Exception: print(frappe.get_traceback()) -def sync_jobs(): - frappe.reload_doc('core', 'doctype', 'scheduled_job_type') - all_events = [] - scheduler_events = frappe.get_hooks("scheduler_events") - insert_events(all_events, scheduler_events) - clear_events(all_events, scheduler_events) -def insert_events(all_events, scheduler_events): +def sync_jobs(hooks=None): + frappe.reload_doc("core", "doctype", "scheduled_job_type") + scheduler_events = hooks or frappe.get_hooks("scheduler_events") + all_events = insert_events(scheduler_events) + clear_events(all_events) + + +def insert_events(scheduler_events): + cron_jobs, event_jobs = [], [] for event_type in scheduler_events: events = scheduler_events.get(event_type) if isinstance(events, dict): - insert_cron_event(events, all_events) + cron_jobs += insert_cron_jobs(events) else: # hourly, daily etc - insert_event_list(events, event_type, all_events) + event_jobs += insert_event_jobs(events, event_type) + return cron_jobs + event_jobs -def insert_cron_event(events, all_events): + +def insert_cron_jobs(events): + cron_jobs = [] for cron_format in events: for event in events.get(cron_format): - all_events.append(event) - insert_single_event('Cron', event, cron_format) + cron_jobs.append(event) + insert_single_event("Cron", event, cron_format) + return cron_jobs -def insert_event_list(events, event_type, all_events): + +def insert_event_jobs(events, event_type): + event_jobs = [] for event in events: - all_events.append(event) + event_jobs.append(event) frequency = event_type.replace('_', ' ').title() insert_single_event(frequency, event) + return event_jobs -def insert_single_event(frequency, event, cron_format = None): - if not frappe.db.exists('Scheduled Job Type', dict(method=event)): - frappe.get_doc(dict( - doctype = 'Scheduled Job Type', - method = event, - cron_format = cron_format, - frequency = frequency - )).insert() -def clear_events(all_events, scheduler_events): - for event in frappe.get_all('Scheduled Job Type', ('name', 'method')): +def insert_single_event(frequency, event, cron_format=None): + cron_expr = {"cron_format": cron_format} if cron_format else {} + doc = frappe.get_doc({ + "doctype": "Scheduled Job Type", + "method": event, + "cron_format": cron_format, + "frequency": frequency + }) + + if not frappe.db.exists("Scheduled Job Type", {"method": event, "frequency": frequency, **cron_expr }): + try: + doc.insert() + except frappe.DuplicateEntryError: + doc.delete() + doc.insert() + + +def clear_events(all_events): + for event in frappe.get_all("Scheduled Job Type", ("name", "method")): if event.method not in all_events: - frappe.delete_doc('Scheduled Job Type', event.name) + frappe.delete_doc("Scheduled Job Type", event.name) diff --git a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py index ec1e70ad6a..e7db6f9045 100644 --- a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py @@ -11,11 +11,10 @@ from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs class TestScheduledJobType(unittest.TestCase): def setUp(self): - if not frappe.get_all('Scheduled Job Type', limit=1): - frappe.db.rollback() - frappe.db.sql('truncate `tabScheduled Job Type`') - sync_jobs() - frappe.db.commit() + frappe.db.rollback() + frappe.db.sql('truncate `tabScheduled Job Type`') + sync_jobs() + frappe.db.commit() def test_sync_jobs(self): all_job = frappe.get_doc('Scheduled Job Type', @@ -32,6 +31,12 @@ class TestScheduledJobType(unittest.TestCase): self.assertEqual(cron_job.frequency, 'Cron') self.assertEqual(cron_job.cron_format, '0/15 * * * *') + # check if jobs are synced after change in hooks + updated_scheduler_events = { "hourly": ["frappe.email.queue.flush"] } + sync_jobs(updated_scheduler_events) + updated_scheduled_job = frappe.get_doc("Scheduled Job Type", {"method": "frappe.email.queue.flush"}) + self.assertEqual(updated_scheduled_job.frequency, "Hourly") + def test_daily_job(self): job = frappe.get_doc('Scheduled Job Type', dict(method = 'frappe.email.queue.clear_outbox')) job.db_set('last_execution', '2019-01-01 00:00:00') diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index 819bb89e72..17f97b3e1a 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -40,6 +40,7 @@ "password_settings", "logout_on_password_reset", "force_user_to_reset_password", + "password_reset_limit", "column_break_31", "enable_password_policy", "minimum_password_score", @@ -415,6 +416,13 @@ "fieldtype": "Int", "label": "Run Jobs only Daily if Inactive For (Days)" }, + { + "default": "3", + "description": "Hourly rate limit for generating password reset links", + "fieldname": "password_reset_limit", + "fieldtype": "Int", + "label": "Password Reset Link Generation Limit" + }, { "default": "1", "fieldname": "logout_on_password_reset", diff --git a/frappe/core/doctype/user/test_user.py b/frappe/core/doctype/user/test_user.py index d4c0fa98ed..fb1fa4aff9 100644 --- a/frappe/core/doctype/user/test_user.py +++ b/frappe/core/doctype/user/test_user.py @@ -19,6 +19,7 @@ class TestUser(unittest.TestCase): # disable password strength test frappe.db.set_value("System Settings", "System Settings", "enable_password_policy", 0) frappe.db.set_value("System Settings", "System Settings", "minimum_password_score", "") + frappe.db.set_value("System Settings", "System Settings", "password_reset_limit", 3) def test_user_type(self): new_user = frappe.get_doc(dict(doctype='User', email='test-for-type@example.com', @@ -222,6 +223,19 @@ class TestUser(unittest.TestCase): self.assertEqual(extract_mentions(comment)[0], "test_user@example.com") self.assertEqual(extract_mentions(comment)[1], "test.again@example1.com") + def test_rate_limiting_for_reset_password(self): + from frappe.utils.password import delete_password_reset_cache + delete_password_reset_cache() + + frappe.db.set_value("System Settings", "System Settings", "password_reset_limit", 1) + + user = frappe.get_doc("User", "testperm@example.com") + link = user.reset_password() + self.assertRegex(link, "\/update-password\?key=[A-Za-z0-9]*") + + self.assertRaises(frappe.ValidationError, user.reset_password, False) + + def delete_contact(user): frappe.db.sql("DELETE FROM `tabContact` WHERE `email_id`= %s", user) frappe.db.sql("DELETE FROM `tabContact Email` WHERE `email_id`= %s", user) diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index ef2de679ae..2c5865fb69 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -13,15 +13,16 @@ from frappe.utils.user import get_system_managers from bs4 import BeautifulSoup import frappe.permissions import frappe.share -import re -import json from frappe.website.utils import is_signup_enabled from frappe.utils.background_jobs import enqueue STANDARD_USERS = ("Guest", "Administrator") -class MaxUsersReachedError(frappe.ValidationError): pass + +class MaxUsersReachedError(frappe.ValidationError): + pass + class User(Document): __new_password = None @@ -225,6 +226,11 @@ class User(Document): def reset_password(self, send_email=False, password_expired=False): from frappe.utils import random_string, get_url + rate_limit = frappe.db.get_single_value("System Settings", "password_reset_limit") + + if rate_limit: + check_password_reset_limit(self.name, rate_limit) + key = random_string(32) self.db_set("reset_password_key", key) @@ -236,6 +242,7 @@ class User(Document): if send_email: self.password_reset_mail(link) + update_password_reset_limit(self.name) return link def get_other_system_managers(self): @@ -1110,3 +1117,16 @@ def generate_keys(user): return {"api_secret": api_secret} frappe.throw(frappe._("Not Permitted"), frappe.PermissionError) + +def update_password_reset_limit(user): + generated_link_count = get_generated_link_count(user) + generated_link_count += 1 + frappe.cache().hset("password_reset_link_count", user, generated_link_count) + +def check_password_reset_limit(user, rate_limit): + generated_link_count = get_generated_link_count(user) + if generated_link_count >= rate_limit: + frappe.throw(_("You have reached the hourly limit for generating password reset links. Please try again later.")) + +def get_generated_link_count(user): + return cint(frappe.cache().hget("password_reset_link_count", user)) or 0 \ No newline at end of file diff --git a/frappe/core/doctype/user_permission/test_user_permission.py b/frappe/core/doctype/user_permission/test_user_permission.py index 5ccc8752cf..82dd2ab27e 100644 --- a/frappe/core/doctype/user_permission/test_user_permission.py +++ b/frappe/core/doctype/user_permission/test_user_permission.py @@ -26,8 +26,7 @@ class TestUserPermission(unittest.TestCase): user = create_user('test_user_perm1@example.com', 'Website Manager') for category in ['general', 'public']: if not frappe.db.exists('Blog Category', category): - frappe.get_doc({'doctype': 'Blog Category', - 'category_name': category, 'title': category}).insert() + frappe.get_doc({'doctype': 'Blog Category', 'title': category}).insert() param = get_params(user, 'Blog Category', 'general', is_default=1) add_user_permissions(param) diff --git a/frappe/custom/doctype/package_publish_tool/package_publish_tool.py b/frappe/custom/doctype/package_publish_tool/package_publish_tool.py index a01dd0ba47..b73f93a628 100644 --- a/frappe/custom/doctype/package_publish_tool/package_publish_tool.py +++ b/frappe/custom/doctype/package_publish_tool/package_publish_tool.py @@ -100,6 +100,7 @@ def export_package(): @frappe.whitelist() def import_package(package=None): """Import package from JSON.""" + frappe.only_for("System Manager") if isinstance(package, string_types): package = json.loads(package) diff --git a/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py b/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py index b346864f02..1cc54a0d1a 100644 --- a/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py +++ b/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py @@ -5,11 +5,12 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from frappe.utils.safe_exec import get_safe_globals class DataMigrationMapping(Document): def get_filters(self): if self.condition: - return frappe.safe_eval(self.condition, dict(frappe=frappe)) + return frappe.safe_eval(self.condition, get_safe_globals()) def get_fields(self): fields = [] @@ -63,7 +64,7 @@ def get_value_from_fieldname(field_map, fieldname_field, doc): field_name = get_source_value(field_map, fieldname_field) if field_name.startswith('eval:'): - value = frappe.safe_eval(field_name[5:], dict(frappe=frappe)) + value = frappe.safe_eval(field_name[5:], get_safe_globals()) elif field_name[0] in ('"', "'"): value = field_name[1:-1] else: diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 94a38a5304..72c4519120 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -40,7 +40,7 @@ class Workspace: self.doc = self.get_page_for_user() - if self.doc.module not in self.allowed_modules: + if self.doc.module and self.doc.module not in self.allowed_modules: raise frappe.PermissionError self.can_read = self.get_cached('user_perm_can_read', self.get_can_read_items) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 4ea61ec6a9..7e2d952928 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -60,11 +60,11 @@ def has_permission(doc, ptype, user): if doc.chart_type == 'Report': - allowed_reports = tuple([key.encode('UTF8') for key in get_allowed_reports()]) + allowed_reports = [key if type(key) == str else key.encode('UTF8') for key in get_allowed_reports()] if doc.report_name in allowed_reports: return True else: - allowed_doctypes = tuple(frappe.permissions.get_doctypes_with_read()) + allowed_doctypes = [frappe.permissions.get_doctypes_with_read()] if doc.document_type in allowed_doctypes: return True diff --git a/frappe/desk/doctype/desk_page/desk_page.py b/frappe/desk/doctype/desk_page/desk_page.py index cc2db53481..e92844ac0b 100644 --- a/frappe/desk/doctype/desk_page/desk_page.py +++ b/frappe/desk/doctype/desk_page/desk_page.py @@ -38,7 +38,7 @@ class DeskPage(Document): pages = frappe.get_all("Desk Page", fields=["name", "module"], filters=filters, as_list=1) - return { page[1]: page[0] for page in pages } + return { page[1]: page[0] for page in pages if page[1] } def disable_saving_as_standard(): return frappe.flags.in_install or \ diff --git a/frappe/desk/query_builder.py b/frappe/desk/query_builder.py deleted file mode 100644 index 81eba35e05..0000000000 --- a/frappe/desk/query_builder.py +++ /dev/null @@ -1,263 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe - -out = frappe.response - -from frappe.utils import cint -import frappe.defaults -from six import text_type - -def get_sql_tables(q): - if q.find('WHERE') != -1: - tl = q.split('FROM')[1].split('WHERE')[0].split(',') - elif q.find('GROUP BY') != -1: - tl = q.split('FROM')[1].split('GROUP BY')[0].split(',') - else: - tl = q.split('FROM')[1].split('ORDER BY')[0].split(',') - return [t.strip().strip('`')[3:] for t in tl] - -def get_parent_dt(dt): - pdt = '' - if frappe.db.sql('select name from `tabDocType` where istable=1 and name=%s', dt): - import frappe.model.meta - return frappe.model.meta.get_parent_dt(dt) - return pdt - -def get_sql_meta(tl): - std_columns = { - 'owner':('Owner', '', '', '100'), - 'creation':('Created on', 'Date', '', '100'), - 'modified':('Last modified on', 'Date', '', '100'), - 'modified_by':('Modified By', '', '', '100') - } - - meta = {} - - for dt in tl: - meta[dt] = std_columns.copy() - - # for table doctype, the ID is the parent id - pdt = get_parent_dt(dt) - if pdt: - meta[dt]['parent'] = ('ID', 'Link', pdt, '200') - - # get the field properties from DocField - res = frappe.db.sql("select fieldname, label, fieldtype, options, width \ - from tabDocField where parent=%s", dt) - for r in res: - if r[0]: - meta[dt][r[0]] = (r[1], r[2], r[3], r[4]); - - # name - meta[dt]['name'] = ('ID', 'Link', dt, '200') - - return meta - -def add_match_conditions(q, tl): - from frappe.desk.reportview import build_match_conditions - sl = [] - for dt in tl: - s = build_match_conditions(dt) - if s: - sl.append(s) - - # insert the conditions - if sl: - condition_st = q.find('WHERE')!=-1 and ' AND ' or ' WHERE ' - condition_end = q.find('ORDER BY')!=-1 and 'ORDER BY' or 'LIMIT' - condition_end = q.find('GROUP BY')!=-1 and 'GROUP BY' or condition_end - - if q.find('ORDER BY')!=-1 or q.find('LIMIT')!=-1 or q.find('GROUP BY')!=-1: # if query continues beyond conditions - q = q.split(condition_end) - q = q[0] + condition_st + '(' + ' OR '.join(sl) + ') ' + condition_end + q[1] - else: - q = q + condition_st + '(' + ' OR '.join(sl) + ')' - - return q - -def guess_type(m): - """ - Returns fieldtype depending on the MySQLdb Description - """ - if frappe.db.is_type_number(m): - return 'Currency' - elif m in frappe.is_type_datetime(m): - return 'Date' - else: - return 'Data' - -def build_description_simple(): - colnames, coltypes, coloptions, colwidths = [], [], [], [] - - for m in frappe.db.get_description(): - colnames.append(m[0]) - coltypes.append(guess_type[m[1]]) - coloptions.append('') - colwidths.append('100') - - return colnames, coltypes, coloptions, colwidths - -def build_description_standard(meta, tl): - - desc = frappe.db.get_description() - - colnames, coltypes, coloptions, colwidths = [], [], [], [] - - # merged metadata - used if we are unable to - # get both the table name and field name from - # the description - in case of joins - merged_meta = {} - for d in meta: - merged_meta.update(meta[d]) - - for f in desc: - fn, dt = f[0], '' - if '.' in fn: - dt, fn = fn.split('.') - - if (not dt) and merged_meta.get(fn): - # no "AS" given, find type from merged description - - desc = merged_meta[fn] - colnames.append(desc[0] or fn) - coltypes.append(desc[1] or '') - coloptions.append(desc[2] or '') - colwidths.append(desc[3] or '100') - - elif fn in meta.get(dt,{}): - # type specified for a multi-table join - # usually from Report Builder - - desc = meta[dt][fn] - colnames.append(desc[0] or fn) - coltypes.append(desc[1] or '') - coloptions.append(desc[2] or '') - colwidths.append(desc[3] or '100') - - else: - # nothing found - # guess - colnames.append(fn) - coltypes.append(guess_type(f[1])) - coloptions.append('') - colwidths.append('100') - - return colnames, coltypes, coloptions, colwidths - -@frappe.whitelist() -def runquery(q='', ret=0, from_export=0): - import frappe.utils - - formatted = cint(frappe.form_dict.get('formatted')) - - # CASE A: Simple Query - # -------------------- - if frappe.form_dict.get('simple_query') or frappe.form_dict.get('is_simple'): - if not q: q = frappe.form_dict.get('simple_query') or frappe.form_dict.get('query') - if q.split()[0].lower() != 'select': - raise Exception('Query must be a SELECT') - - as_dict = cint(frappe.form_dict.get('as_dict')) - res = frappe.db.sql(q, as_dict = as_dict, as_list = not as_dict, formatted=formatted) - - # build colnames etc from metadata - colnames, coltypes, coloptions, colwidths = [], [], [], [] - - # CASE B: Standard Query - # ----------------------- - else: - if not q: q = frappe.form_dict.get('query') - - tl = get_sql_tables(q) - meta = get_sql_meta(tl) - - q = add_match_conditions(q, tl) - - # replace special variables - q = q.replace('__user', frappe.session.user) - q = q.replace('__today', frappe.utils.nowdate()) - - res = frappe.db.sql(q, as_list=1, formatted=formatted) - - colnames, coltypes, coloptions, colwidths = build_description_standard(meta, tl) - - # run server script - # ----------------- - style, header_html, footer_html, page_template = '', '', '', '' - - out['colnames'] = colnames - out['coltypes'] = coltypes - out['coloptions'] = coloptions - out['colwidths'] = colwidths - out['header_html'] = header_html - out['footer_html'] = footer_html - out['page_template'] = page_template - - if style: - out['style'] = style - - # just the data - return - if ret==1: - return res - - out['values'] = res - - # return num of entries - qm = frappe.form_dict.get('query_max') or '' - if qm and qm.strip(): - if qm.split()[0].lower() != 'select': - raise Exception('Query (Max) must be a SELECT') - if not frappe.form_dict.get('simple_query'): - qm = add_match_conditions(qm, tl) - - out['n_values'] = frappe.utils.cint(frappe.db.sql(qm)[0][0]) - - -@frappe.whitelist() -def runquery_csv(): - global out - - q = frappe.form_dict.get('query') - - rep_name = frappe.form_dict.get('report_name') - if not frappe.form_dict.get('simple_query'): - - # Report Name - if not rep_name: - rep_name = get_sql_tables(q)[0] - - if not rep_name: rep_name = 'DataExport' - - rows = [[rep_name], out['colnames']] + out['values'] - - from six import StringIO - import csv - - f = StringIO() - writer = csv.writer(f) - for r in rows: - # encode only unicode type strings and not int, floats etc. - writer.writerow(map(lambda v: isinstance(v, text_type) and v.encode('utf-8') or v, r)) - - f.seek(0) - out['result'] = text_type(f.read(), 'utf-8') - out['type'] = 'csv' - out['doctype'] = rep_name - -def add_limit_to_query(query, args): - """ - Add limit condition to query - can be used by methods called in listing to add limit condition - """ - if args.get('limit_page_length'): - query += """ - limit %(limit_start)s, %(limit_page_length)s""" - - import frappe.utils - args['limit_start'] = frappe.utils.cint(args.get('limit_start')) - args['limit_page_length'] = frappe.utils.cint(args.get('limit_page_length')) - - return query, args diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index a1cfd02132..08ef7ae485 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -462,6 +462,9 @@ def add_total_row(result, columns, meta = None): @frappe.whitelist() def get_data_for_custom_field(doctype, field): + if not frappe.has_permission(doctype, "read"): + frappe.throw(_("Not Permitted"), frappe.PermissionError) + value_map = frappe._dict(frappe.get_all(doctype, fields=["name", field], as_list=1)) diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 6102be61ce..d4fc8833ae 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -36,6 +36,7 @@ def get_form_params(): data.pop('data', None) data.pop('ignore_permissions', None) data.pop('view', None) + data.pop('user', None) if "csrf_token" in data: del data["csrf_token"] diff --git a/frappe/desk/treeview.py b/frappe/desk/treeview.py index 2944f20a37..811143be03 100644 --- a/frappe/desk/treeview.py +++ b/frappe/desk/treeview.py @@ -6,12 +6,11 @@ import frappe from frappe import _ @frappe.whitelist() -def get_all_nodes(doctype, parent, tree_method, **filters): +def get_all_nodes(doctype, label, parent, tree_method, **filters): '''Recursively gets all data from tree nodes''' if 'cmd' in filters: del filters['cmd'] - filters.pop('data', None) tree_method = frappe.get_attr(tree_method) @@ -20,7 +19,7 @@ def get_all_nodes(doctype, parent, tree_method, **filters): frappe.throw(_("Not Permitted"), frappe.PermissionError) data = tree_method(doctype, parent, **filters) - out = [dict(parent=parent, data=data)] + out = [dict(parent=label, data=data)] if 'is_root' in filters: del filters['is_root'] diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index 0a0a13a6ce..a4d60706eb 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -85,12 +85,12 @@ class Newsletter(WebsiteGenerator): self.db_set("scheduled_to_send", len(self.recipients)) def get_message(self): - + return { 'Rich Text': self.message, 'Markdown': markdown(self.message_md), 'HTML': self.message_html - }[self.content_type] + }[self.content_type or 'Rich Text'] def get_recipients(self): """Get recipients from Email Group""" diff --git a/frappe/email/doctype/notification/notification.js b/frappe/email/doctype/notification/notification.js index 454514f922..2cc027acd6 100644 --- a/frappe/email/doctype/notification/notification.js +++ b/frappe/email/doctype/notification/notification.js @@ -19,9 +19,12 @@ frappe.notification = { } frappe.model.with_doctype(frm.doc.document_type, function() { - let get_select_options = function(df) { + let get_select_options = function(df, parent_field) { + // Append parent_field name along with fieldname for child table fields + let select_value = parent_field ? df.fieldname + ',' + parent_field : df.fieldname; + return { - value: df.fieldname, + value: select_value, label: df.fieldname + ' (' + __(df.label) + ')' }; }; @@ -59,9 +62,21 @@ frappe.notification = { let receiver_fields = []; if (frm.doc.channel === 'Email') { receiver_fields = $.map(fields, function(d) { - return d.options == 'Email' || - (d.options == 'User' && d.fieldtype == 'Link') - ? get_select_options(d) : null; + + // Add User and Email fields from child into select dropdown + if (d.fieldtype == 'Table') { + let child_fields = frappe.get_doc('DocType', d.options).fields; + return $.map(child_fields, function(df) { + return df.options == 'Email' || + (df.options == 'User' && df.fieldtype == 'Link') + ? get_select_options(df, d.fieldname) : null; + }); + // Add User and Email fields from parent into select dropdown + } else { + return d.options == 'Email' || + (d.options == 'User' && d.fieldtype == 'Link') + ? get_select_options(d) : null; + } }); } else if (in_list(['WhatsApp', 'SMS'], frm.doc.channel)) { receiver_fields = $.map(fields, function(d) { @@ -87,7 +102,7 @@ frappe.notification = {
Message Example
-Your {{ doc.name }} order of {{ doc.total }} has shipped and should be delivered on {{ doc.date }}. Details : {{doc.customer}}
+Your appointment is coming up on {{ doc.date }} at {{ doc.time }}
 
`; } else if (frm.doc.channel === 'Email') { template = `
Message Example
@@ -151,6 +166,7 @@ frappe.ui.form.on('Notification', { }, refresh: function(frm) { frappe.notification.setup_fieldname_select(frm); + frappe.notification.setup_example_message(frm); frm.get_field('is_standard').toggle(frappe.boot.developer_mode); frm.trigger('event'); }, diff --git a/frappe/email/doctype/notification/notification.json b/frappe/email/doctype/notification/notification.json index 95f218ad73..2a8ee1aeb1 100644 --- a/frappe/email/doctype/notification/notification.json +++ b/frappe/email/doctype/notification/notification.json @@ -34,6 +34,7 @@ "set_property_after_alert", "property_value", "column_break_5", + "send_to_all_assignees", "recipients", "message_sb", "message", @@ -66,7 +67,7 @@ }, { "depends_on": "eval:doc.channel=='Slack'", - "description": "To use Slack Channel, add a Slack Webhook URL.", + "description": "To use Slack Channel, add a Slack Webhook URL.", "fieldname": "slack_webhook_url", "fieldtype": "Link", "label": "Slack Channel", @@ -216,7 +217,7 @@ "fieldname": "recipients", "fieldtype": "Table", "label": "Recipients", - "mandatory_depends_on": "eval:doc.channel!=='Slack'", + "mandatory_depends_on": "eval:doc.channel!=='Slack' && !doc.send_to_all_assignees", "options": "Notification Recipient" }, { @@ -268,6 +269,7 @@ "fieldname": "twilio_number", "fieldtype": "Link", "label": "Twilio Number", + "mandatory_depends_on": "eval: doc.channel==='WhatsApp'", "options": "Twilio Number Group" }, { @@ -277,11 +279,19 @@ "fieldname": "send_system_notification", "fieldtype": "Check", "label": "Send System Notification" + }, + { + "default": "0", + "depends_on": "eval:doc.channel == 'Email'", + "fieldname": "send_to_all_assignees", + "fieldtype": "Check", + "label": "Send To All Assignees" } ], "icon": "fa fa-envelope", + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-08-11 19:24:35.479373", + "modified": "2020-09-03 10:33:23.084590", "modified_by": "Administrator", "module": "Email", "name": "Notification", diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 2ec208c89d..9a40fb02b7 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -10,6 +10,7 @@ from frappe.model.document import Document from frappe.core.doctype.role.role import get_info_based_on_role, get_user_info from frappe.utils import validate_email_address, nowdate, parse_val, is_html, add_to_date from frappe.utils.jinja import validate_template +from frappe.utils.safe_exec import get_safe_globals from frappe.modules.utils import export_module_json, get_doc_module from six import string_types from frappe.integrations.doctype.slack_webhook_url.slack_webhook_url import send_slack_message @@ -42,6 +43,7 @@ class Notification(Document): self.validate_forbidden_types() self.validate_condition() self.validate_standard() + self.validate_twilio_settings() frappe.cache().hdel('notifications', self.document_type) def on_update(self): @@ -68,6 +70,11 @@ def get_context(context): if self.is_standard and not frappe.conf.developer_mode: frappe.throw(_('Cannot edit Standard Notification. To edit, please disable this and duplicate it')) + def validate_twilio_settings(self): + if self.enabled and self.channel == "WhatsApp" \ + and not frappe.db.get_single_value("Twilio Settings", "enabled"): + frappe.throw(_("Please enable Twilio settings to send WhatsApp messages")) + def validate_condition(self): temp_doc = frappe.new_doc(self.document_type) if self.condition: @@ -166,8 +173,13 @@ def get_context(context): subject = frappe.render_template(self.subject, context) attachments = self.get_attachment(doc) + recipients, cc, bcc = self.get_list_of_recipients(doc, context) + users = recipients + cc + bcc + + if not users: + return notification_doc = { 'type': 'Alert', @@ -189,6 +201,7 @@ def get_context(context): recipients, cc, bcc = self.get_list_of_recipients(doc, context) if not (recipients or cc or bcc): return + sender = None if self.sender and self.sender_email: sender = formataddr((self.sender, self.sender_email)) @@ -234,13 +247,20 @@ def get_context(context): if not frappe.safe_eval(recipient.condition, None, context): continue if recipient.receiver_by_document_field: - email_ids_value = doc.get(recipient.receiver_by_document_field) - if validate_email_address(email_ids_value): - email_ids = email_ids_value.replace(",", "\n") - recipients = recipients + email_ids.split("\n") + fields = recipient.receiver_by_document_field.split(',') + # fields from child table + if len(fields) > 1: + for d in doc.get(fields[1]): + email_id = d.get(fields[0]) + if validate_email_address(email_id): + recipients.append(email_id) + # field from parent doc + else: + email_ids_value = doc.get(fields[0]) + if validate_email_address(email_ids_value): + email_ids = email_ids_value.replace(",", "\n") + recipients = recipients + email_ids.split("\n") - # else: - # print "invalid email" if recipient.cc and "{" in recipient.cc: recipient.cc = frappe.render_template(recipient.cc, context) @@ -262,8 +282,9 @@ def get_context(context): for email in emails: recipients = recipients + email.split("\n") - if not recipients and not cc and not bcc: - return None, None, None + if self.send_to_all_assignees: + recipients = recipients + get_assignees(doc) + return list(set(recipients)), list(set(cc)), list(set(bcc)) def get_receiver_list(self, doc, context): @@ -404,4 +425,13 @@ def evaluate_alert(doc, alert, event): frappe.utils.get_link_to_form('Error Log', error_log.name))) def get_context(doc): - return {"doc": doc, "nowdate": nowdate, "frappe": frappe._dict(utils=frappe.utils)} + return {"doc": doc, "nowdate": nowdate, "frappe": frappe._dict(utils=get_safe_globals().get("frappe").get("utils"))} + +def get_assignees(doc): + assignees = [] + assignees = frappe.get_all('ToDo', filters={'status': 'Open', 'reference_name': doc.name, + 'reference_type': doc.doctype}, fields=['owner']) + + recipients = [d.owner for d in assignees] + + return recipients diff --git a/frappe/email/doctype/notification/test_notification.py b/frappe/email/doctype/notification/test_notification.py index 9bdf09375d..45a1587c1a 100644 --- a/frappe/email/doctype/notification/test_notification.py +++ b/frappe/email/doctype/notification/test_notification.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe, frappe.utils, frappe.utils.scheduler +from frappe.desk.form import assign_to import unittest test_records = frappe.get_test_records('Notification') @@ -13,7 +14,31 @@ test_dependencies = ["User"] class TestNotification(unittest.TestCase): def setUp(self): frappe.db.sql("""delete from `tabEmail Queue`""") - frappe.set_user("test1@example.com") + frappe.set_user("test@example.com") + + if not frappe.db.exists('Notification', {'name': 'ToDo Status Update'}, 'name'): + notification = frappe.new_doc('Notification') + notification.name = 'ToDo Status Update' + notification.subject = 'ToDo Status Update' + notification.document_type = 'ToDo' + notification.event = 'Value Change' + notification.value_changed = 'status' + notification.send_to_all_assignees = 1 + notification.save() + + if not frappe.db.exists('Notification', {'name': 'Contact Status Update'}, 'name'): + notification = frappe.new_doc('Notification') + notification.name = 'Contact Status Update' + notification.subject = 'Contact Status Update' + notification.document_type = 'Contact' + notification.event = 'Value Change' + notification.value_changed = 'status' + notification.message = 'Test Contact Update' + notification.append('recipients', { + 'receiver_by_document_field': 'email_id,email_ids' + }) + notification.save() + def tearDown(self): frappe.set_user("Administrator") @@ -177,3 +202,65 @@ class TestNotification(unittest.TestCase): frappe.db.sql("""delete from `tabUser` where email='test_jinja@example.com'""") frappe.db.sql("""delete from `tabEmail Queue`""") frappe.db.sql("""delete from `tabEmail Queue Recipient`""") + + def test_notification_to_assignee(self): + todo = frappe.new_doc('ToDo') + todo.description = 'Test Notification' + todo.save() + + assign_to.add({ + "assign_to": ["test2@example.com"], + "doctype": todo.doctype, + "name": todo.name, + "description": "Close this Todo" + }) + + assign_to.add({ + "assign_to": ["test1@example.com"], + "doctype": todo.doctype, + "name": todo.name, + "description": "Close this Todo" + }) + + #change status of todo + todo.status = 'Closed' + todo.save() + + email_queue = frappe.get_doc('Email Queue', {'reference_doctype': 'ToDo', + 'reference_name': todo.name}) + + self.assertTrue(email_queue) + + recipients = [d.recipient for d in email_queue.recipients] + self.assertTrue('test2@example.com' in recipients) + self.assertTrue('test1@example.com' in recipients) + + def test_notification_by_child_table_field(self): + contact = frappe.new_doc('Contact') + contact.first_name = 'John Doe' + contact.status = 'Open' + contact.append('email_ids', { + 'email_id': 'test2@example.com', + 'is_primary': 1 + }) + + contact.append('email_ids', { + 'email_id': 'test1@example.com' + }) + + contact.save() + + #change status of contact + contact.status = 'Replied' + contact.save() + + email_queue = frappe.get_doc('Email Queue', {'reference_doctype': 'Contact', + 'reference_name': contact.name}) + + self.assertTrue(email_queue) + + recipients = [d.recipient for d in email_queue.recipients] + self.assertTrue('test2@example.com' in recipients) + self.assertTrue('test1@example.com' in recipients) + + diff --git a/frappe/email/doctype/notification_recipient/notification_recipient.json b/frappe/email/doctype/notification_recipient/notification_recipient.json index 201899cd57..0670320a77 100644 --- a/frappe/email/doctype/notification_recipient/notification_recipient.json +++ b/frappe/email/doctype/notification_recipient/notification_recipient.json @@ -46,9 +46,10 @@ "options": "Role" } ], + "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-02-21 11:18:40.125233", + "modified": "2020-09-01 17:40:27.289105", "modified_by": "Administrator", "module": "Email", "name": "Notification Recipient", diff --git a/frappe/email/email_body.py b/frappe/email/email_body.py index d545190c47..5bb654abf3 100755 --- a/frappe/email/email_body.py +++ b/frappe/email/email_body.py @@ -207,7 +207,7 @@ class EMail: def set_in_reply_to(self, in_reply_to): """Used to send the Message-Id of a received email back as In-Reply-To""" - self.msg_root["In-Reply-To"] = in_reply_to + self.set_header('In-Reply-To', in_reply_to) def make(self): """build into msg_root""" @@ -234,7 +234,10 @@ class EMail: if key in self.msg_root: del self.msg_root[key] - self.msg_root[key] = value + try: + self.msg_root[key] = value + except ValueError: + self.msg_root[key] = sanitize_email_header(value) def as_string(self): """validate, build message and convert to string""" @@ -458,3 +461,6 @@ def get_header(header=None): }) return email_header + +def sanitize_email_header(str): + return str.replace('\r', '').replace('\n', '') diff --git a/frappe/event_streaming/doctype/document_type_mapping/document_type_mapping.py b/frappe/event_streaming/doctype/document_type_mapping/document_type_mapping.py index 8ace4f57d3..bf96e4e27b 100644 --- a/frappe/event_streaming/doctype/document_type_mapping/document_type_mapping.py +++ b/frappe/event_streaming/doctype/document_type_mapping/document_type_mapping.py @@ -102,7 +102,7 @@ class DocumentTypeMapping(Document): filters = json.loads(mapping.remote_value_filters) for key, value in iteritems(filters): if value.startswith('eval:'): - val = frappe.safe_eval(value[5:], dict(frappe=frappe)) + val = frappe.safe_eval(value[5:], None, dict(doc=doc)) filters[key] = val if doc.get(value): filters[key] = doc.get(value) diff --git a/frappe/event_streaming/doctype/event_consumer/event_consumer.json b/frappe/event_streaming/doctype/event_consumer/event_consumer.json index d863677e03..42b47ce949 100644 --- a/frappe/event_streaming/doctype/event_consumer/event_consumer.json +++ b/frappe/event_streaming/doctype/event_consumer/event_consumer.json @@ -13,8 +13,7 @@ "api_secret", "column_break_6", "user", - "incoming_change", - "in_test" + "incoming_change" ], "fields": [ { @@ -22,6 +21,7 @@ "fieldtype": "Data", "in_list_view": 1, "label": "Callback URL", + "read_only": 1, "reqd": 1, "unique": 1 }, @@ -29,19 +29,20 @@ "fieldname": "api_key", "fieldtype": "Data", "label": "API Key", - "read_only": 1 + "reqd": 1 }, { "fieldname": "api_secret", "fieldtype": "Password", "label": "API Secret", - "read_only": 1 + "reqd": 1 }, { "fieldname": "user", "fieldtype": "Link", "label": "Event Subscriber", "options": "User", + "read_only": 1, "reqd": 1 }, { @@ -60,14 +61,6 @@ "label": "Incoming Change", "read_only": 1 }, - { - "default": "0", - "fieldname": "in_test", - "fieldtype": "Check", - "hidden": 1, - "label": "In Test", - "read_only": 1 - }, { "fieldname": "consumer_doctypes", "fieldtype": "Table", @@ -78,7 +71,7 @@ ], "in_create": 1, "links": [], - "modified": "2019-12-30 11:52:16.276047", + "modified": "2020-09-08 16:42:39.828085", "modified_by": "Administrator", "module": "Event Streaming", "name": "Event Consumer", diff --git a/frappe/event_streaming/doctype/event_consumer/event_consumer.py b/frappe/event_streaming/doctype/event_consumer/event_consumer.py index a53d046be5..1505c3a05d 100644 --- a/frappe/event_streaming/doctype/event_consumer/event_consumer.py +++ b/frappe/event_streaming/doctype/event_consumer/event_consumer.py @@ -6,6 +6,8 @@ from __future__ import unicode_literals import frappe import json import requests +import os +from frappe import _ from frappe.model.document import Document from frappe.frappeclient import FrappeClient from frappe.utils.data import get_url @@ -14,13 +16,18 @@ from frappe.utils.background_jobs import get_jobs class EventConsumer(Document): def validate(self): - if self.in_test: + # approve subscribed doctypes for tests + # frappe.flags.in_test won't work here as tests are running on the consumer site + if os.environ.get('CI'): for entry in self.consumer_doctypes: entry.status = 'Approved' - self.in_test = False def on_update(self): if not self.incoming_change: + doc_before_save = self.get_doc_before_save() + if doc_before_save.api_key != self.api_key or doc_before_save.api_secret != self.api_secret: + return + self.update_consumer_status() else: frappe.db.set_value(self.doctype, self.name, 'incoming_change', 0) @@ -56,17 +63,26 @@ class EventConsumer(Document): return 'offline' return 'online' - -@frappe.whitelist(allow_guest=True) +@frappe.whitelist() def register_consumer(data): """create an event consumer document for registering a consumer""" data = json.loads(data) # to ensure that consumer is created only once if frappe.db.exists('Event Consumer', data['event_consumer']): return None + + user = data['user'] + if not frappe.db.exists('User', user): + frappe.throw(_('User {0} not found on the producer site').format(user)) + + if "System Manager" not in frappe.get_roles(user): + frappe.throw(_("Event Subscriber has to be a System Manager.")) + consumer = frappe.new_doc('Event Consumer') consumer.callback_url = data['event_consumer'] consumer.user = data['user'] + consumer.api_key = data['api_key'] + consumer.api_secret = data['api_secret'] consumer.incoming_change = True consumer_doctypes = json.loads(data['consumer_doctypes']) @@ -76,19 +92,13 @@ def register_consumer(data): 'status': 'Pending' }) - api_key = frappe.generate_hash(length=10) - api_secret = frappe.generate_hash(length=10) - consumer.api_key = api_key - consumer.api_secret = api_secret - consumer.in_test = data['in_test'] - consumer.insert(ignore_permissions=True) - frappe.db.commit() + consumer.insert() # consumer's 'last_update' field should point to the latest update # in producer's update log when subscribing # so that, updates after subscribing are consumed and not the old ones. last_update = str(get_last_update()) - return json.dumps({'api_key': api_key, 'api_secret': api_secret, 'last_update': last_update}) + return json.dumps({'last_update': last_update}) def get_consumer_site(consumer_url): @@ -97,8 +107,7 @@ def get_consumer_site(consumer_url): consumer_site = FrappeClient( url=consumer_url, api_key=consumer_doc.api_key, - api_secret=consumer_doc.get_password('api_secret'), - frappe_authorization_source='Event Producer' + api_secret=consumer_doc.get_password('api_secret') ) return consumer_site diff --git a/frappe/event_streaming/doctype/event_producer/event_producer.json b/frappe/event_streaming/doctype/event_producer/event_producer.json index 8eba1924f5..8fafdc3bb2 100644 --- a/frappe/event_streaming/doctype/event_producer/event_producer.json +++ b/frappe/event_streaming/doctype/event_producer/event_producer.json @@ -32,23 +32,26 @@ "read_only": 1 }, { + "description": "API Key of the user(Event Subscriber) on the producer site", "fieldname": "api_key", "fieldtype": "Data", "label": "API Key", - "read_only": 1 + "reqd": 1 }, { + "description": "API Secret of the user(Event Subscriber) on the producer site", "fieldname": "api_secret", "fieldtype": "Password", "label": "API Secret", - "read_only": 1 + "reqd": 1 }, { "fieldname": "user", "fieldtype": "Link", "label": "Event Subscriber", "options": "User", - "reqd": 1 + "reqd": 1, + "set_only_once": 1 }, { "fieldname": "column_break_6", @@ -74,7 +77,7 @@ } ], "links": [], - "modified": "2019-12-26 13:04:11.438349", + "modified": "2020-09-08 18:50:57.687979", "modified_by": "Administrator", "module": "Event Streaming", "name": "Event Producer", diff --git a/frappe/event_streaming/doctype/event_producer/event_producer.py b/frappe/event_streaming/doctype/event_producer/event_producer.py index 73aea114ab..b0ec998ab9 100644 --- a/frappe/event_streaming/doctype/event_producer/event_producer.py +++ b/frappe/event_streaming/doctype/event_producer/event_producer.py @@ -12,7 +12,8 @@ from frappe import _ from frappe.model.document import Document from frappe.frappeclient import FrappeClient from frappe.utils.background_jobs import get_jobs -from frappe.utils.data import get_url +from frappe.utils.data import get_url, get_link_to_form +from frappe.utils.password import get_decrypted_password from frappe.custom.doctype.custom_field.custom_field import create_custom_field from frappe.integrations.oauth2 import validate_url @@ -20,19 +21,35 @@ from frappe.integrations.oauth2 import validate_url class EventProducer(Document): def before_insert(self): self.check_url() + self.validate_event_subscriber() self.incoming_change = True self.create_event_consumer() self.create_custom_fields() def validate(self): + self.validate_event_subscriber() if frappe.flags.in_test: for entry in self.producer_doctypes: entry.status = 'Approved' + def validate_event_subscriber(self): + if not frappe.db.get_value('User', self.user, 'api_key'): + frappe.throw(_('Please generate keys for the Event Subscriber User {0} first.').format( + frappe.bold(get_link_to_form('User', self.user)) + )) + def on_update(self): if not self.incoming_change: - self.update_event_consumer() - self.create_custom_fields() + if frappe.db.exists('Event Producer', self.name): + if not self.api_key or not self.api_secret: + frappe.throw(_('Please set API Key and Secret on the producer and consumer sites first.')) + else: + doc_before_save = self.get_doc_before_save() + if doc_before_save.api_key != self.api_key or doc_before_save.api_secret != self.api_secret: + return + + self.update_event_consumer() + self.create_custom_fields() else: # when producer doc is updated it updates the consumer doc, set flag to avoid deadlock self.db_set('incoming_change', 0) @@ -50,15 +67,18 @@ class EventProducer(Document): def create_event_consumer(self): """register event consumer on the producer site""" if self.is_producer_online(): - producer_site = FrappeClient(self.producer_url, verify=False) + producer_site = FrappeClient( + url=self.producer_url, + api_key=self.api_key, + api_secret=self.get_password('api_secret') + ) + response = producer_site.post_api( 'frappe.event_streaming.doctype.event_consumer.event_consumer.register_consumer', params={'data': json.dumps(self.get_request_data())} ) if response: response = json.loads(response) - self.api_key = response['api_key'] - self.api_secret = response['api_secret'] self.last_update = response['last_update'] else: frappe.throw(_('Failed to create an Event Consumer or an Event Consumer for the current site is already registered.')) @@ -72,11 +92,14 @@ class EventProducer(Document): else: consumer_doctypes.append(entry.ref_doctype) + user_key = frappe.db.get_value('User', self.user, 'api_key') + user_secret = get_decrypted_password('User', self.user, 'api_secret') return { 'event_consumer': get_url(), 'consumer_doctypes': json.dumps(consumer_doctypes), 'user': self.user, - 'in_test': frappe.flags.in_test + 'api_key': user_key, + 'api_secret': user_secret } def create_custom_fields(self): @@ -110,8 +133,6 @@ class EventProducer(Document): 'status': get_approval_status(config, ref_doctype), 'unsubscribed': entry.unsubscribe }) - if frappe.flags.in_test: - event_consumer.in_test = True event_consumer.user = self.user event_consumer.incoming_change = True producer_site.update(event_consumer) @@ -134,8 +155,7 @@ def get_producer_site(producer_url): producer_site = FrappeClient( url=producer_url, api_key=producer_doc.api_key, - api_secret=producer_doc.get_password('api_secret'), - frappe_authorization_source='Event Consumer' + api_secret=producer_doc.get_password('api_secret') ) return producer_site diff --git a/frappe/event_streaming/doctype/event_producer/test_event_producer.py b/frappe/event_streaming/doctype/event_producer/test_event_producer.py index 4fea55eb39..fa2461a9d8 100644 --- a/frappe/event_streaming/doctype/event_producer/test_event_producer.py +++ b/frappe/event_streaming/doctype/event_producer/test_event_producer.py @@ -8,6 +8,7 @@ import unittest import json from frappe.frappeclient import FrappeClient from frappe.event_streaming.doctype.event_producer.event_producer import pull_from_node +from frappe.core.doctype.user.user import generate_keys producer_url = 'http://test_site_producer:8000' @@ -166,16 +167,6 @@ class TestEventProducer(unittest.TestCase): def pull_producer_data(self): pull_from_node(producer_url) - def get_remote_site(self): - producer_doc = frappe.get_doc('Event Producer', producer_url) - producer_site = FrappeClient( - url=producer_doc.producer_url, - api_key=producer_doc.api_key, - api_secret=producer_doc.get_password('api_secret'), - frappe_authorization_source='Event Consumer' - ) - return producer_site - def test_mapping(self): producer = get_remote_site() event_producer = frappe.get_doc('Event Producer', producer_url, for_update=True) @@ -298,6 +289,20 @@ def create_event_producer(producer_url): event_producer.save() return + generate_keys('Administrator') + + producer_site = connect() + + response = producer_site.post_api( + 'frappe.core.doctype.user.user.generate_keys', + params={'user': 'Administrator'} + ) + + api_secret = response.get('api_secret') + + response = producer_site.get_value('User', 'api_key', {'name': 'Administrator'}) + api_key = response.get('api_key') + event_producer = frappe.new_doc('Event Producer') event_producer.producer_doctypes = [] event_producer.producer_url = producer_url @@ -310,6 +315,8 @@ def create_event_producer(producer_url): 'use_same_name': 1 }) event_producer.user = 'Administrator' + event_producer.api_key = api_key + event_producer.api_secret = api_secret event_producer.save() def reset_configuration(producer_url): @@ -331,9 +338,9 @@ def get_remote_site(): producer_doc = frappe.get_doc('Event Producer', producer_url) producer_site = FrappeClient( url=producer_doc.producer_url, - api_key=producer_doc.api_key, - api_secret=producer_doc.get_password('api_secret'), - frappe_authorization_source='Event Consumer' + username='Administrator', + password='admin', + verify=False ) return producer_site @@ -341,4 +348,17 @@ def unsubscribe_doctypes(producer_url): event_producer = frappe.get_doc('Event Producer', producer_url) for entry in event_producer.producer_doctypes: entry.unsubscribe = 1 - event_producer.save() \ No newline at end of file + event_producer.save() + +def connect(): + def _connect(): + return FrappeClient( + url=producer_url, + username='Administrator', + password='admin', + verify=False + ) + try: + return _connect() + except Exception: + return _connect() diff --git a/frappe/handler.py b/frappe/handler.py index e5a7f742ae..cac9c3a460 100755 --- a/frappe/handler.py +++ b/frappe/handler.py @@ -65,16 +65,21 @@ def execute_cmd(cmd, from_async=False): method = method.queue is_whitelisted(method) + is_valid_http_method(method) return frappe.call(method, **frappe.form_dict) +def is_valid_http_method(method): + http_method = frappe.local.request.method + + if http_method not in frappe.allowed_http_methods_for_whitelisted_func[method]: + frappe.throw(_("Not permitted"), frappe.PermissionError) def is_whitelisted(method): # check if whitelisted if frappe.session['user'] == 'Guest': if (method not in frappe.guest_methods): - frappe.msgprint(_("Not permitted")) - raise frappe.PermissionError('Not Allowed, {0}'.format(method)) + frappe.throw(_("Not permitted"), frappe.PermissionError) if method not in frappe.xss_safe_methods: # strictly sanitize form_dict @@ -85,8 +90,7 @@ def is_whitelisted(method): else: if not method in frappe.whitelisted: - frappe.msgprint(_("Not permitted")) - raise frappe.PermissionError('Not Allowed, {0}'.format(method)) + frappe.throw(_("Not permitted"), frappe.PermissionError) @frappe.whitelist(allow_guest=True) def version(): diff --git a/frappe/hooks.py b/frappe/hooks.py index 8e16261faa..81cfb5af1a 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -201,7 +201,8 @@ scheduler_events = { "frappe.deferred_insert.save_to_db", "frappe.desk.form.document_follow.send_hourly_updates", "frappe.integrations.doctype.google_calendar.google_calendar.sync", - "frappe.email.doctype.newsletter.newsletter.send_scheduled_email" + "frappe.email.doctype.newsletter.newsletter.send_scheduled_email", + "frappe.utils.password.delete_password_reset_cache" ], "daily": [ "frappe.email.queue.clear_outbox", diff --git a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py index 864720174f..6b95a3f5bf 100644 --- a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py +++ b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py @@ -97,7 +97,7 @@ def backup_to_dropbox(upload_db_backup=True): if frappe.flags.create_new_backup: backup = new_backup(ignore_files=True) filename = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_db)) - site_config = os.path.join(get_backups_path(), os.path.basename(backup.site_config_backup_path)) + site_config = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_conf)) else: filename, site_config = get_latest_backup_file() diff --git a/frappe/integrations/doctype/google_drive/google_drive.py b/frappe/integrations/doctype/google_drive/google_drive.py index 58f28f882a..c1c73d7726 100644 --- a/frappe/integrations/doctype/google_drive/google_drive.py +++ b/frappe/integrations/doctype/google_drive/google_drive.py @@ -191,7 +191,7 @@ def upload_system_backup_to_google_drive(): backup = new_backup() file_urls = [] file_urls.append(backup.backup_path_db) - file_urls.append(backup.site_config_backup_path) + file_urls.append(backup.backup_path_conf) if account.file_backup: file_urls.append(backup.backup_path_files) diff --git a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py index 1d2f7f9495..af7686c9b0 100644 --- a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py +++ b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py @@ -65,6 +65,7 @@ import frappe from frappe import _ import json import hmac +import razorpay import hashlib from six.moves.urllib.parse import urlencode from frappe.model.document import Document @@ -75,6 +76,11 @@ from frappe.integrations.utils import (make_get_request, make_post_request, crea class RazorpaySettings(Document): supported_currencies = ["INR"] + def init_client(self): + if self.api_key: + secret = self.get_password(fieldname="api_secret", raise_exception=False) + self.client = razorpay.Client(auth=(self.api_key, secret)) + def validate(self): create_payment_gateway('Razorpay') call_hook_method('payment_gateway_enabled', gateway='Razorpay') diff --git a/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py index c59b0ddd5b..7c90d37f82 100755 --- a/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py +++ b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py @@ -118,7 +118,7 @@ def backup_to_s3(): backup = new_backup(ignore_files=False, backup_path_db=None, backup_path_files=None, backup_path_private_files=None, force=True) db_filename = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_db)) - site_config = os.path.join(get_backups_path(), os.path.basename(backup.site_config_backup_path)) + site_config = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_conf)) if backup_files: files_filename = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_files)) private_files = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_private_files)) diff --git a/frappe/integrations/doctype/twilio_settings/twilio_settings.json b/frappe/integrations/doctype/twilio_settings/twilio_settings.json index e54500fd5d..9eb2c0c512 100644 --- a/frappe/integrations/doctype/twilio_settings/twilio_settings.json +++ b/frappe/integrations/doctype/twilio_settings/twilio_settings.json @@ -5,6 +5,7 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "enabled", "account_sid", "auth_token", "column_break_2", @@ -14,12 +15,14 @@ { "fieldname": "account_sid", "fieldtype": "Data", - "label": "Account SID" + "label": "Account SID", + "mandatory_depends_on": "eval: doc.enabled" }, { "fieldname": "auth_token", "fieldtype": "Password", - "label": "Auth Token" + "label": "Auth Token", + "mandatory_depends_on": "eval: doc.enabled" }, { "fieldname": "column_break_2", @@ -30,11 +33,18 @@ "fieldtype": "Table", "label": "Twilio Number", "options": "Twilio Number Group" + }, + { + "default": "0", + "fieldname": "enabled", + "fieldtype": "Check", + "label": "Enabled" } ], + "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-08-11 15:28:57.860554", + "modified": "2020-09-03 10:17:21.318743", "modified_by": "Administrator", "module": "Integrations", "name": "Twilio Settings", diff --git a/frappe/integrations/doctype/twilio_settings/twilio_settings.py b/frappe/integrations/doctype/twilio_settings/twilio_settings.py index 6c698d719a..b8f991e829 100644 --- a/frappe/integrations/doctype/twilio_settings/twilio_settings.py +++ b/frappe/integrations/doctype/twilio_settings/twilio_settings.py @@ -5,14 +5,16 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document -from twilio.rest import Client from frappe import _ from frappe.utils.password import get_decrypted_password +from twilio.rest import Client from six import string_types +from json import loads class TwilioSettings(Document): - def validate(self): - self.validate_twilio_credentials() + def on_update(self): + if self.enabled: + self.validate_twilio_credentials() def validate_twilio_credentials(self): try: @@ -23,14 +25,15 @@ class TwilioSettings(Document): frappe.throw(_("Invalid Account SID or Auth Token.")) def send_whatsapp_message(sender, receiver_list, message): - import json + twilio_settings = frappe.get_doc("Twilio Settings") + if not twilio_settings.enabled: + frappe.throw(_("Please enable twilio settings before sending WhatsApp messages")) + if isinstance(receiver_list, string_types): - receiver_list = json.loads(receiver_list) + receiver_list = loads(receiver_list) if not isinstance(receiver_list, list): receiver_list = [receiver_list] - - twilio_settings = frappe.get_doc("Twilio Settings") auth_token = get_decrypted_password("Twilio Settings", "Twilio Settings", 'auth_token') client = Client(twilio_settings.account_sid, auth_token) args = { diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py index 8e6c8d58e4..f1556aa661 100644 --- a/frappe/integrations/doctype/webhook/webhook.py +++ b/frappe/integrations/doctype/webhook/webhook.py @@ -18,6 +18,7 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils.jinja import validate_template +from frappe.utils.safe_exec import get_safe_globals WEBHOOK_SECRET_HEADER = "X-Frappe-Webhook-Signature" @@ -75,8 +76,7 @@ class Webhook(Document): def get_context(doc): - return {"doc": doc, "utils": frappe.utils} - + return {'doc': doc, 'utils': get_safe_globals().get('frappe').get('utils')} def enqueue_webhook(doc, webhook): webhook = frappe.get_doc("Webhook", webhook.get("name")) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index ac87b1d907..018f1afcd7 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -391,7 +391,10 @@ class DatabaseQuery(object): ref_doctype = frappe.get_meta(f.doctype).get_field(f.fieldname).options result=[] - lft, rgt = frappe.db.get_value(ref_doctype, f.value, ["lft", "rgt"]) + + lft, rgt = '', '' + if f.value: + lft, rgt = frappe.db.get_value(ref_doctype, f.value, ["lft", "rgt"]) # Get descendants elements of a DocType with a tree structure if f.operator.lower() in ('descendants of', 'not descendants of') : @@ -769,6 +772,7 @@ def get_list(doctype, *args, **kwargs): kwargs.pop('ignore_permissions', None) kwargs.pop('data', None) kwargs.pop('strict', None) + kwargs.pop('user', None) # If doctype is child table if frappe.is_table(doctype): diff --git a/frappe/model/document.py b/frappe/model/document.py index 2b171547d1..53fcd99f78 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -905,9 +905,9 @@ class Document(BaseDocument): """Cancel the document. Sets `docstatus` = 2, then saves.""" self._cancel() - def delete(self): + def delete(self, ignore_permissions=False): """Delete document.""" - frappe.delete_doc(self.doctype, self.name, flags=self.flags) + frappe.delete_doc(self.doctype, self.name, ignore_permissions = ignore_permissions, flags=self.flags) def run_before_save_methods(self): """Run standard methods before `INSERT` or `UPDATE`. Standard Methods are: diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index 1e3f127b99..7a2129e76e 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -25,7 +25,6 @@ def update_document_title(doctype, docname, title_field=None, old_title=None, ne return docname -@frappe.whitelist() def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=False, ignore_if_exists=False, show_alert=True): """ Renames a doc(dt, old) to doc(dt, new) and diff --git a/frappe/patches.txt b/frappe/patches.txt index 1042ba933a..b08a4e891b 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -305,6 +305,9 @@ frappe.patches.v12_0.fix_email_id_formatting frappe.patches.v13_0.add_toggle_width_in_navbar_settings frappe.patches.v13_0.rename_notification_fields frappe.patches.v13_0.remove_duplicate_navbar_items +frappe.patches.v12_0.set_default_password_reset_limit +frappe.patches.v13_0.set_route_for_blog_category frappe.patches.v13_0.enable_custom_script frappe.patches.v13_0.update_newsletter_content_type execute:frappe.db.set_value('Website Settings', 'Website Settings', {'navbar_template': 'Standard Navbar', 'footer_template': 'Standard Footer'}) +frappe.patches.v13_0.delete_event_producer_and_consumer_keys diff --git a/frappe/patches/v12_0/set_default_password_reset_limit.py b/frappe/patches/v12_0/set_default_password_reset_limit.py new file mode 100644 index 0000000000..188f2383e7 --- /dev/null +++ b/frappe/patches/v12_0/set_default_password_reset_limit.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +import frappe + + +def execute(): + frappe.reload_doc("core", "doctype", "system_settings", force=1) + frappe.db.set_value('System Settings', None, "password_reset_limit", 3) diff --git a/frappe/patches/v13_0/delete_event_producer_and_consumer_keys.py b/frappe/patches/v13_0/delete_event_producer_and_consumer_keys.py new file mode 100644 index 0000000000..1eba5871c2 --- /dev/null +++ b/frappe/patches/v13_0/delete_event_producer_and_consumer_keys.py @@ -0,0 +1,11 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + if frappe.db.exists("DocType", "Event Producer"): + frappe.db.sql("""UPDATE `tabEvent Producer` SET api_key='', api_secret=''""") + if frappe.db.exists("DocType", "Event Consumer"): + frappe.db.sql("""UPDATE `tabEvent Consumer` SET api_key='', api_secret=''""") diff --git a/frappe/patches/v13_0/set_route_for_blog_category.py b/frappe/patches/v13_0/set_route_for_blog_category.py new file mode 100644 index 0000000000..7ea26bc2c0 --- /dev/null +++ b/frappe/patches/v13_0/set_route_for_blog_category.py @@ -0,0 +1,8 @@ +import frappe + +def execute(): + categories = frappe.get_list("Blog Category") + for category in categories: + doc = frappe.get_doc("Blog Category", category["name"]) + doc.set_route() + doc.save() diff --git a/frappe/public/build.json b/frappe/public/build.json index 997a3092ad..844e436e43 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -128,7 +128,7 @@ "public/js/lib/Sortable.min.js", "public/js/lib/jquery/jquery.hotkeys.js", "public/js/lib/bootstrap.min.js", - "node_modules/vue/dist/vue.js", + "node_modules/vue/dist/vue.min.js", "node_modules/moment/min/moment-with-locales.min.js", "node_modules/moment-timezone/builds/moment-timezone-with-data.min.js", "public/js/lib/socket.io.min.js", diff --git a/frappe/public/js/frappe/form/controls/data.js b/frappe/public/js/frappe/form/controls/data.js index 355f35891a..bbf9a89072 100644 --- a/frappe/public/js/frappe/form/controls/data.js +++ b/frappe/public/js/frappe/form/controls/data.js @@ -61,7 +61,7 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ // check if name exists frappe.db.get_value(this.doctype, this.$input.val(), 'name', (val) => { - if (val) { + if (val && val.name) { this.set_description(__('{0} already exists. Select another name', [val.name])); } }, diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 733c1bea5f..827fbfdee6 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -393,11 +393,16 @@ export default class GridRow { // sync get_query field.get_query = this.grid.get_field(df.fieldname).get_query; - var field_on_change_function = field.df.onchange; - field.df.onchange = function(e) { - field_on_change_function && field_on_change_function(e); - me.grid.grid_rows[this.doc.idx - 1].refresh_field(field.df.fieldname); - }; + if (!field.df.onchange_modified) { + var field_on_change_function = field.df.onchange; + field.df.onchange = function(e) { + field_on_change_function && field_on_change_function(e); + me.grid.grid_rows[this.doc.idx - 1].refresh_field(this.df.fieldname); + }; + + field.df.onchange_modified = true; + } + field.refresh(); if(field.$input) { field.$input diff --git a/frappe/public/js/frappe/ui/tree.js b/frappe/public/js/frappe/ui/tree.js index a6287ed2c8..d50d78a7e5 100644 --- a/frappe/public/js/frappe/ui/tree.js +++ b/frappe/public/js/frappe/ui/tree.js @@ -5,17 +5,20 @@ frappe.provide('frappe.ui'); frappe.ui.Tree = class { constructor({ - parent, label, icon_set, toolbar, expandable, with_skeleton=1, // eslint-disable-line + parent, label, root_value, icon_set, toolbar, expandable, with_skeleton=1, // eslint-disable-line args, method, get_label, on_render, on_click // eslint-disable-line }) { $.extend(this, arguments[0]); + if (root_value == null) { + this.root_value = label; + } this.setup_treenode_class(); this.nodes = {}; this.wrapper = $('
').appendTo(this.parent); - if(with_skeleton) this.wrapper.addClass('with-skeleton'); + if (with_skeleton) this.wrapper.addClass('with-skeleton'); - if(!icon_set) { + if (!icon_set) { this.icon_set = { open: 'fa fa-fw fa-folder-open', closed: 'fa fa-fw fa-folder', @@ -42,8 +45,9 @@ frappe.ui.Tree = class { }); } - get_all_nodes(value, is_root) { + get_all_nodes(value, is_root, label) { var args = Object.assign({}, this.args); + args.label = label || value; args.parent = value; args.is_root = is_root; @@ -88,7 +92,7 @@ frappe.ui.Tree = class { expandable: true, is_root: true, data: { - value: this.label + value: this.root_value } }); this.expand_node(this.root_node, false); @@ -144,25 +148,25 @@ frappe.ui.Tree = class { } load_children(node, deep=false) { - let value = node.data.value, is_root = node.is_root; + let lab = node.label, value = node.data.value, is_root = node.is_root; if(!deep) { frappe.run_serially([ - () => {return this.get_nodes(value, is_root);}, - (data_set) => { this.render_node_children(node, data_set); }, - () => { this.set_selected_node(node); } + () => this.get_nodes(value, is_root), + (data_set) => this.render_node_children(node, data_set), + () => this.set_selected_node(node) ]); } else { frappe.run_serially([ - () => {return this.get_all_nodes(value, is_root);}, - (data_list) => { this.render_children_of_all_nodes(data_list); }, - () => { this.set_selected_node(node); } + () => this.get_all_nodes(value, is_root, lab), + (data_list) => this.render_children_of_all_nodes(data_list), + () => this.set_selected_node(node) ]); } } render_children_of_all_nodes(data_list) { - data_list.map(d => { this.render_node_children(this.nodes[d.parent], d.data); }); + data_list.map(d => this.render_node_children(this.nodes[d.parent], d.data)); } render_node_children(node, data_set) { diff --git a/frappe/public/js/frappe/views/reports/print_grid.html b/frappe/public/js/frappe/views/reports/print_grid.html index 852c2925e8..e607f12b52 100644 --- a/frappe/public/js/frappe/views/reports/print_grid.html +++ b/frappe/public/js/frappe/views/reports/print_grid.html @@ -37,16 +37,20 @@ - {% format_data = row.is_total_row ? data[0] : row %} - {{ - col.formatter - ? col.formatter(row._index, col._index, value, col, format_data, true) - : col.format - ? col.format(value, row, col, format_data) - : col.docfield - ? frappe.format(value, col.docfield) - : value - }} + {% format_data = row.is_total_row && ["Currency", "Float"].includes(col.fieldtype) ? data[0] : row %} + {% if (row.is_total_row && col._index == 0) { %} + {{ __("Total") }} + {% } else { %} + {{ + col.formatter + ? col.formatter(row._index, col._index, value, col, format_data, true) + : col.format + ? col.format(value, row, col, format_data) + : col.docfield + ? frappe.format(value, col.docfield) + : value + }} + {% } %} {% endif %} @@ -55,4 +59,3 @@ {% endfor %} - diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 1bec65e460..1514fcb070 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -1259,7 +1259,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { return; } - this.export_dialog = frappe.prompt([ + let export_dialog_fields = [ { label: __('Select File Format'), fieldname: 'file_format', @@ -1267,13 +1267,18 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { options: ['Excel', 'CSV'], default: 'Excel', reqd: 1 - }, - { + } + ]; + + if (this.tree_report) { + export_dialog_fields.push({ label: __("Include indentation"), fieldname: "include_indentation", fieldtype: "Check", - } - ], ({ file_format, include_indentation }) => { + }); + } + + this.export_dialog = frappe.prompt(export_dialog_fields, ({ file_format, include_indentation }) => { this.make_access_log('Export', file_format); if (file_format === 'CSV') { const column_row = this.columns.reduce((acc, col) => { diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index c7d001ed94..05e8e75eeb 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -50,6 +50,12 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { super.setup_new_doc_event(); } + toggle_side_bar() { + super.toggle_side_bar(); + // refresh datatable when sidebar is toggled to accomodate extra space + this.render(true); + } + setup_result_area() { super.setup_result_area(); this.setup_charts_area(); diff --git a/frappe/public/js/frappe/views/treeview.js b/frappe/public/js/frappe/views/treeview.js index 815e561f41..777ce14da6 100644 --- a/frappe/public/js/frappe/views/treeview.js +++ b/frappe/public/js/frappe/views/treeview.js @@ -39,6 +39,7 @@ frappe.views.TreeView = Class.extend({ this.get_permissions(); this.make_page(); this.make_filters(); + this.root_value = null; if (me.opts.get_tree_root) { this.get_root(); @@ -129,7 +130,13 @@ frappe.views.TreeView = Class.extend({ args: me.args, callback: function(r) { if (r.message) { - me.root_label = r.message[0]["value"]; + if (r.message.length > 1) { + me.root_label = me.doctype; + me.root_value = ""; + } else { + me.root_label = r.message[0]["value"]; + me.root_value = me.root_label; + } me.make_tree(); } } @@ -138,9 +145,15 @@ frappe.views.TreeView = Class.extend({ make_tree: function() { $(this.parent).find(".tree").remove(); + var use_label = this.args[this.opts.root_label] || this.root_label || this.opts.root_label; + var use_value = this.root_value; + if (use_value == null) { + use_value = use_label; + } this.tree = new frappe.ui.Tree({ parent: this.body, - label: this.args[this.opts.root_label] || this.root_label || this.opts.root_label, + label: use_label, + root_value: use_value, expandable: true, args: this.args, diff --git a/frappe/public/js/frappe/widgets/shortcut_widget.js b/frappe/public/js/frappe/widgets/shortcut_widget.js index a8eead8a3d..7174ba0d2a 100644 --- a/frappe/public/js/frappe/widgets/shortcut_widget.js +++ b/frappe/public/js/frappe/widgets/shortcut_widget.js @@ -89,11 +89,10 @@ export default class ShortcutWidget extends Widget { const label = get_label(); const buttons = $(`
${label}
`); if (this.color) { - buttons.css("background-color", this.color); - buttons.css( - "color", - frappe.ui.color.get_contrast_color(this.color) - ); + let bg_color = count ? this.color: '#EEEEEE'; + let text_color = count ? frappe.ui.color.get_contrast_color(bg_color): '#8D99A6'; + buttons.css("background-color", bg_color); + buttons.css("color", text_color); } buttons.appendTo(this.action_area); diff --git a/frappe/public/less/report.less b/frappe/public/less/report.less index f519640f7c..0a8d37b9ba 100644 --- a/frappe/public/less/report.less +++ b/frappe/public/less/report.less @@ -72,7 +72,7 @@ margin-bottom: 10px; } -.report-wrapper { +.report-wrapper, .datatable-wrapper { overflow: auto; } diff --git a/frappe/public/scss/login.scss b/frappe/public/scss/login.scss new file mode 100644 index 0000000000..19d8b50343 --- /dev/null +++ b/frappe/public/scss/login.scss @@ -0,0 +1,203 @@ +/* login-css */ + +#page-login { + .hero-and-content { + /*background-color: #f5f7fa;*/ + background-color: #fafbfc; + } + + .page-sidebar, + #wrap-footer, + .page-header { + display: none; + } + + .page-content { + right: 0%; + width: 100%; + } + + .icon-twitter, + .icon-twitter-sign { + color: #00a0d1; + } + + .icon-linkedin, + .icon-linkedin-sign { + color: #4875b4; + } + + #wrap { + background-color: #7575ff; + } + + .for-login { + display: none; + } + + .for-forgot { + display: none; + } + + .for-signup { + display: none; + } + + .form-signin { + .form-signin-heading, + .checkbox { + margin-bottom: 10px; + } + .checkbox { + font-weight: normal; + } + .form-control { + position: relative; + height: auto; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 6px; + font-size: 14px; + margin-bottom: 10px; + } + .form-control:focus { + z-index: 2; + } + } + + .btn-social { + margin: 10px; + } + + .social-logins .fa { + margin-right: 5px; + color: #8d99a6; + } + + .form-footer { + margin-top: -45px; + text-align: center; + font-size: 12px; + color: #8d99a6; + font-weight: bold; + + a { + font-size: 12px; + color: #8d99a6; + font-weight: bold; + } + + h6 { + font-size: 12px; + color: #8d99a6; + font-weight: bold; + } + + .btn-default { + color: #36414c; + } + } + + h5 { + position: relative; + text-align: center; + margin-top: 20px; + margin-bottom: 20px; + } + + p { + margin-bottom: 20px; + } + + .login-content .btn { + font-size: 14px; + margin-top: 45px; + } + + .page-card { + max-width: 360px; + padding: 15px; + margin: 70px auto; + border: 1px solid #d1d8dd; + border-radius: 4px; + background-color: #fff; + box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.1); + + .page-card-head { + padding: 10px 15px; + margin: -15px; + margin-bottom: 15px; + border-bottom: 1px solid #d1d8dd; + } + + .page-card-head .indicator { + color: #36414c; + font-size: 14px; + } + + .page-card-head .indicator::before { + margin: 0 6px 0.5px 0px; + } + + .btn { + margin-top: 30px; + } + } + + .bordered { + border: 1px solid #d1d8dd; + padding: 10px; + border-radius: 4px; + } + + .toggle-password { + right: 9px; + top: 9px; + position: absolute; + z-index: 2; + cursor: pointer; + font-size: 12px; + } + + .invalid-login { + -webkit-animation: wiggle 0.5s linear; + animation: wiggle 0.5s linear; + } + + @-webkit-keyframes wiggle { + 8%, 41% { + -webkit-transform: translateX(-10px); + } + 25%, 58% { + -webkit-transform: translateX(10px); + } + 75% { + -webkit-transform: translateX(-5px); + } + 92% { + -webkit-transform: translateX(5px); + } + 0%, 100% { + -webkit-transform: translateX(0); + } + } + + @keyframes wiggle { + 8%, 41% { + transform: translate(-10px); + } + 25%, 58% { + transform: translate(10px); + } + 75% { + transform: translate(-5px); + } + 92% { + transform: translate(5px); + } + 0%, 100% { + transform: translate(0); + } + } +} diff --git a/frappe/public/scss/website.scss b/frappe/public/scss/website.scss index b65966e102..a60329400a 100644 --- a/frappe/public/scss/website.scss +++ b/frappe/public/scss/website.scss @@ -15,6 +15,7 @@ @import 'doc'; @import 'navbar'; @import 'footer'; +@import 'login'; .ql-editor.read-mode { padding: 0; diff --git a/frappe/search/full_text_search.py b/frappe/search/full_text_search.py index dd6e69111d..ecb018dbb4 100644 --- a/frappe/search/full_text_search.py +++ b/frappe/search/full_text_search.py @@ -46,7 +46,8 @@ class FullTextSearch: doc_name (str): name of the document to be updated """ document = self.get_document_to_index(doc_name) - self.update_index(document) + if document: + self.update_index(document) def remove_document_from_index(self, doc_name): """Remove document from search index diff --git a/frappe/templates/includes/login/login.css b/frappe/templates/includes/login/login.css deleted file mode 100644 index c336ae742e..0000000000 --- a/frappe/templates/includes/login/login.css +++ /dev/null @@ -1,167 +0,0 @@ -/* login-css */ - -.hero-and-content { - /*background-color: #f5f7fa;*/ - background-color: #fafbfc; -} - -.page-sidebar, #wrap-footer, .page-header { - display: none; -} - -.page-content { - right: 0%; - width: 100%; -} - -.icon-twitter, .icon-twitter-sign{ - color: #00a0d1; -} - -.icon-linkedin, .icon-linkedin-sign{ - color: #4875B4; -} - -#wrap { - background-color: #7575ff; -} - -.for-login { - display: none; -} - -.for-forgot { - display: none; -} - -.for-signup { - display: none; -} - -.form-signin .form-signin-heading, -.form-signin .checkbox { - margin-bottom: 10px; -} -.form-signin .checkbox { - font-weight: normal; -} -.form-signin .form-control { - position: relative; - height: auto; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - padding: 6px; - font-size: 14px; - margin-bottom: 10px; -} -.form-signin .form-control:focus { - z-index: 2; -} - -.btn-social { - margin: 10px; -} - -.social-logins .fa { - margin-right: 5px; - color: #8D99A6; -} - -.form-footer { - margin-top: -45px; - text-align: center; -} - -.form-footer, .form-footer a, .form-footer h6 { - font-size: 12px; - color: #8D99A6; - font-weight: bold; -} - -.form-footer .btn-default { - color: #36414C; -} - -h5 { - position: relative; - text-align: center; - margin-top:20px; - margin-bottom:20px; -} - -p { - margin-bottom:20px; -} - -.login-content .btn { - font-size: 14px; - margin-top: 45px; -} - -.page-card { - max-width: 360px; - padding: 15px; - margin: 70px auto; - border: 1px solid #d1d8dd; - border-radius: 4px; - background-color: #fff; - box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.1); -} -.page-card .page-card-head { - padding: 10px 15px; - margin: -15px; - margin-bottom: 15px; - border-bottom: 1px solid #d1d8dd; -} -.page-card .page-card-head .indicator { - color: #36414C; - font-size: 14px; -} -.page-card .page-card-head .indicator::before { - margin: 0 6px 0.5px 0px; -} -.page-card .btn { - margin-top: 30px; -} - -.bordered { - border: 1px solid #d1d8dd; - padding: 10px; - border-radius: 4px; -} - -.toggle-password { - right: 9px; - top: 9px; - position: absolute; - z-index: 2; - cursor: pointer; - font-size: 12px; -} - -.invalid-login { - -webkit-animation: wiggle 0.5s linear; -} - -@-webkit-keyframes wiggle { - 8%, - 41% { - -webkit-transform: translateX(-10px); - } - 25%, - 58% { - -webkit-transform: translateX(10px); - } - 75% { - -webkit-transform: translateX(-5px); - } - 92% { - -webkit-transform: translateX(5px); - } - 0%, - 100% { - -webkit-transform: translateX(0); - } -} - diff --git a/frappe/tests/test_client.py b/frappe/tests/test_client.py index 8371849935..89460203f6 100644 --- a/frappe/tests/test_client.py +++ b/frappe/tests/test_client.py @@ -21,3 +21,37 @@ class TestClient(unittest.TestCase): self.assertFalse(frappe.db.exists("ToDo", todo.name)) self.assertRaises(frappe.DoesNotExistError, delete, "ToDo", todo.name) + + def test_http_valid_method_access(self): + from frappe.client import delete + from frappe.handler import execute_cmd + + frappe.set_user("Administrator") + + frappe.local.request = frappe._dict() + frappe.local.request.method = 'POST' + + frappe.local.form_dict = frappe._dict({ + 'doc': dict(doctype='ToDo', description='Valid http method'), + 'cmd': 'frappe.client.save' + }) + todo = execute_cmd('frappe.client.save') + + self.assertEqual(todo.get('description'), 'Valid http method') + + delete("ToDo", todo.name) + + def test_http_invalid_method_access(self): + from frappe.handler import execute_cmd + + frappe.set_user("Administrator") + + frappe.local.request = frappe._dict() + frappe.local.request.method = 'GET' + + frappe.local.form_dict = frappe._dict({ + 'doc': dict(doctype='ToDo', description='Invalid http method'), + 'cmd': 'frappe.client.save' + }) + + self.assertRaises(frappe.PermissionError, execute_cmd, 'frappe.client.save') diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py new file mode 100644 index 0000000000..82c0cdce5c --- /dev/null +++ b/frappe/tests/test_commands.py @@ -0,0 +1,46 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors + +# imports - standard imports +import shlex +import subprocess +import unittest + +# imports - module imports +import frappe + + +def clean(value): + if isinstance(value, (bytes, str)): + value = value.decode().strip() + return value + + +class BaseTestCommands: + def execute(self, command): + command = command.format(**{"site": frappe.local.site}) + command = shlex.split(command) + self._proc = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + self.stdout = clean(self._proc.stdout) + self.stderr = clean(self._proc.stderr) + self.returncode = clean(self._proc.returncode) + + +class TestCommands(BaseTestCommands, unittest.TestCase): + def test_execute(self): + # test 1: execute a command expecting a numeric output + self.execute("bench --site {site} execute frappe.db.get_database_size") + self.assertEquals(self.returncode, 0) + self.assertIsInstance(float(self.stdout), float) + + # test 2: execute a command expecting an errored output as local won't exist + self.execute("bench --site {site} execute frappe.local.site") + self.assertEquals(self.returncode, 1) + self.assertIsNotNone(self.stderr) + + # test 3: execute a command with kwargs + # Note: + # terminal command has been escaped to avoid .format string replacement + # The returned value has quotes which have been trimmed for the test + self.execute("""bench --site {site} execute frappe.bold --kwargs '{{"text": "DocType"}}'""") + self.assertEquals(self.returncode, 0) + self.assertEquals(self.stdout[1:-1], frappe.bold(text='DocType')) diff --git a/frappe/tests/test_form_load.py b/frappe/tests/test_form_load.py index 34fc58465e..78562e1055 100644 --- a/frappe/tests/test_form_load.py +++ b/frappe/tests/test_form_load.py @@ -24,7 +24,7 @@ class TestFormLoad(unittest.TestCase): def test_fieldlevel_permissions_in_load(self): blog = frappe.get_doc({ "doctype": "Blog Post", - "blog_category": "_Test Blog Category 1", + "blog_category": "-test-blog-category-1", "blog_intro": "Test Blog Intro", "blogger": "_Test Blogger 1", "content": "Test Blog Content", @@ -40,7 +40,7 @@ class TestFormLoad(unittest.TestCase): user.remove_roles(*user_roles) user.add_roles('Blogger') - make_property_setter('Blog Post', 'published', 'permlevel', 1, 'Int') + blog_post_property_setter = make_property_setter('Blog Post', 'published', 'permlevel', 1, 'Int') reset('Blog Post') add('Blog Post', 'Website Manager', 1) update('Blog Post', 'Website Manager', 1, 'write', 1) @@ -80,6 +80,7 @@ class TestFormLoad(unittest.TestCase): user.add_roles(*user_roles) blog_doc.delete() + frappe.delete_doc(blog_post_property_setter.doctype, blog_post_property_setter.name) def test_fieldlevel_permissions_in_load_for_child_table(self): contact = frappe.new_doc('Contact') diff --git a/frappe/tests/test_permissions.py b/frappe/tests/test_permissions.py index 364469f168..dddc790c94 100644 --- a/frappe/tests/test_permissions.py +++ b/frappe/tests/test_permissions.py @@ -59,7 +59,7 @@ class TestPermissions(unittest.TestCase): self.assertTrue(post.has_permission("read")) def test_user_permissions_in_doc(self): - add_user_permission("Blog Category", "_Test Blog Category 1", + add_user_permission("Blog Category", "-test-blog-category-1", "test2@example.com") frappe.set_user("test2@example.com") @@ -73,7 +73,7 @@ class TestPermissions(unittest.TestCase): self.assertTrue(get_doc_permissions(post1).get("read")) def test_user_permissions_in_report(self): - add_user_permission("Blog Category", "_Test Blog Category 1", "test2@example.com") + add_user_permission("Blog Category", "-test-blog-category-1", "test2@example.com") frappe.set_user("test2@example.com") names = [d.name for d in frappe.get_list("Blog Post", fields=["name", "blog_category"])] @@ -86,23 +86,23 @@ class TestPermissions(unittest.TestCase): self.assertFalse(doc.get("blog_category")) # Fetch default based on single user permission - add_user_permission("Blog Category", "_Test Blog Category 1", "test2@example.com") + add_user_permission("Blog Category", "-test-blog-category-1", "test2@example.com") frappe.set_user("test2@example.com") doc = frappe.new_doc("Blog Post") - self.assertEqual(doc.get("blog_category"), "_Test Blog Category 1") + self.assertEqual(doc.get("blog_category"), "-test-blog-category-1") # Don't fetch default if user permissions is more than 1 - add_user_permission("Blog Category", "_Test Blog Category", "test2@example.com", ignore_permissions=True) + add_user_permission("Blog Category", "-test-blog-category", "test2@example.com", ignore_permissions=True) frappe.clear_cache() doc = frappe.new_doc("Blog Post") self.assertFalse(doc.get("blog_category")) # Fetch user permission set as default from multiple user permission - add_user_permission("Blog Category", "_Test Blog Category 2", "test2@example.com", ignore_permissions=True, is_default=1) + add_user_permission("Blog Category", "-test-blog-category-2", "test2@example.com", ignore_permissions=True, is_default=1) frappe.clear_cache() doc = frappe.new_doc("Blog Post") - self.assertEqual(doc.get("blog_category"), "_Test Blog Category 2") + self.assertEqual(doc.get("blog_category"), "-test-blog-category-2") def test_user_link_match_doc(self): blogger = frappe.get_doc("Blogger", "_Test Blogger 1") @@ -215,7 +215,7 @@ class TestPermissions(unittest.TestCase): frappe.clear_cache(doctype='DocType') def test_user_permission_doctypes(self): - add_user_permission("Blog Category", "_Test Blog Category 1", + add_user_permission("Blog Category", "-test-blog-category-1", "test2@example.com") add_user_permission("Blogger", "_Test Blogger 1", "test2@example.com") @@ -235,7 +235,7 @@ class TestPermissions(unittest.TestCase): def if_owner_setup(self): update('Blog Post', 'Blogger', 0, 'if_owner', 1) - add_user_permission("Blog Category", "_Test Blog Category 1", + add_user_permission("Blog Category", "-test-blog-category-1", "test2@example.com") add_user_permission("Blogger", "_Test Blogger 1", "test2@example.com") @@ -254,7 +254,7 @@ class TestPermissions(unittest.TestCase): doc = frappe.get_doc({ "doctype": "Blog Post", - "blog_category": "_Test Blog Category", + "blog_category": "-test-blog-category", "blogger": "_Test Blogger 1", "title": "_Test Blog Post Title", "content": "_Test Blog Post Content" @@ -263,14 +263,14 @@ class TestPermissions(unittest.TestCase): self.assertRaises(frappe.PermissionError, doc.insert) frappe.set_user('test1@example.com') - add_user_permission("Blog Category", "_Test Blog Category", + add_user_permission("Blog Category", "-test-blog-category", "test2@example.com") frappe.set_user("test2@example.com") doc.insert() frappe.set_user("Administrator") - remove_user_permission("Blog Category", "_Test Blog Category", + remove_user_permission("Blog Category", "-test-blog-category", "test2@example.com") frappe.set_user("test2@example.com") @@ -286,13 +286,13 @@ class TestPermissions(unittest.TestCase): def test_ignore_user_permissions_if_missing(self): """If there are no user permissions, then allow as per role""" - add_user_permission("Blog Category", "_Test Blog Category", + add_user_permission("Blog Category", "-test-blog-category", "test2@example.com") frappe.set_user("test2@example.com") doc = frappe.get_doc({ "doctype": "Blog Post", - "blog_category": "_Test Blog Category 2", + "blog_category": "-test-blog-category-2", "blogger": "_Test Blogger 1", "title": "_Test Blog Post Title", "content": "_Test Blog Post Content" @@ -301,7 +301,7 @@ class TestPermissions(unittest.TestCase): self.assertFalse(doc.has_permission("write")) frappe.set_user("Administrator") - remove_user_permission("Blog Category", "_Test Blog Category", + remove_user_permission("Blog Category", "-test-blog-category", "test2@example.com") frappe.set_user("test2@example.com") @@ -420,7 +420,7 @@ class TestPermissions(unittest.TestCase): doc = frappe.get_doc({ "doctype": "Blog Post", - "blog_category": "_Test Blog Category", + "blog_category": "-test-blog-category", "blogger": "_Test Blogger 1", "title": "_Test Blog Post Title", "content": "_Test Blog Post Content" @@ -454,7 +454,7 @@ class TestPermissions(unittest.TestCase): add_user_permission('Blog Post', '-test-blog-post-1', 'test2@example.com') add_user_permission('Blog Post', '-test-blog-post-2', 'test2@example.com') - add_user_permission("Blog Category", '_Test Blog Category 1', 'test2@example.com') + add_user_permission("Blog Category", '-test-blog-category-1', 'test2@example.com') deleted_user_permission_count = clear_user_permissions('test2@example.com', 'Blog Post') diff --git a/frappe/tests/test_safe_exec.py b/frappe/tests/test_safe_exec.py index 95bbae6746..d7b25b8194 100644 --- a/frappe/tests/test_safe_exec.py +++ b/frappe/tests/test_safe_exec.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals import unittest, frappe -from frappe.utils.safe_exec import safe_exec +from frappe.utils.safe_exec import safe_exec, get_safe_globals class TestSafeExec(unittest.TestCase): def test_import_fails(self): @@ -9,6 +9,15 @@ class TestSafeExec(unittest.TestCase): def test_internal_attributes(self): self.assertRaises(SyntaxError, safe_exec, '().__class__.__call__') + def test_utils(self): + _locals = dict(out=None) + safe_exec('''out = frappe.utils.cint("1")''', None, _locals) + self.assertEqual(_locals['out'], 1) + + def test_safe_eval(self): + self.assertEqual(frappe.safe_eval('1+1'), 2) + self.assertRaises(AttributeError, frappe.safe_eval, 'frappe.utils.os.path', get_safe_globals()) + def test_sql(self): _locals = dict(out=None) safe_exec('''out = frappe.db.sql("select name from tabDocType where name='DocType'")''', None, _locals) diff --git a/frappe/tests/test_website.py b/frappe/tests/test_website.py index c708f670c1..c5da2bdfb7 100644 --- a/frappe/tests/test_website.py +++ b/frappe/tests/test_website.py @@ -9,6 +9,7 @@ from frappe.utils import set_request class TestWebsite(unittest.TestCase): + def test_home_page_for_role(self): frappe.delete_doc_if_exists('User', 'test-user-for-home-page@example.com') frappe.delete_doc_if_exists('Role', 'home-page-test') @@ -42,8 +43,6 @@ class TestWebsite(unittest.TestCase): frappe.cache().hdel('home_page', frappe.session.user) self.assertEqual(get_home_page(), 'test-portal-home') - - def test_page_load(self): frappe.set_user('Guest') set_request(method='POST', path='login') @@ -53,7 +52,6 @@ class TestWebsite(unittest.TestCase): html = frappe.safe_decode(response.get_data()) - self.assertTrue('/* login-css */' in html) self.assertTrue('// login.js' in html) self.assertTrue('' in html) frappe.set_user('Administrator') diff --git a/frappe/tests/ui_test_helpers.py b/frappe/tests/ui_test_helpers.py index 40ebc8ea6e..ef572c6971 100644 --- a/frappe/tests/ui_test_helpers.py +++ b/frappe/tests/ui_test_helpers.py @@ -9,6 +9,9 @@ def create_if_not_exists(doc): :param doc: dict of field value pairs. can be a list of dict for multiple records. ''' + if not frappe.local.dev_server: + frappe.throw('This method can only be accessed in development', frappe.PermissionError) + doc = frappe.parse_json(doc) if not isinstance(doc, list): diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index 1da220dc30..557a2fd647 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -135,7 +135,8 @@ def validate_email_address(email_str, throw=False): if not _valid: if throw: - frappe.throw(frappe._("{0} is not a valid Email Address").format(e), + invalid_email = frappe.utils.escape_html(e) + frappe.throw(frappe._("{0} is not a valid Email Address").format(invalid_email), frappe.InvalidEmailAddressError) return None else: diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py index b8184886f9..7f06a26ee0 100644 --- a/frappe/utils/backups.py +++ b/frappe/utils/backups.py @@ -68,6 +68,12 @@ class BackupGenerator: dir = os.path.dirname(file_path) os.makedirs(dir, exist_ok=True) + @property + def site_config_backup_path(self): + # For backwards compatibility + import click + click.secho("BackupGenerator.site_config_backup_path has been deprecated in favour of BackupGenerator.backup_path_conf", fg="yellow") + return getattr(self, "backup_path_conf", None) def get_backup(self, older_than=24, ignore_files=False, force=False): """ @@ -96,7 +102,7 @@ class BackupGenerator: self.backup_path_files = last_file self.backup_path_db = last_db self.backup_path_private_files = last_private_file - self.site_config_backup_path = site_config_backup_path + self.backup_path_conf = site_config_backup_path def set_backup_file_name(self): #Generate a random name using today's date and a 8 digit random number diff --git a/frappe/utils/change_log.py b/frappe/utils/change_log.py index c75b3289db..29fee2bac0 100644 --- a/frappe/utils/change_log.py +++ b/frappe/utils/change_log.py @@ -9,7 +9,6 @@ import frappe import requests import subprocess # nosec from frappe.utils import cstr -from frappe.utils.gitutils import get_app_branch from frappe import _, safe_decode diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 098399cf6d..ab8c107bd8 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -3,10 +3,8 @@ from __future__ import unicode_literals -# IMPORTANT: only import safe functions as this module will be included in jinja environment import frappe from dateutil.parser._parser import ParserError -import subprocess import operator import json import re, datetime, math, time @@ -427,19 +425,6 @@ def flt(s, precision=None): return num -def get_wkhtmltopdf_version(): - wkhtmltopdf_version = frappe.cache().hget("wkhtmltopdf_version", None) - - if not wkhtmltopdf_version: - try: - res = subprocess.check_output(["wkhtmltopdf", "--version"]) - wkhtmltopdf_version = res.decode('utf-8').split(" ")[1] - frappe.cache().hset("wkhtmltopdf_version", None, wkhtmltopdf_version) - except Exception: - pass - - return (wkhtmltopdf_version or '0') - def cint(s): """Convert to integer""" try: num = int(float(s)) @@ -754,7 +739,7 @@ def get_thumbnail_base64_for_image(src): if not src: frappe.throw('Invalid source for image: {0}'.format(src)) - if not src.startswith('/files'): + if not src.startswith('/files') or '..' in src: return if src.endswith('.svg'): diff --git a/frappe/utils/gitutils.py b/frappe/utils/gitutils.py deleted file mode 100644 index 10268a6581..0000000000 --- a/frappe/utils/gitutils.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import unicode_literals - -import subprocess - -def get_app_branch(app): - '''Returns branch of an app''' - try: - branch = subprocess.check_output('cd ../apps/{0} && git rev-parse --abbrev-ref HEAD'.format(app), - shell=True) - branch = branch.decode('utf-8') - branch = branch.strip() - return branch - except Exception: - return '' - -def get_app_last_commit_ref(app): - try: - commit_id = subprocess.check_output('cd ../apps/{0} && git rev-parse HEAD'.format(app), - shell=True) - commit_id = commit_id.decode('utf-8') - commit_id = commit_id.strip()[:7] - return commit_id - except Exception: - return '' diff --git a/frappe/utils/password.py b/frappe/utils/password.py index b1461ce7fe..177a3118fb 100644 --- a/frappe/utils/password.py +++ b/frappe/utils/password.py @@ -50,6 +50,7 @@ def get_decrypted_password(doctype, name, fieldname='password', raise_exception= elif raise_exception: frappe.throw(_('Password not found'), frappe.AuthenticationError) + def set_encrypted_password(doctype, name, pwd, fieldname='password'): try: frappe.db.sql("""insert into `__Auth` (doctype, name, fieldname, `password`, encrypted) @@ -63,6 +64,7 @@ def set_encrypted_password(doctype, name, pwd, fieldname='password'): frappe.throw("Most probably your password is too long.", exc=e) raise e + def check_password(user, pwd, doctype='User', fieldname='password'): '''Checks if user and password are correct, else raises frappe.AuthenticationError''' @@ -82,11 +84,20 @@ def check_password(user, pwd, doctype='User', fieldname='password'): return user + def delete_login_failed_cache(user): frappe.cache().hdel('last_login_tried', user) frappe.cache().hdel('login_failed_count', user) frappe.cache().hdel('locked_account_time', user) + +def delete_password_reset_cache(user=None): + if user: + frappe.cache().hdel('password_reset_link_count', user) + else: + frappe.cache().delete_key('password_reset_link_count') + + def update_password(user, pwd, doctype='User', fieldname='password', logout_all_sessions=False): ''' Update the password for the User @@ -115,6 +126,7 @@ def update_password(user, pwd, doctype='User', fieldname='password', logout_all_ from frappe.sessions import clear_sessions clear_sessions(user=user, keep_current=True, force=True) + def delete_all_passwords_for(doctype, name): try: frappe.db.sql("""delete from `__Auth` where `doctype`=%(doctype)s and `name`=%(name)s""", @@ -123,26 +135,31 @@ def delete_all_passwords_for(doctype, name): if not frappe.db.is_missing_column(e): raise + def rename_password(doctype, old_name, new_name): # NOTE: fieldname is not considered, since the document is renamed frappe.db.sql("""update `__Auth` set name=%(new_name)s where doctype=%(doctype)s and name=%(old_name)s""", { 'doctype': doctype, 'new_name': new_name, 'old_name': old_name }) + def rename_password_field(doctype, old_fieldname, new_fieldname): frappe.db.sql('''update `__Auth` set fieldname=%(new_fieldname)s where doctype=%(doctype)s and fieldname=%(old_fieldname)s''', { 'doctype': doctype, 'old_fieldname': old_fieldname, 'new_fieldname': new_fieldname }) + def create_auth_table(): # same as Framework.sql frappe.db.create_auth_table() + def encrypt(pwd): cipher_suite = Fernet(encode(get_encryption_key())) cipher_text = cstr(cipher_suite.encrypt(encode(pwd))) return cipher_text + def decrypt(pwd): try: cipher_suite = Fernet(encode(get_encryption_key())) @@ -152,6 +169,7 @@ def decrypt(pwd): # encryption_key in site_config is changed and not valid frappe.throw(_('Encryption key is invalid, Please check site_config.json')) + def get_encryption_key(): from frappe.installer import update_site_config diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py index f3d2e75c2b..ae5fc2b334 100644 --- a/frappe/utils/pdf.py +++ b/frappe/utils/pdf.py @@ -6,6 +6,7 @@ import io import os import re from distutils.version import LooseVersion +import subprocess import pdfkit import six @@ -14,7 +15,7 @@ from PyPDF2 import PdfFileReader, PdfFileWriter import frappe from frappe import _ -from frappe.utils import get_wkhtmltopdf_version, scrub_urls +from frappe.utils import scrub_urls PDF_CONTENT_ERRORS = ["ContentNotFoundError", "ContentOperationNotPermittedError", @@ -51,6 +52,8 @@ def get_pdf(html, options=None, output=None): output.appendPagesFromReader(reader) else: raise + finally: + cleanup(options) if "password" in options: password = options["password"] @@ -109,8 +112,7 @@ def prepare_options(html, options): options.update(html_options or {}) # cookies - if frappe.session and frappe.session.sid: - options['cookie'] = [('sid', '{0}'.format(frappe.session.sid))] + options.update(get_cookie_options()) # page size if not options.get("page-size"): @@ -119,6 +121,22 @@ def prepare_options(html, options): return html, options +def get_cookie_options(): + options = {} + if frappe.session and frappe.session.sid: + # Use wkhtmltopdf's cookie-jar feature to set cookies and restrict them to host domain + cookiejar = "/tmp/{}.jar".format(frappe.generate_hash()) + + # Remove port from request.host + # https://werkzeug.palletsprojects.com/en/0.16.x/wrappers/#werkzeug.wrappers.BaseRequest.host + domain = frappe.local.request.host.split(":", 1)[0] + with open(cookiejar, "w") as f: + f.write("sid={}; Domain={};\n".format(frappe.session.sid, domain)) + + options['cookie-jar'] = cookiejar + + return options + def read_options_from_html(html): options = {} soup = BeautifulSoup(html, "html5lib") @@ -183,15 +201,11 @@ def prepare_header_footer(soup): return options -def cleanup(fname, options): - if os.path.exists(fname): - os.remove(fname) - - for key in ("header-html", "footer-html"): +def cleanup(options): + for key in ("header-html", "footer-html", "cookie-jar"): if options.get(key) and os.path.exists(options[key]): os.remove(options[key]) - def toggle_visible_pdf(soup): for tag in soup.find_all(attrs={"class": "visible-pdf"}): # remove visible-pdf class to unhide @@ -200,3 +214,16 @@ def toggle_visible_pdf(soup): for tag in soup.find_all(attrs={"class": "hidden-pdf"}): # remove tag from html tag.extract() + +def get_wkhtmltopdf_version(): + wkhtmltopdf_version = frappe.cache().hget("wkhtmltopdf_version", None) + + if not wkhtmltopdf_version: + try: + res = subprocess.check_output(["wkhtmltopdf", "--version"]) + wkhtmltopdf_version = res.decode('utf-8').split(" ")[1] + frappe.cache().hset("wkhtmltopdf_version", None, wkhtmltopdf_version) + except Exception: + pass + + return (wkhtmltopdf_version or '0') diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index 738ce2ba0f..12382e85cd 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -39,7 +39,7 @@ def get_safe_globals(): date_format = "yyyy-mm-dd" time_format = "HH:mm:ss" - add_module_properties(frappe.utils.data, datautils, lambda obj: hasattr(obj, "__call__")) + add_data_utils(datautils) if "_" in getattr(frappe.local, 'form_dict', {}): del frappe.local.form_dict["_"] @@ -162,6 +162,11 @@ def _write(obj): # allow writing to any object return obj +def add_data_utils(data): + for key, obj in frappe.utils.data.__dict__.items(): + if key in VALID_UTILS: + data[key] = obj + def add_module_properties(module, data, filter_method): for key, obj in module.__dict__.items(): if key.startswith("_"): @@ -171,3 +176,106 @@ def add_module_properties(module, data, filter_method): if filter_method(obj): # only allow functions data[key] = obj + +VALID_UTILS = ( +"DATE_FORMAT", +"TIME_FORMAT", +"DATETIME_FORMAT", +"is_invalid_date_string", +"getdate", +"get_datetime", +"to_timedelta", +"add_to_date", +"add_days", +"add_months", +"add_years", +"date_diff", +"month_diff", +"time_diff", +"time_diff_in_seconds", +"time_diff_in_hours", +"now_datetime", +"get_timestamp", +"get_eta", +"get_time_zone", +"convert_utc_to_user_timezone", +"now", +"nowdate", +"today", +"nowtime", +"get_first_day", +"get_quarter_start", +"get_first_day_of_week", +"get_year_start", +"get_last_day_of_week", +"get_last_day", +"get_time", +"get_datetime_str", +"get_date_str", +"get_time_str", +"get_user_date_format", +"get_user_time_format", +"format_date", +"format_time", +"format_datetime", +"format_duration", +"get_weekdays", +"get_weekday", +"get_timespan_date_range", +"global_date_format", +"has_common", +"flt", +"cint", +"floor", +"ceil", +"cstr", +"rounded", +"remainder", +"safe_div", +"round_based_on_smallest_currency_fraction", +"encode", +"parse_val", +"fmt_money", +"get_number_format_info", +"money_in_words", +"in_words", +"is_html", +"is_image", +"get_thumbnail_base64_for_image", +"image_to_base64", +"strip_html", +"escape_html", +"pretty_date", +"comma_or", +"comma_and", +"comma_sep", +"new_line_sep", +"filter_strip_join", +"get_url", +"get_host_name_from_request", +"url_contains_port", +"get_host_name", +"get_link_to_form", +"get_link_to_report", +"get_absolute_url", +"get_url_to_form", +"get_url_to_list", +"get_url_to_report", +"get_url_to_report_with_filters", +"evaluate_filters", +"compare", +"get_filter", +"make_filter_tuple", +"make_filter_dict", +"sanitize_column", +"scrub_urls", +"expand_relative_urls", +"quoted", +"quote_urls", +"unique", +"strip", +"to_markdown", +"md_to_html", +"is_subset", +"generate_hash" +) \ No newline at end of file diff --git a/frappe/website/doctype/blog_category/blog_category.json b/frappe/website/doctype/blog_category/blog_category.json index b2180047cd..67e17f49fb 100644 --- a/frappe/website/doctype/blog_category/blog_category.json +++ b/frappe/website/doctype/blog_category/blog_category.json @@ -2,26 +2,17 @@ "actions": [], "allow_guest_to_view": 1, "allow_import": 1, - "autoname": "field:category_name", + "allow_rename": 1, "creation": "2013-03-08 09:41:11", "doctype": "DocType", "document_type": "Setup", "engine": "InnoDB", "field_order": [ - "category_name", "title", "published", "route" ], "fields": [ - { - "fieldname": "category_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Category Name", - "reqd": 1, - "unique": 1 - }, { "fieldname": "title", "fieldtype": "Data", @@ -31,7 +22,7 @@ "reqd": 1 }, { - "default": "0", + "default": "1", "fieldname": "published", "fieldtype": "Check", "in_list_view": 1, @@ -42,15 +33,17 @@ "fieldname": "route", "fieldtype": "Data", "label": "Route", + "read_only": 1, "unique": 1 } ], "has_web_view": 1, "icon": "fa fa-tag", "idx": 1, + "index_web_pages_for_search": 1, "is_published_field": "published", "links": [], - "modified": "2020-07-29 21:14:47.210446", + "modified": "2020-08-21 11:40:36.919321", "modified_by": "Administrator", "module": "Website", "name": "Blog Category", @@ -78,5 +71,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "title_field": "title", "track_changes": 1 } \ No newline at end of file diff --git a/frappe/website/doctype/blog_category/blog_category.py b/frappe/website/doctype/blog_category/blog_category.py index a293158a09..375ba5b6a3 100644 --- a/frappe/website/doctype/blog_category/blog_category.py +++ b/frappe/website/doctype/blog_category/blog_category.py @@ -8,12 +8,11 @@ from frappe.website.render import clear_cache class BlogCategory(WebsiteGenerator): def autoname(self): # to override autoname of WebsiteGenerator - self.name = self.category_name + self.name = self.scrub(self.title) def on_update(self): clear_cache() - def validate(self): - if not self.route: - self.route = 'blog/' + self.scrub(self.name) - super(BlogCategory, self).validate() + def set_route(self): + # Override blog route since it has to been templated + self.route = 'blog/' + self.name diff --git a/frappe/website/doctype/blog_category/test_blog_category.py b/frappe/website/doctype/blog_category/test_blog_category.py index d033b84786..fe8f4544cd 100644 --- a/frappe/website/doctype/blog_category/test_blog_category.py +++ b/frappe/website/doctype/blog_category/test_blog_category.py @@ -3,5 +3,7 @@ from __future__ import unicode_literals import frappe +import unittest -test_records = frappe.get_test_records('Blog Category') \ No newline at end of file +class TestBlogCategory(unittest.TestCase): + pass diff --git a/frappe/website/doctype/blog_category/test_records.json b/frappe/website/doctype/blog_category/test_records.json index 3334bbc4f9..4bd4ac35b7 100644 --- a/frappe/website/doctype/blog_category/test_records.json +++ b/frappe/website/doctype/blog_category/test_records.json @@ -1,18 +1,15 @@ [ { - "category_name": "_Test Blog Category", "doctype": "Blog Category", "parent_website_route": "blog", "title": "_Test Blog Category" }, { - "category_name": "_Test Blog Category 1", "doctype": "Blog Category", "parent_website_route": "blog", "title": "_Test Blog Category 1" }, { - "category_name": "_Test Blog Category 2", "doctype": "Blog Category", "parent_website_route": "blog", "title": "_Test Blog Category 2" diff --git a/frappe/website/doctype/blog_post/blog_post.js b/frappe/website/doctype/blog_post/blog_post.js index 7aa83f536d..97916b6fc6 100644 --- a/frappe/website/doctype/blog_post/blog_post.js +++ b/frappe/website/doctype/blog_post/blog_post.js @@ -11,18 +11,31 @@ frappe.ui.form.on('Blog Post', { }, title: function(frm) { generate_google_search_preview(frm); + frm.trigger('set_route'); }, meta_description: function(frm) { generate_google_search_preview(frm); }, blog_intro: function(frm) { generate_google_search_preview(frm); + }, + blog_category(frm) { + frm.trigger('set_route'); + }, + set_route(frm) { + if (frm.doc.route) return; + if (frm.doc.title && frm.doc.blog_category) { + frm.call('make_route').then(r => { + frm.set_value('route', r.message); + }); + } } }); function generate_google_search_preview(frm) { + if (!(frm.doc.meta_title || frm.doc.title)) return; let google_preview = frm.get_field("google_preview"); - let seo_title = (frm.doc.title).slice(0, 60); + let seo_title = (frm.doc.meta_title || frm.doc.title).slice(0, 60); let seo_description = (frm.doc.meta_description || frm.doc.blog_intro || "").slice(0, 160); let date = frm.doc.published_on ? new frappe.datetime.datetime(frm.doc.published_on).moment.format('ll') + ' - ' : ''; let route_array = frm.doc.route ? frm.doc.route.split('/') : []; diff --git a/frappe/website/doctype/blog_post/blog_post.json b/frappe/website/doctype/blog_post/blog_post.json index 25bca28e85..48e9a18e71 100644 --- a/frappe/website/doctype/blog_post/blog_post.json +++ b/frappe/website/doctype/blog_post/blog_post.json @@ -26,6 +26,7 @@ "content_html", "email_sent", "meta_tags", + "meta_title", "meta_description", "column_break_18", "meta_image", @@ -110,7 +111,6 @@ "depends_on": "eval:doc.content_type === 'Markdown'", "fieldname": "content_md", "fieldtype": "Markdown Editor", - "ignore_xss_filter": 1, "label": "Content (Markdown)" }, { @@ -185,6 +185,12 @@ "fieldtype": "Check", "hidden": 1, "label": "Hide CTA" + }, + { + "fieldname": "meta_title", + "fieldtype": "Data", + "label": "Meta Title", + "length": 60 } ], "has_web_view": 1, @@ -194,7 +200,7 @@ "is_published_field": "published", "links": [], "max_attachments": 5, - "modified": "2020-08-31 16:55:03.687862", + "modified": "2020-08-31 21:01:51.100349", "modified_by": "Administrator", "module": "Website", "name": "Blog Post", diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py index beffcdca25..a7bc81f08c 100644 --- a/frappe/website/doctype/blog_post/blog_post.py +++ b/frappe/website/doctype/blog_post/blog_post.py @@ -36,6 +36,11 @@ class BlogPost(WebsiteGenerator): if self.blog_intro: self.blog_intro = self.blog_intro[:200] + if not self.meta_title: + self.meta_title = self.title[:60] + else: + self.meta_title = self.meta_title[:60] + if not self.meta_description: self.meta_description = self.blog_intro[:140] else: @@ -88,7 +93,7 @@ class BlogPost(WebsiteGenerator): context.description = self.meta_description or self.blog_intro or strip_html_tags(context.content[:140]) context.metatags = { - "name": self.title, + "name": self.meta_title, "description": context.description, } @@ -242,7 +247,7 @@ def get_blog_list(doctype, txt=None, filters=None, limit_start=0, limit_page_len and t1.blogger = t2.name %(condition)s order by featured desc, published_on desc, name asc - limit %(start)s, %(page_len)s""" % { + limit %(page_len)s OFFSET %(start)s""" % { "start": limit_start, "page_len": limit_page_length, "condition": (" and " + " and ".join(conditions)) if conditions else "" } diff --git a/frappe/website/doctype/blog_post/templates/blog_post.html b/frappe/website/doctype/blog_post/templates/blog_post.html index b9f6521519..dad8b97164 100644 --- a/frappe/website/doctype/blog_post/templates/blog_post.html +++ b/frappe/website/doctype/blog_post/templates/blog_post.html @@ -12,7 +12,7 @@
{{ _('Blog') }} / - {{ category.title }} + {{ category.title }}

{{ title }}

diff --git a/frappe/website/doctype/blog_post/test_blog_post.py b/frappe/website/doctype/blog_post/test_blog_post.py index 15634a7caf..cdf53122b2 100644 --- a/frappe/website/doctype/blog_post/test_blog_post.py +++ b/frappe/website/doctype/blog_post/test_blog_post.py @@ -3,10 +3,14 @@ from __future__ import unicode_literals import frappe import unittest +from bs4 import BeautifulSoup +import re from frappe.utils import set_request from frappe.website.render import render from frappe.utils import random_string +from frappe.website.doctype.blog_post.blog_post import get_blog_list +from frappe.website.website_generator import WebsiteGenerator class TestBlogPost(unittest.TestCase): def test_generator_view(self): @@ -32,12 +36,62 @@ class TestBlogPost(unittest.TestCase): self.assertTrue(response.status_code, 404) -def make_test_blog(): - if not frappe.db.exists('Blog Category', 'Test Blog Category'): + def test_category_link(self): + # Make a temporary Blog Post (and a Blog Category) + blog = make_test_blog() + + # Visit the blog post page + set_request(path=blog.route) + blog_page_response = render() + blog_page_html = frappe.safe_decode(blog_page_response.get_data()) + + # On blog post page find link to the category page + soup = BeautifulSoup(blog_page_html, "lxml") + category_page_link = list(soup.find_all('a', href=re.compile(blog.blog_category)))[0] + category_page_url = category_page_link["href"] + + # Visit the category page (by following the link found in above stage) + set_request(path=category_page_url) + category_page_response = render() + category_page_html = frappe.safe_decode(category_page_response.get_data()) + + # Category page should contain the blog post title + self.assertIn(blog.title, category_page_html) + + # Cleanup afterwords + frappe.delete_doc("Blog Post", blog.name) + frappe.delete_doc("Blog Category", blog.blog_category) + + def test_blog_pagination(self): + # Create some Blog Posts for a Blog Category + category_title, blogs, BLOG_COUNT = "List Category", [], 4 + + for index in range(BLOG_COUNT): + blog = make_test_blog(category_title) + blogs.append(blog) + + filters = frappe._dict({"blog_category": scrub(category_title)}) + # Assert that get_blog_list returns results as expected + + self.assertEqual(len(get_blog_list(None, None, filters, 0, 3)), 3) + self.assertEqual(len(get_blog_list(None, None, filters, 0, BLOG_COUNT)), BLOG_COUNT) + self.assertEqual(len(get_blog_list(None, None, filters, 0, 2)), 2) + self.assertEqual(len(get_blog_list(None, None, filters, 2, BLOG_COUNT)), 2) + + # Cleanup Blog Post and linked Blog Category + for blog in blogs: + frappe.delete_doc(blog.doctype, blog.name) + frappe.delete_doc("Blog Category", blogs[0].blog_category) + +def scrub(text): + return WebsiteGenerator.scrub(None, text) + +def make_test_blog(category_title="Test Blog Category"): + category_name = scrub(category_title) + if not frappe.db.exists('Blog Category', category_name): frappe.get_doc(dict( doctype = 'Blog Category', - category_name = 'Test Blog Category', - title='Test Blog Category')).insert() + title=category_title)).insert() if not frappe.db.exists('Blogger', 'test-blogger'): frappe.get_doc(dict( doctype = 'Blogger', @@ -45,7 +99,7 @@ def make_test_blog(): full_name='Test Blogger')).insert() test_blog = frappe.get_doc(dict( doctype = 'Blog Post', - blog_category = 'Test Blog Category', + blog_category = category_name, blogger = 'test-blogger', title = random_string(20), route = random_string(20), diff --git a/frappe/website/doctype/blog_post/test_records.json b/frappe/website/doctype/blog_post/test_records.json index 79a3525801..4b29eadfa4 100644 --- a/frappe/website/doctype/blog_post/test_records.json +++ b/frappe/website/doctype/blog_post/test_records.json @@ -1,6 +1,6 @@ [ { - "blog_category": "_Test Blog Category", + "blog_category": "-test-blog-category", "blog_intro": "Test Blog Intro", "blogger": "_Test Blogger", "content": "Test Blog Content", @@ -9,7 +9,7 @@ "published": 1 }, { - "blog_category": "_Test Blog Category 1", + "blog_category": "-test-blog-category-1", "blog_intro": "Test Blog Intro", "blogger": "_Test Blogger", "content": "Test Blog Content", @@ -18,7 +18,7 @@ "published": 1 }, { - "blog_category": "_Test Blog Category 1", + "blog_category": "-test-blog-category-1", "blog_intro": "Test Blog Intro", "blogger": "_Test Blogger 1", "content": "Test Blog Content", @@ -27,7 +27,7 @@ "published": 0 }, { - "blog_category": "_Test Blog Category 1", + "blog_category": "-test-blog-category-1", "blog_intro": "Test Blog Intro", "blogger": "_Test Blogger 2", "content": "Test Blog Content", @@ -35,4 +35,4 @@ "title": "_Test Blog Post 3", "published": 0 } -] \ No newline at end of file +] diff --git a/frappe/website/web_template/section_with_tabs/section_with_tabs.html b/frappe/website/web_template/section_with_tabs/section_with_tabs.html index 4b77a8656b..5cf84b6ce4 100644 --- a/frappe/website/web_template/section_with_tabs/section_with_tabs.html +++ b/frappe/website/web_template/section_with_tabs/section_with_tabs.html @@ -1,5 +1,8 @@

{{ title }}

+ +{%- if subtitle -%}

{{ subtitle }}

+{%- endif -%}
{% set ns = namespace(tabs=[]) %} diff --git a/frappe/www/login.html b/frappe/www/login.html index 9d00892b18..96b5bc7026 100644 --- a/frappe/www/login.html +++ b/frappe/www/login.html @@ -1,11 +1,5 @@ {% extends "templates/web.html" %} -{% block style %} - -{% endblock %} - {% block page_content %}
diff --git a/package.json b/package.json index fb3e507bd5..0ef96ad642 100644 --- a/package.json +++ b/package.json @@ -38,13 +38,14 @@ "jsbarcode": "^3.9.0", "moment": "^2.20.1", "moment-timezone": "^0.5.28", + "node-sass": "^4.13.1", "quagga": "^0.12.1", "quill": "2.0.0-dev.4", "quill-image-resize": "^3.0.9", "qz-tray": "^2.0.8", "redis": "^2.8.0", "showdown": "^1.9.1", - "snyk": "^1.382.0", + "snyk": "^1.398.1", "socket.io": "^2.3.0", "superagent": "^3.8.2", "touch": "^3.1.0", @@ -58,7 +59,6 @@ "cypress-file-upload": "^3.1.0", "graphlib": "^2.1.8", "less": "^3.11.1", - "node-sass": "^4.13.1", "rollup": "^1.2.2", "rollup-plugin-buble": "^0.19.2", "rollup-plugin-commonjs": "^8.3.0", diff --git a/requirements.txt b/requirements.txt index af3104cce7..dab8d1214b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -57,6 +57,7 @@ RestrictedPython==5.0 rq>=1.1.0 schedule==0.6.0 semantic-version==2.8.4 +simple-chalk==0.1.0 six==1.14.0 sqlparse==0.2.4 stripe==2.40.0 @@ -71,4 +72,5 @@ zxcvbn-python==4.4.24 pycryptodome==3.9.8 paytmchecksum==1.7.0 wrapt==1.10.11 -twilio==6.44.2 \ No newline at end of file +twilio==6.44.2 +razorpay==1.2.0 diff --git a/rollup/build.js b/rollup/build.js index ea1ac54c09..3375b2d1cd 100644 --- a/rollup/build.js +++ b/rollup/build.js @@ -15,17 +15,35 @@ const { } = require('./rollup.utils'); const { - get_options_for + get_options_for, + get_options } = require('./config'); -const build_for_app = process.argv[2] === '--app' ? process.argv[3] : null; +const skip_frappe = process.argv.includes("--skip_frappe") -show_production_message(); +if (skip_frappe) { + let idx = apps_list.indexOf("frappe"); + if (idx > -1) { + apps_list.splice(idx, 1); + } +} + +const exists = (flag) => process.argv.indexOf(flag) != -1 +const value = (flag) => (process.argv.indexOf(flag) != -1) ? process.argv[process.argv.indexOf(flag) + 1] : null; + +const files = exists("--files") ? value("--files").split(",") : false; +const build_for_app = exists("--app") ? value("--app") : null; +const concat = !exists("--no-concat"); + +if (!files) show_production_message(); ensure_js_css_dirs(); -concatenate_files(); +if (concat) concatenate_files(); create_build_file(); -if (build_for_app) { + +if (files) { + build_files(files); +} else if (build_for_app) { build_assets_for_app(build_for_app) .then(() => { run_build_command_for_app(build_for_app); @@ -48,11 +66,7 @@ function build_assets_for_app(app) { return build_assets(app) } -function build_assets(app) { - const options = get_options_for(app); - if (!options.length) return Promise.resolve(); - log(chalk.yellow(`\nBuilding ${app} assets...\n`)); - +function build_from_(options) { const promises = options.map(({ inputOptions, outputOptions, output_file}) => { return build(inputOptions, outputOptions) .then(() => { @@ -68,6 +82,23 @@ function build_assets(app) { }); } +function build_assets(app) { + const options = get_options_for(app); + if (!options.length) return Promise.resolve(); + log(chalk.yellow(`\nBuilding ${app} assets...\n`)); + return build_from_(options); +} + +function build_files(files, app="frappe") { + let ret; + for (let file of files) { + let options = get_options(file, app); + if (!options.length) return Promise.resolve(); + ret += build_from_(options); + } + return ret; +} + function build(inputOptions, outputOptions) { return rollup.rollup(inputOptions) .then(bundle => bundle.write(outputOptions)) diff --git a/rollup/config.js b/rollup/config.js index b1816cb4c6..cdeb8eb952 100644 --- a/rollup/config.js +++ b/rollup/config.js @@ -165,6 +165,31 @@ function get_rollup_options_for_css(output_file, input_files) { }; } +function get_options(file, app="frappe") { + const build_json = get_build_json(app); + if (!build_json) return []; + + return Object.keys(build_json) + .map(output_file => { + if (output_file === file) { + if (output_file.startsWith('concat:')) return null; + const input_files = build_json[output_file] + .map(input_file => { + let prefix = get_app_path(app); + if (input_file.startsWith('node_modules/')) { + prefix = path.resolve(get_app_path(app), '..'); + } + return path.resolve(prefix, input_file); + }); + return Object.assign( + get_rollup_options(output_file, input_files), { + output_file + }); + } + }) + .filter(Boolean); +} + function get_options_for(app) { const build_json = get_build_json(app); if (!build_json) return []; @@ -205,5 +230,6 @@ function ignore_css() { }; module.exports = { - get_options_for + get_options_for, + get_options }; diff --git a/yarn.lock b/yarn.lock index e5c975c357..dd28ad1a95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,13 +2,6 @@ # yarn lockfile v1 -"@arcanis/slice-ansi@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@arcanis/slice-ansi/-/slice-ansi-1.0.2.tgz#35331e41a1062e3c53c01ad2ec1555c5c1959d8f" - integrity sha512-lDL63z0W/L/WTgqrwVOuNyMAsTv+pvjybd21z9SWdStmQoXT59E/iVWwat3gYjcdTNBf6oHAMoyFm8dtjpXEYw== - dependencies: - grapheme-splitter "^1.0.4" - "@babel/code-frame@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" @@ -50,27 +43,6 @@ debug "^3.1.0" lodash.once "^4.1.1" -"@nodelib/fs.scandir@2.1.3": - version "2.1.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" - integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== - dependencies: - "@nodelib/fs.stat" "2.0.3" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" - integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" - integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== - dependencies: - "@nodelib/fs.scandir" "2.1.3" - fastq "^1.6.0" - "@sindresorhus/is@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" @@ -81,81 +53,58 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.1.tgz#ceff6a28a5b4867c2dd4a1ba513de278ccbe8bb1" integrity sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg== -"@sindresorhus/is@^3.0.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-3.1.2.tgz#548650de521b344e3781fbdb0ece4aa6f729afb8" - integrity sha512-JiX9vxoKMmu8Y3Zr2RVathBL1Cdu4Nt4MuNWemt1Nc06A0RAin9c5FArkhGsyMBWfCu4zj+9b+GxtjAnE4qqLQ== +"@snyk/cli-interface@2.9.1": + version "2.9.1" + resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.9.1.tgz#e0466d183e7d4a13112ba098b1702a0d628dd380" + integrity sha512-2zHRvEt4S0DO+hPRX3hp5ssELouJqgb/JUTmPDMr/32r//qooSTxojwSvAK2A6VYgYOHuo1S3VTpsSP/ywkPXA== + dependencies: + "@snyk/dep-graph" "1.19.4" + "@types/graphlib" "^2.1.7" + tslib "^1.9.3" -"@snyk/cli-interface@2.3.2", "@snyk/cli-interface@^2.0.3": +"@snyk/cli-interface@2.9.2", "@snyk/cli-interface@^2.9.1", "@snyk/cli-interface@^2.9.2": + version "2.9.2" + resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.9.2.tgz#defbeafd5fa7fa5ab1c39d57f1d379b2fbfc9860" + integrity sha512-C64bGtcQbh7941l7qgXFJ+FJIZdQtBHkPhKfGtUlCCMbC0FK0oaUmp6d7YPQxT4dEnkQdtlBT/eA2F6qIKbEng== + dependencies: + "@snyk/dep-graph" "1.19.4" + "@types/graphlib" "^2.1.7" + tslib "^1.9.3" + +"@snyk/cli-interface@^2.0.3": version "2.3.2" resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.3.2.tgz#e93afa82de15b912e657f1ba86f9d7963983e594" integrity sha512-jmZyxVHqzYU1GfdnWCGdd68WY/lAzpPVyqalHazPj4tFJehrSfEFc82RMTYAMgXEJuvFRFIwhsvXh3sWUhIQmg== dependencies: tslib "^1.9.3" -"@snyk/cli-interface@2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.6.1.tgz#1e4e7acf5f9aca1267ddf3c1bb98a469011c907a" - integrity sha512-3X+OwwwT9j0r2ObqxYIiAgdaHsTW71b92PN3wawGAxl4YgPRrRVw8Fouhe41I4WJsn7OlKUNedylZguvpYg9qw== +"@snyk/cocoapods-lockfile-parser@3.5.2": + version "3.5.2" + resolved "https://registry.yarnpkg.com/@snyk/cocoapods-lockfile-parser/-/cocoapods-lockfile-parser-3.5.2.tgz#7f441ecf2fb9c0b488db7e392447d2c520b60cef" + integrity sha512-fIiUNCmhDp7lVKTs/nHCnLK1roMkG15HhuQhtZXxiFW3EZ5H9IqMdtrxqjXuzVWt7X2h7lbF5OMBzD07NODtug== dependencies: - "@snyk/graphlib" "2.1.9-patch" - tslib "^1.9.3" - -"@snyk/cli-interface@2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.8.0.tgz#ee5b45b7c75942163875b29e712c44f9d7f36bb3" - integrity sha512-St/G39iJG1zQK15L24kcVYM2gmFc/ylBCcBqU2DMZKJKwOPccKLUO6s+dWIUXMccQ+DFS6TuHPvuAKQNi9C4Yg== - dependencies: - "@snyk/dep-graph" "1.19.0" - "@snyk/graphlib" "2.1.9-patch" - tslib "^1.9.3" - -"@snyk/cli-interface@2.8.1": - version "2.8.1" - resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.8.1.tgz#67a0c34e8473dd433d6c206c41fa8b0e12426621" - integrity sha512-pALcfgoY0hAavy/pBlDIqEu+FFC5m+D4bMnCwlQ26mObL/zzxp2+Ohx+HykCIom62u2J94SzAtRLFdm/2TgoOw== - dependencies: - "@snyk/dep-graph" "1.19.0" - "@snyk/graphlib" "2.1.9-patch" - tslib "^1.9.3" - -"@snyk/cocoapods-lockfile-parser@3.5.1": - version "3.5.1" - resolved "https://registry.yarnpkg.com/@snyk/cocoapods-lockfile-parser/-/cocoapods-lockfile-parser-3.5.1.tgz#572907ae12abf77c1ea3dd497f7d1ced31104aa7" - integrity sha512-0bzajH/HdP3k5cOZKUmT/xqmHZFuWN124c/lrqh+U6Q1Z9Bt7TLOB2ifLKL+1I4rq+IgOesGWJYG1KhxBy3RLw== - dependencies: - "@snyk/dep-graph" "1.19.3" - "@snyk/ruby-semver" "^3.0.0" + "@snyk/dep-graph" "1.19.4" "@types/js-yaml" "^3.12.1" js-yaml "^3.13.1" source-map-support "^0.5.7" tslib "^1.10.0" -"@snyk/composer-lockfile-parser@1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@snyk/composer-lockfile-parser/-/composer-lockfile-parser-1.4.0.tgz#a16fff515288496a27292b32b5cc14d7d84f9026" - integrity sha512-ga4YTRjJUuP0Ufr+t1IucwVjEFAv66JSBB/zVHP2zy/jmfA3l3ZjlGQSjsRC6Me9P2Z0esQ83AYNZvmIf9pq2w== +"@snyk/composer-lockfile-parser@^1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@snyk/composer-lockfile-parser/-/composer-lockfile-parser-1.4.1.tgz#2f7c93ad367520322b16d9490a208fec08445e0e" + integrity sha512-wNANv235j95NFsQuODIXCiQZ9kcyg9fz92Kg1zoGvaP3kN/ma7fgCnvQL/dyml6iouQJR5aZovjhrrfEFoKtiQ== dependencies: - "@snyk/lodash" "^4.17.15-patch" + lodash.findkey "^4.6.0" + lodash.get "^4.4.2" + lodash.invert "^4.3.0" + lodash.isempty "^4.4.0" -"@snyk/dep-graph@1.19.0": - version "1.19.0" - resolved "https://registry.yarnpkg.com/@snyk/dep-graph/-/dep-graph-1.19.0.tgz#dfb2699520225e715083f6dd590bb91b55e99ba1" - integrity sha512-/0phOICMk4hkX2KtZgi+4KNd5G9oYDIlxQDQk+ui2xl4gonPvK6Q5MFzHP7Xet1YY/XoU33ox41i+IO48qZ+zQ== +"@snyk/dep-graph@1.19.4", "@snyk/dep-graph@^1.19.3", "@snyk/dep-graph@^1.19.4": + version "1.19.4" + resolved "https://registry.yarnpkg.com/@snyk/dep-graph/-/dep-graph-1.19.4.tgz#d156c482bfdfba7505f1cf3e8a80b86dd9f45383" + integrity sha512-h3MMhjVm3BuIruwpDBqnMowKOG9viwr3TJHdIxTHulWKWSsPTTW1AAP3/RaK+UBp1y/Ua9yzeHncKIrzBdT5Nw== dependencies: - "@snyk/graphlib" "2.1.9-patch" - lodash.isequal "^4.5.0" - object-hash "^2.0.3" - semver "^6.0.0" - source-map-support "^0.5.19" - tslib "^2.0.0" - -"@snyk/dep-graph@1.19.3", "@snyk/dep-graph@^1.17.0", "@snyk/dep-graph@^1.19.0": - version "1.19.3" - resolved "https://registry.yarnpkg.com/@snyk/dep-graph/-/dep-graph-1.19.3.tgz#f20521baf060f83f052fd6b55fad8b377833418d" - integrity sha512-WJLUFKBokoFK5imi0t8Dkyj+uqtS/5Ziuf4oE/OOFX30UqP1ffMDkv9/3sqBJQVQ9FjdgsX3Cm8JZMtMlYRc6w== - dependencies: - "@snyk/graphlib" "2.1.9-patch.2" + graphlib "^2.1.8" lodash.isequal "^4.5.0" object-hash "^2.0.3" semver "^6.0.0" @@ -176,63 +125,15 @@ resolved "https://registry.yarnpkg.com/@snyk/gemfile/-/gemfile-1.2.0.tgz#919857944973cce74c650e5428aaf11bcd5c0457" integrity sha512-nI7ELxukf7pT4/VraL4iabtNNMz8mUo7EXlqCFld8O5z6mIMLX9llps24iPpaIZOwArkY3FWA+4t+ixyvtTSIA== -"@snyk/graphlib@2.1.9-patch": - version "2.1.9-patch" - resolved "https://registry.yarnpkg.com/@snyk/graphlib/-/graphlib-2.1.9-patch.tgz#2cf8b39fc879681569c8070776feaec8efe71442" - integrity sha512-uFO/pNMm3pN15QB+hVMU7uaQXhsBNwEA8lOET/VDcdOzLptODhXzkJqSHqt0tZlpdAz6/6Uaj8jY00UvPFgFMA== +"@snyk/java-call-graph-builder@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.13.2.tgz#6e4a9495d5c47bbab9bc69e066d4646473781b67" + integrity sha512-YN3a93ttscqFQRUeThrxa7i2SJkFPfYn0VpFqdPB6mIJz2fRVLxUkMtlCbG0aSEUvWiLnGVHN0IYxwWEzhq11w== dependencies: - "@snyk/lodash" "4.17.15-patch" - -"@snyk/graphlib@2.1.9-patch.2": - version "2.1.9-patch.2" - resolved "https://registry.yarnpkg.com/@snyk/graphlib/-/graphlib-2.1.9-patch.2.tgz#571255808f5bf291f42d51f1e1f0adc8f9145be9" - integrity sha512-BjJzOXDNzoEMBOjSks7vadu5f0c39SeorJMi9vUvvWM5dcE22CZqcN9VMRW5DYTifUJiCWszkm5TOyfYfB0bfg== - dependencies: - lodash.clone "^4.5.0" - lodash.constant "^3.0.0" - lodash.filter "^4.6.0" - lodash.foreach "^4.5.0" - lodash.has "^4.5.2" - lodash.isarray "^4.0.0" - lodash.isempty "^4.4.0" - lodash.isfunction "^3.0.9" - lodash.isundefined "^3.0.1" - lodash.keys "^4.2.0" - lodash.map "^4.6.0" - lodash.reduce "^4.6.0" - lodash.size "^4.2.0" - lodash.transform "^4.6.0" - lodash.union "^4.6.0" - lodash.values "^4.3.0" - -"@snyk/inquirer@6.2.2-patch": - version "6.2.2-patch" - resolved "https://registry.yarnpkg.com/@snyk/inquirer/-/inquirer-6.2.2-patch.tgz#14bfd111493eebdb8858f7ac4a98f8d274d10a91" - integrity sha512-IUq5bHRL0vtVKtfvd4GOccAIaLYHbcertug2UVZzk5+yY6R/CxfYsnFUTho1h4BdkfNdin2tPjE/5jRF4SKSrw== - dependencies: - "@snyk/lodash" "4.17.15-patch" - ansi-escapes "^3.2.0" - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^2.0.0" - mute-stream "0.0.7" - run-async "^2.2.0" - rxjs "^6.4.0" - string-width "^2.1.0" - strip-ansi "^5.0.0" - through "^2.3.6" - -"@snyk/java-call-graph-builder@1.13.1": - version "1.13.1" - resolved "https://registry.yarnpkg.com/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.13.1.tgz#641d0f929baa95cd30f8e16f938b4d21499a1e9b" - integrity sha512-oOCSIyOMplV73a1agcXKXlFYQftK5esUUaFRTf90GOxQwKy8R9tZtKdP+CdutlgvjRP286DQ+7GlvKYsGGZbWg== - dependencies: - "@snyk/graphlib" "2.1.9-patch" ci-info "^2.0.0" debug "^4.1.1" glob "^7.1.6" + graphlib "^2.1.8" jszip "^3.2.2" needle "^2.3.3" progress "^2.0.3" @@ -241,11 +142,6 @@ temp-dir "^2.0.0" tslib "^1.9.3" -"@snyk/lodash@4.17.15-patch", "@snyk/lodash@^4.17.15-patch": - version "4.17.15-patch" - resolved "https://registry.yarnpkg.com/@snyk/lodash/-/lodash-4.17.15-patch.tgz#fb61af14b75d10a20015b40af5d0423944af89dc" - integrity sha512-e4+t34bGyjjRnwXwI14hqye9J/nRbG9iwaqTgXWHskm5qC+iK0UrjgYdWXiHJCf3Plbpr+1rpW+4LPzZnCGMhQ== - "@snyk/rpm-parser@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@snyk/rpm-parser/-/rpm-parser-2.0.0.tgz#4ded7fa4b0a8efca7699359e4ca7a79bfbe38bc1" @@ -253,31 +149,14 @@ dependencies: event-loop-spinner "^2.0.0" -"@snyk/ruby-semver@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@snyk/ruby-semver/-/ruby-semver-2.2.0.tgz#dfb2f11c52e52f8273ec8750e9327db7c84e679c" - integrity sha512-FqUayoVjcyCsQFYPm3DcaCKdFR4xmapUkCGY+bcNBs3jqCUw687PoP9CPQ1Jvtaw5YpfBNl/62jyntsWCeciuA== +"@snyk/snyk-cocoapods-plugin@2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@snyk/snyk-cocoapods-plugin/-/snyk-cocoapods-plugin-2.5.1.tgz#af4e749ee1420c1596345fefae3c5eb48b84ecb0" + integrity sha512-A+1xHD+SpmXQa0p+dWmiApFZtz/y37qAW9aWmFx2B1j7fwRBf9Qr89/6RbJOznf1a4nEitjzE3fa98yNZk/MNg== dependencies: - "@snyk/lodash" "4.17.15-patch" - -"@snyk/ruby-semver@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@snyk/ruby-semver/-/ruby-semver-3.0.0.tgz#592e648343a88eccb7d787a97d3ad6c9df1dd61f" - integrity sha512-GoSRcwNuJ/mK3Q+tqelRJlylPh8K3RZRWh3ZpkOKm1gQPdG+z0wt+LipSIHxGR8yBDl5bQjwTrPLkL49/N1V6Q== - dependencies: - lodash.escaperegexp "^4.1.0" - lodash.flatten "^4.4.0" - lodash.uniq "^4.5.0" - tslib "^1.13.0" - -"@snyk/snyk-cocoapods-plugin@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@snyk/snyk-cocoapods-plugin/-/snyk-cocoapods-plugin-2.5.0.tgz#8dedb9d3165e5c8cd72ab13fdb49397083a15866" - integrity sha512-arK4VHzNh/D9vCFQFeAiSP+rMRXwLbzaRoIKucodf8Q/3KftIo/byeDmoc2Cc7awR1HPo5E391bwBNH5ra8UqA== - dependencies: - "@snyk/cli-interface" "2.6.1" - "@snyk/cocoapods-lockfile-parser" "3.5.1" - "@snyk/dep-graph" "^1.19.0" + "@snyk/cli-interface" "^2.9.2" + "@snyk/cocoapods-lockfile-parser" "3.5.2" + "@snyk/dep-graph" "^1.19.4" source-map-support "^0.5.7" tslib "^2.0.0" @@ -325,23 +204,15 @@ resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ== -"@types/emscripten@^1.38.0": - version "1.39.4" - resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.39.4.tgz#d61990c0cee72c4e475de737a140b51fe925a2c8" - integrity sha512-k3LLVMFrdNA9UCvMDPWMbFrGPNb+GcPyw29ktJTo1RCN7RmxFG5XzPZcPKRlnLuLT/FRm8wp4ohvDwNY7GlROQ== - "@types/estree@0.0.39": version "0.0.39" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== -"@types/glob@^7.1.1": - version "7.1.3" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" - integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== - dependencies: - "@types/minimatch" "*" - "@types/node" "*" +"@types/graphlib@^2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@types/graphlib/-/graphlib-2.1.7.tgz#e6a47a4f43511f5bad30058a669ce5ce93bfd823" + integrity sha512-K7T1n6U2HbTYu+SFHlBjz/RH74OA2D/zF1qlzn8uXbvB4uRg7knOM85ugS2bbXI1TXMh7rLqk4OVRwIwEBaixg== "@types/hosted-git-info@^2.7.0": version "2.7.0" @@ -365,11 +236,6 @@ dependencies: "@types/node" "*" -"@types/minimatch@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" - integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== - "@types/node@*": version "13.7.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.4.tgz#76c3cb3a12909510f52e5dc04a6298cdf9504ffd" @@ -380,11 +246,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-11.10.4.tgz#3f5fc4f0f322805f009e00ab35a2ff3d6b778e42" integrity sha512-wa09itaLE8L705aXd8F80jnFpxz3Y1/KRHfKsYL2bPc0XF+wEWu8sR9n5bmeu8Ba1N9z2GRNzm/YdHcghLkLKg== -"@types/node@^13.7.0": - version "13.13.15" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.15.tgz#fe1cc3aa465a3ea6858b793fd380b66c39919766" - integrity sha512-kwbcs0jySLxzLsa2nWUAGOd/s21WU1jebrEdtzhsj1D4Yps1EOuyI1Qcu+FD56dL7NRNIJtDDjcqIG22NwkgLw== - "@types/node@^6.14.4": version "6.14.9" resolved "https://registry.yarnpkg.com/@types/node/-/node-6.14.9.tgz#733583e21ef0eab85a9737dfafbaa66345a92ef0" @@ -417,13 +278,6 @@ resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47" integrity sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg== -"@types/xml2js@0.4.5": - version "0.4.5" - resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.5.tgz#d21759b056f282d9c7066f15bbf5c19b908f22fa" - integrity sha512-yohU3zMn0fkhlape1nxXG2bLEGZRc1FeqF80RoHaYXJN7uibaauXfhzhOJr1Xh36sn+/tx21QAOf07b/xYVk1w== - dependencies: - "@types/node" "*" - "@vue/component-compiler-utils@^1.2.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-1.3.1.tgz#686f0b913d59590ae327b2a1cb4b6d9b931bbe0e" @@ -465,98 +319,11 @@ postcss-modules-sync "^1.0.0" source-map "0.6.*" -"@yarnpkg/core@^2.0.0-rc.29": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@yarnpkg/core/-/core-2.1.1.tgz#637786eb2acd09b48db7af59f2287934a0a5afd7" - integrity sha512-qeBxz8nHjKAbGTP2ZcXBnXGfM7+cN0A73mIai/24uru1ayvCIgfjWL1uIj/MM+m+K5lJX0Dcn94ZBHWits9JWQ== - dependencies: - "@arcanis/slice-ansi" "^1.0.2" - "@yarnpkg/fslib" "^2.1.0" - "@yarnpkg/json-proxy" "^2.1.0" - "@yarnpkg/libzip" "^2.1.0" - "@yarnpkg/parsers" "^2.1.0" - "@yarnpkg/pnp" "^2.1.0" - "@yarnpkg/shell" "^2.1.0" - camelcase "^5.3.1" - chalk "^3.0.0" - ci-info "^2.0.0" - clipanion "^2.4.2" - cross-spawn "7.0.3" - diff "^4.0.1" - globby "^10.0.1" - got "^11.1.3" - json-file-plus "^3.3.1" - logic-solver "^2.0.1" - micromatch "^4.0.2" - mkdirp "^0.5.1" - p-limit "^2.2.0" - pluralize "^7.0.0" - pretty-bytes "^5.1.0" - semver "^7.1.2" - stream-to-promise "^2.2.0" - tar "^4.4.6" - tslib "^1.13.0" - tunnel "^0.0.6" - -"@yarnpkg/fslib@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/fslib/-/fslib-2.1.0.tgz#c7039ec369a8fde06932cbddb3d3e473d789c458" - integrity sha512-E+f8w5yQZnTf1soyTWy7qdf+GmHsY+A0yEN4Di44/Txk6XRIMruyc1ShDi93mOI6ilnXxD87rNms18zJ8WnspA== - dependencies: - "@yarnpkg/libzip" "^2.1.0" - tslib "^1.13.0" - -"@yarnpkg/json-proxy@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/json-proxy/-/json-proxy-2.1.0.tgz#362a161678cd7dda74b47b4fc848a2f1730d16cd" - integrity sha512-rOgCg2DkyviLgr80mUMTt9vzdf5RGOujQB26yPiXjlz4WNePLBshKlTNG9rKSoKQSOYEQcw6cUmosfOKDatrCw== - dependencies: - "@yarnpkg/fslib" "^2.1.0" - tslib "^1.13.0" - -"@yarnpkg/libzip@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/libzip/-/libzip-2.1.0.tgz#f45d861a853555a063f90a85142199e7cf181b71" - integrity sha512-39c7KuSWcYUqVxlBLZwfqdD/D6lS+jplNVWd6uAnk8EpnacaYGJRegvkqWyfw5c8KHukNMeEGF5JHrXPZYBM0w== - dependencies: - "@types/emscripten" "^1.38.0" - tslib "^1.13.0" - -"@yarnpkg/lockfile@^1.0.2", "@yarnpkg/lockfile@^1.1.0": +"@yarnpkg/lockfile@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== -"@yarnpkg/parsers@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/parsers/-/parsers-2.1.0.tgz#52615acdb54803d6404d6fb8f15fa124936bff1f" - integrity sha512-75OYQ6PMs1C3zm+W+T1xhLyVDX78zXQGEVHpWd4o/QwpAbhneB3/5FXVGRzI3gjPPWWSb/pKOPB1S6p0xmQD2Q== - dependencies: - js-yaml "^3.10.0" - tslib "^1.13.0" - -"@yarnpkg/pnp@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/pnp/-/pnp-2.1.0.tgz#0eba881ccd4675594198e7f1e327fe11a845291b" - integrity sha512-b8NlB71EFifv1jDX47nFaRXrykROxHcS7YuGb2dQ+Gp9gqJ0thIaZ3yB9+qWF8acdWtNcMpjCug4xkfAAR5Odw== - dependencies: - "@types/node" "^13.7.0" - "@yarnpkg/fslib" "^2.1.0" - tslib "^1.13.0" - -"@yarnpkg/shell@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/shell/-/shell-2.1.0.tgz#e0855e59d4df663d8fe9044950a7fd390110cd4b" - integrity sha512-9i9ZWqeKHGV0DOfdxTVq5zl73Li8Fg947v57uLBEaytNF+HywkDfouNkg/6HfgBrpI0WH8OJ9Pz/uDaE5cpctw== - dependencies: - "@yarnpkg/fslib" "^2.1.0" - "@yarnpkg/parsers" "^2.1.0" - clipanion "^2.4.2" - cross-spawn "7.0.3" - fast-glob "^3.2.2" - stream-buffers "^3.0.2" - tslib "^1.13.0" - abbrev@1, abbrev@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -637,7 +404,7 @@ ansi-align@^3.0.0: dependencies: string-width "^3.0.0" -ansi-escapes@3.2.0, ansi-escapes@^3.2.0: +ansi-escapes@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== @@ -647,6 +414,13 @@ ansi-escapes@^1.0.0: resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" integrity sha1-06ioOzGapneTZisT52HHkRQiMG4= +ansi-escapes@^4.2.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" + integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== + dependencies: + type-fest "^0.11.0" + ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -692,11 +466,6 @@ ansicolors@^0.3.2: resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk= -any-promise@^1.1.0, any-promise@~1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" - integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= - aproba@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -757,11 +526,6 @@ array-flatten@1.1.1: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" @@ -1038,13 +802,6 @@ braces@^2.3.1: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - browserify-zlib@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d" @@ -1264,6 +1021,14 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -1279,11 +1044,6 @@ child-process@^1.0.2: resolved "https://registry.yarnpkg.com/child-process/-/child-process-1.0.2.tgz#98974dc7ed1ee4c6229f8e305fa7313a6885a7f2" integrity sha1-mJdNx+0e5MYin44wX6cxOmiFp/I= -chownr@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - ci-info@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" @@ -1323,12 +1083,12 @@ cli-cursor@^1.0.2: dependencies: restore-cursor "^1.0.1" -cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" - integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== dependencies: - restore-cursor "^2.0.0" + restore-cursor "^3.1.0" cli-spinner@0.2.10: version "0.2.10" @@ -1348,15 +1108,10 @@ cli-truncate@^0.2.1: slice-ansi "0.0.4" string-width "^1.0.1" -cli-width@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" - integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= - -clipanion@^2.4.2: - version "2.4.4" - resolved "https://registry.yarnpkg.com/clipanion/-/clipanion-2.4.4.tgz#d70244c6f60feb3f4cbd509d2fcbe829fc619061" - integrity sha512-KjyCBz8xplftHjIK/nOqq/9b3hPlXbAAo/AxoITrO4yySpQ6a9QSJDAfOx9PfcRUHteeqbdNxZKSPfeFqQ7plg== +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== cliui@^3.0.3, cliui@^3.2.0: version "3.2.0" @@ -1611,15 +1366,6 @@ cosmiconfig@^5.0.0: lodash.get "^4.4.2" parse-json "^4.0.0" -cross-spawn@7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - cross-spawn@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" @@ -2054,13 +1800,6 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - docker-modem@2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-2.1.3.tgz#15432225f63db02eb5de4bb9a621b7293e5f264d" @@ -2071,12 +1810,12 @@ docker-modem@2.1.3: split-ca "^1.0.1" ssh2 "^0.8.7" -dockerfile-ast@0.0.19: - version "0.0.19" - resolved "https://registry.yarnpkg.com/dockerfile-ast/-/dockerfile-ast-0.0.19.tgz#b1e21138eba995d7bf5576dc30ba1130c15995c3" - integrity sha512-iDRNFeAB2j4rh/Ecc2gh3fjciVifCMsszfCfHlYF5Wv8yybjZLiRDZUBt/pS3xrAz8uWT8fCHLq4pOQMmwCDwA== +dockerfile-ast@0.0.30: + version "0.0.30" + resolved "https://registry.yarnpkg.com/dockerfile-ast/-/dockerfile-ast-0.0.30.tgz#74cbcd65e389852d752c5687117255ea622583ad" + integrity sha512-QOeP5NjbjoSLtnMz6jzBLsrKtywLEVPoCOAwA54cQpulyKb1gBnZ63tr6Amq8oVDvu5PXa3aifBVw+wcoCGHKg== dependencies: - vscode-languageserver-types "^3.5.0" + vscode-languageserver-types "^3.15.1" dom-serializer@0: version "0.1.1" @@ -2113,13 +1852,14 @@ dot-prop@^5.2.0: dependencies: is-obj "^2.0.0" -dotnet-deps-parser@4.10.0: - version "4.10.0" - resolved "https://registry.yarnpkg.com/dotnet-deps-parser/-/dotnet-deps-parser-4.10.0.tgz#f51c1e36700c9bf992263adb478e0e6f0095c4f0" - integrity sha512-dEO1oTvreaDCtcvhRdOmmAMubyC+MWqVr1c/1Wvasi+NW4NZeB67qGh1taqowUFh+aCXtPw3SP2eExn6aNkhwA== +dotnet-deps-parser@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/dotnet-deps-parser/-/dotnet-deps-parser-5.0.0.tgz#5115c442cbefea59e4fb9f9ed8fa4863a0f3186d" + integrity sha512-1l9K4UnQQHSfKgeHeLrxnB53AidCZqPyf9dkRL4/fZl8//NPiiDD43zHtgylw8DHlO7gvM8+O5a0UPHesNYZKw== dependencies: - "@snyk/lodash" "4.17.15-patch" - "@types/xml2js" "0.4.5" + lodash.isempty "^4.4.0" + lodash.set "^4.3.2" + lodash.uniq "^4.5.0" source-map-support "^0.5.7" tslib "^1.10.0" xml2js "0.4.23" @@ -2209,13 +1949,6 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" -end-of-stream@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.1.0.tgz#e9353258baa9108965efc41cb0ef8ade2f3cfb07" - integrity sha1-6TUyWLqpEIll78QcsO+K3i88+wc= - dependencies: - once "~1.3.0" - engine.io-client@~3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.4.0.tgz#82a642b42862a9b3f7a188f41776b2deab643700" @@ -2414,13 +2147,6 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -event-loop-spinner@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/event-loop-spinner/-/event-loop-spinner-1.1.0.tgz#96de9c70e6e2b0b3e257b0901e25e792e3c9c8d0" - integrity sha512-YVFs6dPpZIgH665kKckDktEVvSBccSYJmoZUfhNUdv5d3Xv+Q+SKF4Xis1jolq9aBzuW1ZZhQh/m/zU/TPdDhw== - dependencies: - tslib "^1.10.0" - event-loop-spinner@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/event-loop-spinner/-/event-loop-spinner-2.0.0.tgz#3aa949264a7d80be45d6cb782ab5e97ffc87b9e1" @@ -2620,18 +2346,6 @@ fast-diff@1.2.0: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== -fast-glob@^3.0.3, fast-glob@^3.2.2: - version "3.2.4" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" - integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.0" - merge2 "^1.3.0" - micromatch "^4.0.2" - picomatch "^2.2.1" - fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -2647,13 +2361,6 @@ fastparse@^1.1.1: resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== -fastq@^1.6.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" - integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q== - dependencies: - reusify "^1.0.4" - fd-slicer@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" @@ -2676,10 +2383,10 @@ figures@^1.7.0: escape-string-regexp "^1.0.5" object-assign "^4.1.0" -figures@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" - integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== dependencies: escape-string-regexp "^1.0.5" @@ -2698,13 +2405,6 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - finalhandler@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" @@ -2812,13 +2512,6 @@ fs-extra@5.0.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-minipass@^1.2.5: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" - integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== - dependencies: - minipass "^2.6.0" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -2977,13 +2670,6 @@ gl-vec3@^1.0.3: resolved "https://registry.yarnpkg.com/gl-vec3/-/gl-vec3-1.1.3.tgz#a47c62f918774a06cbed1b65bcd0288ecbb03826" integrity sha512-jduKUqT0SGH02l8Yl+mV1yVsDfYgQAJyXGxkJQGyxPLHRiW25DwVIRPt6uvhrEMHftJfqhqKthRcyZqNEl9Xdw== -glob-parent@^5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== - dependencies: - is-glob "^4.0.1" - glob@^7.0.0, glob@^7.0.3, glob@^7.1.2, glob@^7.1.3, glob@^7.1.6, glob@~7.1.1: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -3030,20 +2716,6 @@ global-prefix@^1.0.1: is-windows "^1.0.1" which "^1.2.14" -globby@^10.0.1: - version "10.0.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543" - integrity sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg== - dependencies: - "@types/glob" "^7.1.1" - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.0.3" - glob "^7.1.3" - ignore "^5.1.1" - merge2 "^1.2.3" - slash "^3.0.0" - globule@^1.0.0: version "1.2.1" resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.1.tgz#5dffb1b191f22d20797a9369b49eab4e9839696d" @@ -3070,23 +2742,6 @@ got@11.4.0: p-cancelable "^2.0.0" responselike "^2.0.0" -got@^11.1.3: - version "11.5.2" - resolved "https://registry.yarnpkg.com/got/-/got-11.5.2.tgz#772e3f3a06d9c7589c7c94dc3c83cdb31ddbf742" - integrity sha512-yUhpEDLeuGiGJjRSzEq3kvt4zJtAcjKmhIiwNp/eUs75tRlXfWcHo5tcBaMQtnjHWC7nQYT5HkY/l0QOQTkVww== - dependencies: - "@sindresorhus/is" "^3.0.0" - "@szmarczak/http-timer" "^4.0.5" - "@types/cacheable-request" "^6.0.1" - "@types/responselike" "^1.0.0" - cacheable-lookup "^5.0.3" - cacheable-request "^7.0.1" - decompress-response "^6.0.0" - http2-wrapper "^1.0.0-beta.5.0" - lowercase-keys "^2.0.0" - p-cancelable "^2.0.0" - responselike "^2.0.0" - got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -3109,12 +2764,7 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== -grapheme-splitter@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" - integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== - -graphlib@^2.1.8: +graphlib@2.1.8, graphlib@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A== @@ -3348,7 +2998,7 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -http2-wrapper@^1.0.0-beta.4.5, http2-wrapper@^1.0.0-beta.5.0: +http2-wrapper@^1.0.0-beta.4.5: version "1.0.0-beta.5.2" resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz#8b923deb90144aea65cf834b016a340fc98556f3" integrity sha512-xYz9goEyBnC8XwXDTuC/MZ6t+MrKVQZOk4s7+PaDkwIsQd8IwqvM+0M6bA/2lvG8GHXcPdf+MejTUeO2LCPCeQ== @@ -3386,11 +3036,6 @@ ieee754@^1.1.4: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== -ignore@^5.1.1: - version "5.1.8" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" - integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== - image-size@~0.5.0: version "0.5.5" resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" @@ -3483,6 +3128,25 @@ ini@^1.3.0, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== +inquirer@^7.3.3: + version "7.3.3" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" + integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.19" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.6.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" @@ -3657,7 +3321,7 @@ is-extendable@^1.0.1: dependencies: is-plain-object "^2.0.4" -is-extglob@^2.1.0, is-extglob@^2.1.1: +is-extglob@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= @@ -3691,13 +3355,6 @@ is-glob@^3.0.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - is-gzip@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-gzip/-/is-gzip-1.0.0.tgz#6ca8b07b99c77998025900e555ced8ed80879a83" @@ -3746,11 +3403,6 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - is-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" @@ -3885,11 +3537,6 @@ is-yarn-global@^0.3.0: resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== -is@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/is/-/is-3.3.0.tgz#61cff6dd3c4193db94a3d62582072b44e5645d79" - integrity sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg== - isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -3965,7 +3612,7 @@ js-tokens@^4.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.10.0: +js-yaml@^3.12.0, js-yaml@^3.13.1, js-yaml@^3.9.0: version "3.14.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== @@ -3973,22 +3620,6 @@ js-yaml@^3.10.0: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^3.12.0, js-yaml@^3.9.0: - version "3.12.2" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.2.tgz#ef1d067c5a9d9cb65bd72f285b5d8105c77f14fc" - integrity sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -js-yaml@^3.13.1: - version "3.13.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" - integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - jsbarcode@^3.9.0: version "3.11.0" resolved "https://registry.yarnpkg.com/jsbarcode/-/jsbarcode-3.11.0.tgz#20623e008b101ef45d0cce9c8022cdf49be28547" @@ -4014,17 +3645,6 @@ json-buffer@3.0.1: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== -json-file-plus@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/json-file-plus/-/json-file-plus-3.3.1.tgz#f4363806b82819ff8803d83d539d6a9edd2a5258" - integrity sha512-wo0q1UuiV5NsDPQDup1Km8IwEeqe+olr8tkWxeJq9Bjtcp7DZ0l+yrg28fSC3DEtrE311mhTZ54QGS6oiqnZEA== - dependencies: - is "^3.2.1" - node.extend "^2.0.0" - object.assign "^4.1.0" - promiseback "^2.0.2" - safer-buffer "^2.0.2" - json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -4067,17 +3687,7 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jszip@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.3.0.tgz#29d72c21a54990fa885b11fc843db320640d5271" - integrity sha512-EJ9k766htB1ZWnsV5ZMDkKLgA+201r/ouFF8R2OigVjVdcm2rurcBrrdXaeqBJbqnUVMko512PYmlncBKE1Huw== - dependencies: - lie "~3.3.0" - pako "~1.0.2" - readable-stream "~2.3.6" - set-immediate-shim "~1.0.1" - -jszip@^3.2.2: +jszip@3.4.0, jszip@^3.2.2: version "3.4.0" resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.4.0.tgz#1a69421fa5f0bb9bc222a46bca88182fba075350" integrity sha512-gZAOYuPl4EhPTXT0GjhI3o+ZAz3su6EhLrKUoAivcKqyqC7laS5JEv4XWZND9BgcDcF83vI85yGbDmDR6UhrIg== @@ -4281,20 +3891,10 @@ lodash.clonedeep@^4.3.0, lodash.clonedeep@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= -lodash.constant@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash.constant/-/lodash.constant-3.0.0.tgz#bfe05cce7e515b3128925d6362138420bd624910" - integrity sha1-v+Bczn5RWzEokl1jYhOEIL1iSRA= - -lodash.escaperegexp@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" - integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c= - -lodash.filter@^4.6.0: +lodash.findkey@^4.6.0: version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" - integrity sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4= + resolved "https://registry.yarnpkg.com/lodash.findkey/-/lodash.findkey-4.6.0.tgz#83058e903b51cbb759d09ccf546dea3ea39c4718" + integrity sha1-gwWOkDtRy7dZ0JzPVG3qPqOcRxg= lodash.flatmap@^4.5.0: version "4.5.0" @@ -4306,25 +3906,15 @@ lodash.flatten@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= -lodash.foreach@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" - integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM= - lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= -lodash.has@^4.5.2: - version "4.5.2" - resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" - integrity sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI= - -lodash.isarray@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-4.0.0.tgz#2aca496b28c4ca6d726715313590c02e6ea34403" - integrity sha1-KspJayjEym1yZxUxNZDALm6jRAM= +lodash.invert@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.invert/-/lodash.invert-4.3.0.tgz#8ffe20d4b616f56bea8f1aa0c6ebd80dcf742aee" + integrity sha1-j/4g1LYW9WvqjxqgxuvYDc90Ku4= lodash.isempty@^4.4.0: version "4.4.0" @@ -4336,26 +3926,6 @@ lodash.isequal@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= -lodash.isfunction@^3.0.9: - version "3.0.9" - resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" - integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw== - -lodash.isundefined@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz#23ef3d9535565203a66cefd5b830f848911afb48" - integrity sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g= - -lodash.keys@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-4.2.0.tgz#a08602ac12e4fb83f91fc1fb7a360a4d9ba35205" - integrity sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU= - -lodash.map@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" - integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM= - lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -4371,51 +3941,31 @@ lodash.once@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= -lodash.reduce@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" - integrity sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs= - lodash.set@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM= -lodash.size@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.size/-/lodash.size-4.2.0.tgz#71fe75ed3eabdb2bcb73a1b0b4f51c392ee27b86" - integrity sha1-cf517T6r2yvLc6GwtPUcOS7ie4Y= - lodash.topairs@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.topairs/-/lodash.topairs-4.3.0.tgz#3b6deaa37d60fb116713c46c5f17ea190ec48d64" integrity sha1-O23qo31g+xFnE8RsXxfqGQ7EjWQ= -lodash.transform@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.transform/-/lodash.transform-4.6.0.tgz#12306422f63324aed8483d3f38332b5f670547a0" - integrity sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A= - -lodash.union@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" - integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= - lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash.values@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" - integrity sha1-o6bCsOvsxcLLocF+bmIP6BtT00c= - lodash@4.17.15, lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +lodash@^4.17.19, lodash@^4.17.20: + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" + integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + log-symbols@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" @@ -4438,13 +3988,6 @@ log-update@^1.0.2: ansi-escapes "^1.0.0" cli-cursor "^1.0.2" -logic-solver@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/logic-solver/-/logic-solver-2.0.1.tgz#e9fa47002eb5d8cda7616d41639b97552eb674be" - integrity sha1-6fpHAC612M2nYW1BY5uXVS62dL4= - dependencies: - underscore "^1.7.0" - loud-rejection@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" @@ -4585,11 +4128,6 @@ merge-stream@^1.0.1: dependencies: readable-stream "^2.0.1" -merge2@^1.2.3, merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - methods@^1.1.1, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -4614,14 +4152,6 @@ micromatch@^3.1.10: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== - dependencies: - braces "^3.0.1" - picomatch "^2.0.5" - mime-db@1.40.0: version "1.40.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" @@ -4651,10 +4181,10 @@ mime@1.6.0, mime@^1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mimic-fn@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" - integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" @@ -4683,21 +4213,6 @@ minimist@1.2.0, minimist@^1.1.3, minimist@^1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= -minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" - integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minizlib@^1.2.1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" - integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== - dependencies: - minipass "^2.9.0" - mixin-deep@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" @@ -4745,10 +4260,10 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -mute-stream@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" - integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== nan@^2.13.2: version "2.14.0" @@ -4803,19 +4318,19 @@ ndarray@^1.0.13, ndarray@^1.0.18: iota-array "^1.0.0" is-buffer "^1.0.2" -needle@^2.3.3: - version "2.4.1" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.1.tgz#14af48732463d7475696f937626b1b993247a56a" - integrity sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g== +needle@2.5.0, needle@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.5.0.tgz#e6fc4b3cc6c25caed7554bd613a5cf0bac8c31c0" + integrity sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA== dependencies: debug "^3.2.6" iconv-lite "^0.4.4" sax "^1.2.4" -needle@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.5.0.tgz#e6fc4b3cc6c25caed7554bd613a5cf0bac8c31c0" - integrity sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA== +needle@^2.3.3: + version "2.4.1" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.1.tgz#14af48732463d7475696f937626b1b993247a56a" + integrity sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g== dependencies: debug "^3.2.6" iconv-lite "^0.4.4" @@ -4894,14 +4409,6 @@ node-sass@^4.13.1: stdout-stream "^1.4.0" "true-case-path" "^1.0.2" -node.extend@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/node.extend/-/node.extend-2.0.2.tgz#b4404525494acc99740f3703c496b7d5182cc6cc" - integrity sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ== - dependencies: - has "^1.0.3" - is "^3.2.1" - "nopt@2 || 3": version "3.0.6" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" @@ -5088,24 +4595,17 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -once@~1.3.0: - version "1.3.3" - resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20" - integrity sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA= - dependencies: - wrappy "1" - onetime@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k= -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: - mimic-fn "^1.0.0" + mimic-fn "^2.1.0" open@^7.0.3: version "7.0.3" @@ -5192,13 +4692,6 @@ p-limit@^2.0.0: dependencies: p-try "^2.0.0" -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - p-locate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" @@ -5366,11 +4859,6 @@ path-key@^2.0.0, path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -5390,11 +4878,6 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - peek-stream@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/peek-stream/-/peek-stream-1.1.3.tgz#3b35d84b7ccbbd262fff31dc10da56856ead6d67" @@ -5414,11 +4897,6 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.5, picomatch@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== - pify@^2.0.0, pify@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -5441,11 +4919,6 @@ pinkie@^2.0.0: resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= -pluralize@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" - integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== - pngjs@^3.3.3: version "3.4.0" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" @@ -5860,11 +5333,6 @@ prettier@^1.13.0: resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.4.tgz#73e37e73e018ad2db9c76742e2647e21790c9717" integrity sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g== -pretty-bytes@^5.1.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2" - integrity sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg== - process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -5875,33 +5343,18 @@ progress@^2.0.3: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== -promise-deferred@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/promise-deferred/-/promise-deferred-2.0.3.tgz#b99c9588820798501862a593d49cece51d06fd7f" - integrity sha512-n10XaoznCzLfyPFOlEE8iurezHpxrYzyjgq/1eW9Wk1gJwur/N7BdBmjJYJpqMeMcXK4wEbzo2EvZQcqjYcKUQ== - dependencies: - promise "^7.3.1" - promise.series@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/promise.series/-/promise.series-0.2.0.tgz#2cc7ebe959fc3a6619c04ab4dbdc9e452d864bbd" integrity sha1-LMfr6Vn8OmYZwEq029yeRS2GS70= -"promise@>=3.2 <8", promise@^7.1.1, promise@^7.3.1: +"promise@>=3.2 <8", promise@^7.1.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== dependencies: asap "~2.0.3" -promiseback@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/promiseback/-/promiseback-2.0.3.tgz#bd468d86930e8cd44bfc3292de9a6fbafb6378e6" - integrity sha512-VZXdCwS0ppVNTIRfNsCvVwJAaP2b+pxQF7lM8DMWfmpNWyTxB6O5YNbzs+8z0ki/KIBHKHk308NTIl4kJUem3w== - dependencies: - is-callable "^1.1.5" - promise-deferred "^2.0.3" - proxy-addr@~2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" @@ -6430,12 +5883,12 @@ restore-cursor@^1.0.1: exit-hook "^1.0.0" onetime "^1.0.0" -restore-cursor@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" - integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== dependencies: - onetime "^2.0.0" + onetime "^5.1.0" signal-exit "^3.0.2" ret@~0.1.10: @@ -6443,11 +5896,6 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - rgb-regex@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" @@ -6568,17 +6016,10 @@ rollup@^1.2.2: "@types/node" "^11.9.5" acorn "^6.1.1" -run-async@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" - integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= - dependencies: - is-promise "^2.1.0" - -run-parallel@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" - integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== rxjs@^5.0.0-beta.11: version "5.5.12" @@ -6587,10 +6028,10 @@ rxjs@^5.0.0-beta.11: dependencies: symbol-observable "1.0.1" -rxjs@^6.4.0: - version "6.5.4" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c" - integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q== +rxjs@^6.6.0: + version "6.6.3" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552" + integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ== dependencies: tslib "^1.9.0" @@ -6666,11 +6107,6 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.1.2: - version "7.3.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" - integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== - semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -6752,23 +6188,11 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - showdown@^1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/showdown/-/showdown-1.9.1.tgz#134e148e75cd4623e09c21b0511977d79b5ad0ef" @@ -6801,11 +6225,6 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - slice-ansi@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" @@ -6846,16 +6265,7 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" -snyk-config@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/snyk-config/-/snyk-config-3.1.0.tgz#61dec9296bff8d7459c46bd3f1e6c0ffcf7eea5f" - integrity sha512-3UlyogA67/9WOssJ7s4d7gqWQRWyO/LbgdBBNMhhmFDKa7eTUSW+A782CVHgyDSJZ2kNANcMWwMiOL+h3p6zQg== - dependencies: - "@snyk/lodash" "4.17.15-patch" - debug "^4.1.1" - nconf "^0.10.0" - -snyk-config@^3.0.0: +snyk-config@3.1.1, snyk-config@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/snyk-config/-/snyk-config-3.1.1.tgz#a511ef8bf769545f0564e09d382b5ea3aacb9c6a" integrity sha512-wwrMIEDozfLJ8LmakCsCC1FQ0siIX5icCQPCbUKKgRbeVsZ27NjPJs37BpTXX4rcHkaWpe8TbH3yOtp23qmszg== @@ -6864,28 +6274,31 @@ snyk-config@^3.0.0: lodash.merge "^4.6.2" nconf "^0.10.0" -snyk-cpp-plugin@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/snyk-cpp-plugin/-/snyk-cpp-plugin-1.2.0.tgz#1e554eece61d40288bbc745f040c2ca54ec7e08a" - integrity sha512-zc2h0IAAjcxJwxWp4rauliwEUXZahmC3WoCgjTmRi36UhzwlxWJf8QpIeSOKJnc3t0NhlKzUc1YFkaQGJ2dXVQ== +snyk-cpp-plugin@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/snyk-cpp-plugin/-/snyk-cpp-plugin-1.5.0.tgz#2ec2068fdcf5e579eb7d9b9eed8bb984fd00a925" + integrity sha512-nBZ0cBmpT4RVJUFzYydQJOxwjcdXk7NtRJE1UIIOafQa2FcvIl3GBezfrCJ6pu61svOAf5r8Qi/likx6F15K1A== dependencies: + "@snyk/dep-graph" "^1.19.3" + chalk "^4.1.0" + debug "^4.1.1" tslib "^2.0.0" -snyk-docker-plugin@3.17.0: - version "3.17.0" - resolved "https://registry.yarnpkg.com/snyk-docker-plugin/-/snyk-docker-plugin-3.17.0.tgz#9f48f9d4455b15dc15fdfc37137c567e9c9e24e2" - integrity sha512-Pq0xr/NQQUss3bLb0rTfVtsot/9ID6TzZCWlF9nMrj9JFrJRrJPQxi/KO/uqfX0Nbwz4VysXDzwdWNtrxpaabg== +snyk-docker-plugin@3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/snyk-docker-plugin/-/snyk-docker-plugin-3.21.0.tgz#a92074c0411578c1a7b86852a06f1421770e985d" + integrity sha512-A7oJS3QGR7bwm1qeeczCb8PDfi8go1KM6VWph/drJHBQ7JxVKKLb3j4AzrMmIM96mGZFbmyNOL4pznwumaOM8g== dependencies: "@snyk/rpm-parser" "^2.0.0" "@snyk/snyk-docker-pull" "^3.2.0" debug "^4.1.1" docker-modem "2.1.3" - dockerfile-ast "0.0.19" + dockerfile-ast "0.0.30" event-loop-spinner "^2.0.0" gunzip-maybe "^1.4.2" mkdirp "^1.0.4" semver "^6.1.0" - snyk-nodejs-lockfile-parser "1.22.0" + snyk-nodejs-lockfile-parser "1.28.1" tar-stream "^2.1.0" tmp "^0.2.1" tslib "^1" @@ -6899,25 +6312,25 @@ snyk-go-parser@1.4.1: toml "^3.0.0" tslib "^1.10.0" -snyk-go-plugin@1.16.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/snyk-go-plugin/-/snyk-go-plugin-1.16.0.tgz#4688e45e59d1673256f645fccca2fa6a7ab0f55a" - integrity sha512-XNGHEFyP+pCzcqmXnj5T/1Oy6AZzm2WkTSuUpohWQ/09ecMRCCv2yrr/kwMQemrKN4+7CoJS/9xfm3GnNlzVHA== +snyk-go-plugin@1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/snyk-go-plugin/-/snyk-go-plugin-1.16.2.tgz#aa3d57fee79d4f2c6bb2282ec94609123fd2ed1d" + integrity sha512-FAM56z3bl1iuxeqkCEA/jyZ2hpwkQK8xQxQbhR+QppEK5lole7w1PQyWYgZAJ9oRY/BU32zdRAJwGuZbhk7G2Q== dependencies: - "@snyk/dep-graph" "1.19.3" - "@snyk/graphlib" "2.1.9-patch" + "@snyk/dep-graph" "1.19.4" debug "^4.1.1" + graphlib "2.1.8" snyk-go-parser "1.4.1" - tmp "0.2.0" + tmp "0.2.1" tslib "^1.10.0" -snyk-gradle-plugin@3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/snyk-gradle-plugin/-/snyk-gradle-plugin-3.5.1.tgz#a424a7b8136471296e971e7b25f4776d1a08b9ff" - integrity sha512-8tZwQCqRbjp1azvc+bBRXSbn2AjbUpFAM6qoSpM/IZpfGl1RaOtz4/JTkGFxj+iBhTFoAkGxEunT66eO0pHZZw== +snyk-gradle-plugin@3.6.3: + version "3.6.3" + resolved "https://registry.yarnpkg.com/snyk-gradle-plugin/-/snyk-gradle-plugin-3.6.3.tgz#484059bcb98469b6a674bbcbdc995eafb5581041" + integrity sha512-j/eQSLSsK3DHmvVX2fNig4+ugYrKlCOV8Xvo6OYFkNzhMpdyNFiGWTS1uyP1HH75Gyc78MaLANMgjlSYePukzQ== dependencies: - "@snyk/cli-interface" "2.8.0" - "@snyk/dep-graph" "^1.17.0" + "@snyk/cli-interface" "2.9.1" + "@snyk/dep-graph" "^1.19.4" "@types/debug" "^4.1.4" chalk "^3.0.0" debug "^4.1.1" @@ -6950,43 +6363,27 @@ snyk-module@^2.0.2: debug "^3.1.0" hosted-git-info "^2.7.1" -snyk-mvn-plugin@2.19.1: - version "2.19.1" - resolved "https://registry.yarnpkg.com/snyk-mvn-plugin/-/snyk-mvn-plugin-2.19.1.tgz#00b68a459a4da9e39b0d8c416ae05496b3b03ee3" - integrity sha512-VXYJSdhUmOQAyxdsv5frAKbi3UOcHPabWEQxQ9wxhVBEEmx2lP5ajv1a+ntxwWwL7u3jdc+rnCIKHpLlQJ5nyw== +snyk-mvn-plugin@2.19.4: + version "2.19.4" + resolved "https://registry.yarnpkg.com/snyk-mvn-plugin/-/snyk-mvn-plugin-2.19.4.tgz#4e29fa82b9ca409789d441939c766797d6a2360f" + integrity sha512-kYPUKOugnNd31PFqx1YHJTo90pospELYHME4AzBx8dkMDgs5ZPjAmQXSxegQ3AMUqfqcETMSTzlKHe6uHujI8A== dependencies: - "@snyk/cli-interface" "2.8.1" - "@snyk/java-call-graph-builder" "1.13.1" + "@snyk/cli-interface" "2.9.1" + "@snyk/java-call-graph-builder" "1.13.2" debug "^4.1.1" needle "^2.5.0" tmp "^0.1.0" tslib "1.11.1" -snyk-nodejs-lockfile-parser@1.22.0: - version "1.22.0" - resolved "https://registry.yarnpkg.com/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.22.0.tgz#213e4a9bfda45b6af490d71a511b63e46b64a07f" - integrity sha512-l6jLoJxqcIIkQopSdQuAstXdMw5AIgLu+uGc5CYpHyw8fYqOwna8rawwofNeGuwJAAv4nEiNiexeYaR88OCq6Q== +snyk-nodejs-lockfile-parser@1.28.1: + version "1.28.1" + resolved "https://registry.yarnpkg.com/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.28.1.tgz#9eda1354bbca1fc881a4e63a1e1042f80c37bff2" + integrity sha512-0zbmtidYLI2ia/DQD4rZm2YKrhfHLvHlVBdF2cMAGPwhOoKW5ovG9eBO4wNQdvjxNi7b4VeUyAj8SfuhjDraDQ== dependencies: - "@snyk/graphlib" "2.1.9-patch" - "@snyk/lodash" "^4.17.15-patch" - "@yarnpkg/lockfile" "^1.0.2" - event-loop-spinner "^1.1.0" - p-map "2.1.0" - snyk-config "^3.0.0" - source-map-support "^0.5.7" - tslib "^1.9.3" - uuid "^3.3.2" - -snyk-nodejs-lockfile-parser@1.27.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.27.0.tgz#cde4d052ac429c5fa2844c981578f5ff5dea3022" - integrity sha512-s1yEqEC8uxELzEXqXmnWKpoOIn0rM8StMjuy0X5U+FmxGvO3jRtFDU5hcVw9cAULLfrQYBHRowrO/5FaR79fxA== - dependencies: - "@snyk/graphlib" "2.1.9-patch" - "@yarnpkg/core" "^2.0.0-rc.29" "@yarnpkg/lockfile" "^1.1.0" event-loop-spinner "^2.0.0" got "11.4.0" + graphlib "2.1.8" lodash.clonedeep "^4.5.0" lodash.flatmap "^4.5.0" lodash.isempty "^4.4.0" @@ -6999,15 +6396,15 @@ snyk-nodejs-lockfile-parser@1.27.0: uuid "^3.3.2" yaml "^1.9.2" -snyk-nuget-plugin@1.18.1: - version "1.18.1" - resolved "https://registry.yarnpkg.com/snyk-nuget-plugin/-/snyk-nuget-plugin-1.18.1.tgz#5296ee7b3ee96fc8186fa157f71697b804173a60" - integrity sha512-Bq+IzbyewxIrUhgdFaDKS5wCNixERC7QBitKsZGM3uCOr9fJM8rr5qg5SS9UIU7eyeKvzuVO/V1yDzjo1cKvUw== +snyk-nuget-plugin@1.19.3: + version "1.19.3" + resolved "https://registry.yarnpkg.com/snyk-nuget-plugin/-/snyk-nuget-plugin-1.19.3.tgz#5b4d9a5a61a543810c98bd4e67b9f6b1d95e3c3a" + integrity sha512-KwKoMumwcXVz/DQH80ifXfX7CTnm29bmHJ2fczjCGohxLGb4EKBGQtA3t7K98O7lTISQGgXDxnWIaM9ZXkxPdw== dependencies: - "@snyk/lodash" "4.17.15-patch" debug "^4.1.1" - dotnet-deps-parser "4.10.0" - jszip "3.3.0" + dotnet-deps-parser "5.0.0" + jszip "3.4.0" + lodash "^4.17.20" snyk-paket-parser "1.6.0" tslib "^1.11.2" xml2js "^0.4.17" @@ -7019,13 +6416,13 @@ snyk-paket-parser@1.6.0: dependencies: tslib "^1.9.3" -snyk-php-plugin@1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/snyk-php-plugin/-/snyk-php-plugin-1.9.0.tgz#9cbb0439f251325aff7b4cda5f22797e8541df9a" - integrity sha512-uORrEoC47dw0ITZYu5vKqQtmXnbbQs+ZkWeo5bRHGdf10W8e4rNr1S1R4bReiLrSbSisYhVHeFMkdOAiLIPJVQ== +snyk-php-plugin@1.9.2: + version "1.9.2" + resolved "https://registry.yarnpkg.com/snyk-php-plugin/-/snyk-php-plugin-1.9.2.tgz#282ef733060aab49da23e1fb2d2dd1af8f71f7cd" + integrity sha512-IQcdsQBqqXVRY5DatlI7ASy4flbhtU2V7cr4P2rK9rkFnVHO6LHcitwKXVZa9ocdOmpZDzk7U6iwHJkVFcR6OA== dependencies: - "@snyk/cli-interface" "2.3.2" - "@snyk/composer-lockfile-parser" "1.4.0" + "@snyk/cli-interface" "^2.9.1" + "@snyk/composer-lockfile-parser" "^1.4.1" tslib "1.11.1" snyk-policy@1.14.1: @@ -7110,19 +6507,15 @@ snyk-try-require@1.3.1, snyk-try-require@^1.1.1, snyk-try-require@^1.3.1: lru-cache "^4.0.0" then-fs "^2.0.0" -snyk@^1.382.0: - version "1.382.0" - resolved "https://registry.yarnpkg.com/snyk/-/snyk-1.382.0.tgz#5625931dd066fca406f2d35663eb7280d57565bd" - integrity sha512-lZsuJ6HUVNO7ZYDQm6B2gPdcXiRtd8bCQUre8xYMZu1VakKwrCCNsJbxo91z3gK5aYqempjmTe7+qH2CksXZ+Q== +snyk@1.398.1, snyk@^1.398.1: + version "1.398.1" + resolved "https://registry.yarnpkg.com/snyk/-/snyk-1.398.1.tgz#19aec8dfffa60e7412e6309117e96b2cfa960355" + integrity sha512-jH24ztdJY8DQlqkd1z8n/JutdOqHtTPccCynM2hfOedW20yAp9c108LFjXvqBEk/EH3YyNmWzyLkkHOySeDkwQ== dependencies: - "@snyk/cli-interface" "2.8.1" - "@snyk/dep-graph" "1.19.3" + "@snyk/cli-interface" "2.9.2" + "@snyk/dep-graph" "1.19.4" "@snyk/gemfile" "1.2.0" - "@snyk/graphlib" "2.1.9-patch" - "@snyk/inquirer" "6.2.2-patch" - "@snyk/lodash" "^4.17.15-patch" - "@snyk/ruby-semver" "2.2.0" - "@snyk/snyk-cocoapods-plugin" "2.5.0" + "@snyk/snyk-cocoapods-plugin" "2.5.1" abbrev "^1.1.1" ansi-escapes "3.2.0" chalk "^2.4.2" @@ -7131,22 +6524,25 @@ snyk@^1.382.0: debug "^4.1.1" diff "^4.0.1" glob "^7.1.3" - needle "^2.5.0" + graphlib "^2.1.8" + inquirer "^7.3.3" + lodash "^4.17.20" + needle "2.5.0" open "^7.0.3" os-name "^3.0.0" proxy-agent "^3.1.1" proxy-from-env "^1.0.0" semver "^6.0.0" - snyk-config "3.1.0" - snyk-cpp-plugin "1.2.0" - snyk-docker-plugin "3.17.0" - snyk-go-plugin "1.16.0" - snyk-gradle-plugin "3.5.1" + snyk-config "3.1.1" + snyk-cpp-plugin "1.5.0" + snyk-docker-plugin "3.21.0" + snyk-go-plugin "1.16.2" + snyk-gradle-plugin "3.6.3" snyk-module "3.1.0" - snyk-mvn-plugin "2.19.1" - snyk-nodejs-lockfile-parser "1.27.0" - snyk-nuget-plugin "1.18.1" - snyk-php-plugin "1.9.0" + snyk-mvn-plugin "2.19.4" + snyk-nodejs-lockfile-parser "1.28.1" + snyk-nuget-plugin "1.19.3" + snyk-php-plugin "1.9.2" snyk-policy "1.14.1" snyk-python-plugin "1.17.1" snyk-resolve "1.0.1" @@ -7390,37 +6786,16 @@ stdout-stream@^1.4.0: dependencies: readable-stream "^2.0.1" -stream-buffers@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-3.0.2.tgz#5249005a8d5c2d00b3a32e6e0a6ea209dc4f3521" - integrity sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ== - stream-shift@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -stream-to-array@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/stream-to-array/-/stream-to-array-2.3.0.tgz#bbf6b39f5f43ec30bc71babcb37557acecf34353" - integrity sha1-u/azn19D7DC8cbq8s3VXrOzzQ1M= - dependencies: - any-promise "^1.1.0" - stream-to-observable@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.1.0.tgz#45bf1d9f2d7dc09bed81f1c307c430e68b84cffe" integrity sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4= -stream-to-promise@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/stream-to-promise/-/stream-to-promise-2.2.0.tgz#b1edb2e1c8cb11289d1b503c08d3f2aef51e650f" - integrity sha1-se2y4cjLESidG1A8CNPyrvUeZQ8= - dependencies: - any-promise "~1.3.0" - end-of-stream "~1.1.0" - stream-to-array "~2.3.0" - streamsearch@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" @@ -7440,7 +6815,7 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2", string-width@^2.1.0: +"string-width@^1.0.2 || 2": version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -7690,19 +7065,6 @@ tar@^2.0.0: fstream "^1.0.12" inherits "2" -tar@^4.4.6: - version "4.4.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" - integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.8.6" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" - temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" @@ -7784,13 +7146,6 @@ tmp@0.1.0, tmp@^0.1.0: dependencies: rimraf "^2.6.3" -tmp@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.0.tgz#fdc6a78d2a77bf7e426ca476d8a4f82eefcf648c" - integrity sha512-spsb5g6EiPmteS5TcOAECU3rltCMDMp4VMU2Sb0+WttN4qGobEkMAd+dkr1cubscN08JGNDX765dPbGImbG7MQ== - dependencies: - rimraf "^3.0.0" - tmp@0.2.1, tmp@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" @@ -7823,13 +7178,6 @@ to-regex-range@^2.1.0: is-number "^3.0.0" repeat-string "^1.6.1" -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - to-regex@^3.0.1, to-regex@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" @@ -7909,11 +7257,6 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -tunnel@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" - integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== - tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -7926,6 +7269,11 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" +type-fest@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" + integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== + type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" @@ -7951,11 +7299,6 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -underscore@^1.7.0: - version "1.10.2" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.10.2.tgz#73d6aa3668f3188e4adb0f1943bd12cfd7efaaaf" - integrity sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg== - unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -8150,7 +7493,7 @@ vlq@^1.0.0: resolved "https://registry.yarnpkg.com/vlq/-/vlq-1.0.0.tgz#8101be90843422954c2b13eb27f2f3122bdcc806" integrity sha512-o3WmXySo+oI5thgqr7Qy8uBkT/v9Zr+sRyrh1lr8aWPUkgDWdWt4Nae2WKBrLsocgE8BuWWD0jLc+VW8LeU+2g== -vscode-languageserver-types@^3.5.0: +vscode-languageserver-types@^3.15.1: version "3.15.1" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de" integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ== @@ -8228,13 +7571,6 @@ which@1, which@^1.2.14, which@^1.2.9: dependencies: isexe "^2.0.0" -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" @@ -8358,7 +7694,7 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: +yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==