diff --git a/.browserslistrc b/.browserslistrc deleted file mode 100644 index 03696bd4..00000000 --- a/.browserslistrc +++ /dev/null @@ -1 +0,0 @@ -extends browserslist-config-nk diff --git a/.editorconfig b/.editorconfig index 54bfdd15..8b5972e0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,13 +5,9 @@ root = true [*] charset = utf-8 end_of_line = lf -indent_size = 2 insert_final_newline = true trim_trailing_whitespace = true -indent_style = space - -[*.php] -indent_size = 4 +indent_style = tab [*.yml] indent_style = space diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..7bb3f348 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +assets/vendor/** diff --git a/.eslintrc.js b/.eslintrc.js index 87d69583..83f68de1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,15 +1,12 @@ module.exports = { - extends: ['eslint-config-nk'], - globals: { - wp: true, - }, - settings: { - react: { - pragma: 'wp', - }, - }, - rules: { - 'react/prefer-stateless-function': 'off', - 'react/prop-types': 'off', - }, + extends: ['plugin:@wordpress/eslint-plugin/recommended'], + rules: { + '@wordpress/no-unsafe-wp-apis': 0, + '@wordpress/i18n-translator-comments': 0, + 'jsdoc/no-undefined-types': 0, + 'jsdoc/require-param-type': 0, + 'jsdoc/require-returns-description': 0, + 'react-hooks/rules-of-hooks': 0, + 'jsdoc/check-param-names': 0, + }, }; diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index bd129854..c74441b6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,9 +1,5 @@ name: Deploy to WordPress.org -env: - COMPOSER_VERSION: '2' - COMPOSER_CACHE: '${{ github.workspace }}/.composer-cache' - on: workflow_dispatch: push: @@ -14,38 +10,24 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Set Cache Directories - run: | - composer config -g cache-dir "${{ env.COMPOSER_CACHE }}" - - - name: Prepare Composer Cache - uses: actions/cache@v2 - with: - path: ${{ env.COMPOSER_CACHE }} - key: composer-${{ env.COMPOSER_VERSION }}-${{ hashFiles('**/composer.lock') }} - restore-keys: | - composer-${{ env.COMPOSER_VERSION }}- + - uses: actions/checkout@v3 - - name: Set PHP Version + - name: Disable xDebug - fixes PHP Fatal Error for `i18n make-pot` uses: shivammathur/setup-php@v2 with: php-version: '7.4' coverage: none - tools: composer:v2 - name: Setup Node.js and install dependencies uses: ./.github/setup-node - name: Run Production Task - run: npm run production + run: npm run build:prod - name: WordPress Plugin Deploy uses: nk-o/action-wordpress-plugin-deploy@master env: SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} SVN_USERNAME: ${{ secrets.SVN_USERNAME }} - SOURCE_DIR: dist/visual-portfolio/ + SOURCE_DIR: dist-zip/visual-portfolio/ SLUG: visual-portfolio diff --git a/.gitignore b/.gitignore index f8cc78c9..683bccc4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ .publish .idea /dist +/dist-zip +/build /vendor /node_modules npm-debug.log diff --git a/.prettierignore b/.prettierignore index cdf85cb7..6f84398e 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,6 +4,10 @@ **/*.php **/*.yml node_modules +assets/vendor +templates/**/*.css dist +dist-zip +build vendor vendors diff --git a/.prettierrc.js b/.prettierrc.js index 1727b1c6..e596d24c 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1 +1 @@ -module.exports = require('prettier-config-nk'); +module.exports = require('@wordpress/prettier-config'); diff --git a/.stylelintignore b/.stylelintignore index 3c61439f..9ad52b19 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -3,6 +3,10 @@ **/*.min.css **/*.build.css node_modules +assets/vendor +templates/**/*.css dist +duist-zip +build vendor vendors diff --git a/.stylelintrc.js b/.stylelintrc.js index b82b088a..6e2e23ac 100644 --- a/.stylelintrc.js +++ b/.stylelintrc.js @@ -1,3 +1,25 @@ module.exports = { - extends: ['stylelint-config-nk'], + extends: '@wordpress/stylelint-config/scss', + rules: { + 'at-rule-empty-line-before': null, + 'at-rule-no-unknown': null, + 'comment-empty-line-before': null, + 'font-weight-notation': null, + 'max-line-length': null, + 'no-descending-specificity': null, + 'rule-empty-line-before': null, + 'selector-class-pattern': null, + 'value-keyword-case': null, + 'scss/operator-no-unspaced': null, + 'scss/selector-no-redundant-nesting-selector': null, + 'scss/at-import-partial-extension': null, + 'scss/no-global-function-names': null, + 'scss/comment-no-empty': null, + 'scss/at-extend-no-missing-placeholder': null, + 'scss/operator-no-newline-after': null, + 'scss/at-if-closing-brace-newline-after': null, + 'scss/at-else-empty-line-before': null, + 'scss/at-if-closing-brace-space-after': null, + 'no-invalid-position-at-import-rule': null, + }, }; diff --git a/.vscode/settings.json b/.vscode/settings.json index a6550636..0eca5245 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,8 +4,8 @@ "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[php]": { - "editor.defaultFormatter": null, - "editor.formatOnSave": false + "editor.formatOnSave": false, + "editor.defaultFormatter": null }, "editor.formatOnSave": true, @@ -26,5 +26,11 @@ ], "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" - } + }, + + "search.exclude": { + "**/.git": true, + "**/node_modules": true, + "**/build": true + }, } diff --git a/.wp-env.json b/.wp-env.json index 9d0cc5ca..c4e779aa 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -1,13 +1,15 @@ { - "core": null, - "themes": ["https://downloads.wordpress.org/theme/twentytwentythree.zip"], - "plugins": ["."], - "env": { - "tests": { - "mappings": { - "wp-content/plugins/visual-portfolio": ".", - "wp-content/plugins/gutenberg-test-plugin-disables-the-css-animations": "./tests/plugins/gutenberg-test-plugin-disables-the-css-animations" - } - } - } + "core": null, + "themes": ["./tests/themes/empty-theme"], + "plugins": [ + ".", + "./tests/plugins/gutenberg-test-plugin-disables-the-css-animations" + ], + "env": { + "tests": { + "mappings": { + "wp-content/themes/empty-theme-php": "./tests/themes/empty-theme-php" + } + } + } } diff --git a/src/LICENSE.txt b/LICENSE.txt similarity index 100% rename from src/LICENSE.txt rename to LICENSE.txt diff --git a/assets/admin/css/elementor.scss b/assets/admin/css/elementor.scss new file mode 100644 index 00000000..9e02d9fb --- /dev/null +++ b/assets/admin/css/elementor.scss @@ -0,0 +1,13 @@ +/** + * Elementor Preview Styles + */ +.visual-portfolio-elementor-preview { + position: relative; + overflow: hidden; + pointer-events: none; + + iframe { + max-width: none; + min-height: 20px; + } +} diff --git a/assets/admin/css/style.scss b/assets/admin/css/style.scss new file mode 100644 index 00000000..dd5bd1c6 --- /dev/null +++ b/assets/admin/css/style.scss @@ -0,0 +1,1154 @@ +/*! + * Name : Visual Portfolio + * Author : nK https://nkdev.info + */ + +@use "sass:math"; +@use "sass:string"; +@use "sass:color"; + +$escaped-svg-characters: ( + ("<", "%3c"), + (">", "%3e"), + ("#", "%23"), + ("(", "%28"), + (")", "%29"), +) !default; + +// Replace `$search` with `$replace` in `$string` +// Used on our SVG icon backgrounds for custom forms. +// - +// @author Hugo Giraudel +// @param {String} $string - Initial string +// @param {String} $search - Substring to replace +// @param {String} $replace ('') - New value +// @return {String} - Updated string +@function str-replace($string, $search, $replace: "") { + $index: string.index($string, $search); + + @if $index { + @return string.slice($string, 1, $index - 1) + $replace + str-replace(string.slice($string, $index + string.length($search)), $search, $replace); + } + + @return $string; +} + +// See https://codepen.io/kevinweber/pen/dXWoRw +// - +// Requires the use of quotes around data URIs. + +@function escape-svg($string) { + @if string.index($string, "data:image/svg+xml") { + @each $char, $encoded in $escaped-svg-characters { + $string: str-replace($string, $char, $encoded); + } + } + + @return $string; +} + +/* + * Go Pro menu link + */ +#adminmenu a[href*="https://visualportfolio.co/pricing/"], +.wp-list-table.plugins a[href*="https://visualportfolio.co/pricing/"], +.vpf-admin-toolbar a[href*="https://visualportfolio.co/pricing/"] { + font-weight: 700; + color: #11b916; + + .dashicons { + transition: none; + } + + &:hover, + &:focus { + color: #22e429; + } +} + +/** + * Plugin Icon + */ +.dashicons-visual-portfolio, +.mce-widget .mce-i-visual-portfolio { + background-image: url(../../../assets/admin/images/icon.svg); + background-repeat: no-repeat; + background-position: center center; + background-size: 18px; + opacity: 0.6; +} + +.mce-widget .mce-i-visual-portfolio { + background-image: url(../images/icon-mce.svg); + opacity: 1; +} + +.menu-top.current .dashicons-visual-portfolio, +.menu-top:hover .dashicons-visual-portfolio, +.wp-has-current-submenu .dashicons-visual-portfolio { + opacity: 1; +} + +/** + * Visual Composer Icon + */ +.vc_element-icon[data-is-container="true"].icon-visual-portfolio, +.vc_element-icon.icon-visual-portfolio { + background-position: 50% 50%; +} + +.vc_element-icon.icon-visual-portfolio, +.vc_control-visual-portfolio { + position: relative; + overflow: hidden; + background-image: url(../images/icon-gutenberg.svg); + background-position: 50% 50%; + background-size: cover; + border-radius: 3px; +} + +.vc_add-element-container .icon-visual-portfolio { + position: absolute; +} + +.vc_control-visual-portfolio { + display: inline-block; + width: 18px; + height: 18px; + margin: 0 2px; + cursor: pointer; +} + +.vc_controls-row .vc_control-visual-portfolio { + float: right; + margin: 4px; +} + +.vc_control-visual-portfolio-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 2; +} + +/* + * Admin Menu. + * We need to use additional ID `#toplevel...` because when disabled + * Portfolio Archive in settings, the menu ID changes. + */ +/* stylelint-disable-next-line selector-id-pattern */ +#menu-posts-portfolio, +#toplevel_page_visual-portfolio-settings { // stylelint-disable-line selector-id-pattern + #adminmenu & ul.wp-submenu-wrap li { + clear: both; + } + + li:not(:last-child) a[href^="edit-tags.php?taxonomy=portfolio_tag&post_type=portfolio"]::after, + li:not(:last-child) a[href^="edit.php?post_type=portfolio&page=vpf_proofing_page"]::after, + li:not(:last-child) a[href^="edit.php?post_type=vp_proofing"]::after { + display: block; + float: left; + width: calc(100% + 26px); + margin: 13px -15px 8px; + content: ""; + border-bottom: 1px solid rgba(255, 255, 255, 10%); + + @media screen and (max-width: 782px) { + width: calc(100% + 30px); + margin: 20px -20px 8px; + } + } +} + +/* + * Admin Toolbar. + */ +.vpf-admin-toolbar { + background-color: #fff; + border-bottom: 1px solid #d7dbde; + + @media screen and (max-width: 600px) { + display: none; + } + + #wpcontent & { + padding-left: 20px; + margin-left: -20px; + } + + h2 { + display: inline-block; + padding: 5px 0; + margin: 0 10px 0 0; + font-size: 14px; + line-height: 2.5714; + + i { + display: inline-block; + width: 20px; + height: 20px; + margin-top: -5px; + margin-right: 5px; + vertical-align: middle; + filter: invert(1); + opacity: 1; + } + } + + .vpf-admin-toolbar-tab { + display: inline-block; + padding: 5px 10px; + margin: 0 2px; + font-size: 14px; + line-height: 2.5714; + color: inherit; + text-decoration: none; + + &.is-active { + padding-bottom: 2px; + border-bottom: #007cba solid 3px; + } + + &:hover, + &:focus { + color: #007cba; + } + + .dashicons { + line-height: 2; + } + } +} + +/** + * oEmbed preview + */ +.vp-oembed-preview { + position: relative; + width: 100%; + padding-top: 56.25%; + color: #a2a2a2; + background-color: #f1f1f1; + border: 1px solid #e8e8e8; + + &::after { + position: absolute; + top: 50%; + left: 50%; + z-index: 1; + display: block; + width: 20px; + height: 20px; + margin-top: -10px; + margin-left: -10px; + /* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */ + font-family: dashicons; + font-size: 20px; + font-style: normal; + font-weight: 400; + line-height: 1; + text-align: center; + text-decoration: inherit; + content: "\f126"; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + > iframe { + position: absolute; + top: 0; + left: 0; + z-index: 2; + width: 100%; + height: 100%; + } +} + +/** + * Post featured image focal point meta. + */ +.vpf-post-image-focal-point-panel { + .components-focal-point-picker_position-display-container { + .components-base-control__label { + max-width: 100%; + } + + .components-base-control { + margin-bottom: 0; + } + } + + // hide default image preview. + ~ .editor-post-featured-image .editor-post-featured-image__preview { + display: none; + } +} + +/** + * Form inputs + */ +.vp-input { + width: 100%; +} + +/** + * Portfolio Post Type + */ +.vp-portfolio__thumbnail { + position: relative; + display: block; + max-width: 70px; + padding-top: 100%; + overflow: hidden; + border-radius: 3px; + + img { + position: absolute; + top: 0; + left: 0; + display: block; + width: 100%; + height: 100%; + object-fit: cover; + } +} + +.vp-portfolio__thumbnail:empty { + background-color: #f1f1f1; + border: 1px solid #e8e8e8; + + &, + &:hover, + &:active, + &:focus { + color: #a2a2a2; + } + + &::after { + position: absolute; + top: 50%; + left: 50%; + display: block; + width: 18px; + height: 18px; + margin-top: -9px; + margin-left: -9px; + content: ""; + background-image: url("#{escape-svg('data:image/svg+xml,')}"); + background-size: cover; + } +} + +.wp-list-table { + th.column-portfolio_post_thumbs { + width: 70px; + } + + th.column-vp_lists_post_icon { + width: 28px; + } + + th.column-vp_lists_post_shortcode { + width: 250px; + } + + @media screen and (max-width: 782px) { + /* Hide column on mobile device */ + th.column-portfolio_post_thumbs, + th.column-vp_lists_post_icon { + display: none; + } + + tr:not(.inline-edit-row, .no-items) { + td.column-portfolio_post_thumbs, + td.column-vp_lists_post_icon { + float: left; + width: 70px !important; + min-width: 70px; + } + + td.column-vp_lists_post_icon { + width: 40px !important; + } + + td.column-portfolio_post_thumbs::before { + content: none; + } + + td.column-title { + overflow: hidden; + clear: right; + } + } + } +} + +/** + * Admin Notices + */ +.vpf-admin-notice { + display: flex; + padding: 0; + border: none; + border-left: 4px solid #2540cc; + box-shadow: 0 0 4px rgba(0, 0, 0, 15%); + + h3 { + margin-top: 10px; + margin-bottom: 12px; + font-size: 16px; + + svg { + display: inline-block; + width: 1.3em; + height: 1.3em; + margin-right: 0.2em; + vertical-align: -0.28em; + } + } + + .vpf-admin-notice-icon { + display: flex; + justify-content: center; + width: 50px; + min-width: 50px; + padding-top: 14px; + background-color: #f2f8ff; + } + + .dashicons-visual-portfolio { + display: block; + width: 22px; + height: 22px; + filter: invert(1); + background-size: 22px; + opacity: 1; + } + + .vpf-admin-notice-content { + padding: 6px 16px; + } +} + +/** + * Portfolio List page + */ +.vp-portfolio-list__icon { + position: relative; + display: block; + max-width: 70px; + padding-top: 100%; + overflow: hidden; + color: inherit; + color: #868686; + background-color: #f1f1f1; + border: 1px solid #e8e8e8; + border-radius: 3px; + + &:hover, + &:active, + &:focus { + color: #5f5f5f; + } + + svg { + position: absolute; + top: 15%; + left: 15%; + display: block; + width: 70%; + height: 70%; + object-fit: cover; + } +} + +/** + * Settings + */ +.portfolio_page_visual-portfolio-settings, +.toplevel_page_visual-portfolio-settings { + $settings_nav_width: 220px !default; + $settings_content_height: 340px !default; + + // Navigation. + @media (min-width: 782px) { + h2.nav-tab-wrapper { + position: relative; + margin-right: -1px; + border: none; + } + + .nav-tab-wrapper { + display: flex; + flex-direction: column; + float: left; + width: $settings_nav_width; + + .nav-tab { + padding: 10px 15px; + margin-bottom: -1px; + margin-left: 0; + background: none; + border: 1px solid transparent; + border-right: none; + border-left: 2px solid transparent; + } + + .nav-tab:hover, + .nav-tab:focus, + .nav-tab-active { + background: #fff; + border-color: #d7dbde; + border-left-color: #007cba; + } + } + + .metabox-holder { + float: left; + width: calc(100% - #{$settings_nav_width + 2}); + // Compensate 10px padding + 2px border. + min-height: $settings_content_height + 22px; + margin-top: 9px; + + > div, + > div > form { + min-height: $settings_content_height; + } + } + } + + .nav-tab-wrapper { + svg { + width: 1.2em; + height: 1.2em; + margin-right: 0.5em; + vertical-align: -0.2em; + } + } + + // Content with settings. + .metabox-holder { + box-sizing: border-box; + padding: 10px 20px; + background-color: #fff; + border: 1px solid #d7dbde; + + form { + display: flex; + flex-direction: column; + } + + // Footer with save button. + .metabox-holder-footer { + position: sticky; + bottom: 0; + padding: 15px 20px; + margin-top: auto; + margin-right: -20px; + margin-bottom: -10px; + margin-left: -20px; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + + > p { + padding: 0; + margin: 0; + } + } + } + + // Controls base layout. + .form-table { + tr { + display: block; + } + + th { + display: block; + width: 100%; + padding: 0; + margin-bottom: 10px; + } + + td { + display: block; + padding: 0; + margin-bottom: 20px; + } + } + + .submit { + margin: 0; + } + + .description { + opacity: 0.7; + } + + // Section Title. + .vpf-setting-type-section_title { + &::before { + display: block; + padding-top: 15px; + margin-right: -20px; + margin-left: -20px; + content: ""; + border-top: 1px solid #d7dbde; + } + + label { + font-size: 15px; + } + + label:empty { + display: block; + margin-top: -20px; + } + + .description { + margin-top: -5px; + } + } + + // Image control. + .wpsa-image-remove { + display: none; + margin-left: 10px; + } + + // Notices styles fix. + .metabox-holder .notice { + padding: 15px; + } + + // Pro control. + .vpf-settings-control-pro { + > td { + pointer-events: none; + } + + .description { + opacity: 0.3; + } + + .vpf-settings-control-pro-label { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + margin-left: 10px; + font-family: sans-serif; + font-size: 11px; + line-height: 18px; + color: #fff; + text-align: center; + text-decoration: none; + cursor: pointer; + background-color: #4c4c4c; + border-radius: 10px; + + > span { + position: absolute; + bottom: 100%; + z-index: 9990; + display: none; + width: 130px; + padding: 10px; + margin-bottom: 10px; + background-color: #000; + border-radius: 4px; + box-shadow: 0 1px 7px rgba(#000, 0.3); + } + + &:hover > span, + &:focus > span { + display: block; + } + } + + // Hide pro control info from individual breakpoint settings. + &.breakpoint_xs .vpf-settings-control-pro-label, + &.breakpoint_sm .vpf-settings-control-pro-label, + &.breakpoint_md .vpf-settings-control-pro-label, + &.breakpoint_lg .vpf-settings-control-pro-label, + &.breakpoint_xl .vpf-settings-control-pro-label { + display: none; + } + } + + /** + * Toggle Field. + **/ + .vp-toggle-field { + position: relative; + display: inline-block; + + input { + width: 0; + height: 0; + opacity: 0; + } + + .vp-toggle-field-slider-round { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 36px; + height: 18px; + cursor: pointer; + background-color: #ccc; + border-radius: 34px; + transition: 0.4s; + + &::before { + position: absolute; + bottom: 3px; + left: 3px; + width: 12px; + height: 12px; + content: ""; + background-color: #fff; + border-radius: 50%; + transition: 0.4s; + } + } + + input:checked + .vp-toggle-field-slider-round { + background-color: #007cba; + } + + input:focus + .vp-toggle-field-slider-round { + box-shadow: 0 0 1px #007cba; + } + + input:checked + .vp-toggle-field-slider-round::before { + transform: translateX(18px); + } + + .description { + margin-left: 22px; + } + } + + /** + * Range Field. + **/ + .vp-range-field { + width: 183px; + height: 3px; + vertical-align: middle; + cursor: pointer; + background: #007cba; + outline: none; + box-shadow: none; + appearance: none; + + &::-webkit-slider-thumb { + width: 20px; + height: 20px; + background: #fff; + border: 1px solid #7e8993; + border-radius: 20px; + transition: border-width 0.2s cubic-bezier(0.26, 0.08, 0.15, 1); + appearance: none; + } + + &:active::-webkit-slider-thumb { + border-color: #007cba; + box-shadow: 0 0 0 1px #007cba; + } + } + + .vp-range-number-field { + box-sizing: border-box; + width: 55px; + margin-left: 9px; + vertical-align: middle; + background: #fff; + border: 1px solid #8f969f; + border-radius: 4px; + } + + /** + * Select2 Field + */ + .select2-container { + .select2-selection--single { + width: 350px; + height: 30px; + border: 1px solid #8c8f94; + } + } +} + +/** + * PRO Notices. + */ +.portfolio_page_visual-portfolio-settings, +.toplevel_page_visual-portfolio-settings, +.portfolio_page_vpf_proofing_page { + // Settings info Pro. + .social_pro_info > th { + display: none; + } +} + +.vpf-pro-note { + position: relative; + max-width: 280px; + padding: 20px; + font-size: 13px; + color: #b65e96; + background: linear-gradient(to left, rgba(#743ad5, 20%), rgba(#d53a9d, 20%)); + + &::before { + position: absolute; + top: 1px; + right: 1px; + bottom: 1px; + left: 1px; + z-index: 0; + display: block; + content: ""; + background-color: #fff6fc; + } + + > * { + position: relative; + z-index: 1; + } + + h3 { + margin-top: 0; + margin-bottom: 0; + font-size: 13px; + font-weight: 600; + color: #d53a9d; + text-transform: none; + } + + p { + font-size: inherit !important; + } + + ul { + margin: 0; + list-style: none; + + li::before { + display: inline; + margin-bottom: 5px; + content: "- "; + } + } + + .vpf-pro-note-description, + .components-base-control__help { + margin: 1em 0; + } + + &-button { + display: inline-block; + padding: 7px 15px; + text-decoration: none; + background: linear-gradient(to left, #743ad5, #d53a9d); + border-radius: 3px; + transition: 0.2s filter ease, 0.2s transform ease; + + &, + &:hover, + &:focus, + &:active { + color: #fff; + } + + &:hover, + &:focus { + filter: contrast(1.5) drop-shadow(0 3px 3px rgba(213, 58, 157, 30%)); + transform: translateY(-1px); + } + } +} + +/* stylelint-disable selector-id-pattern */ +#vp_social_integrations, +#vp_watermarks, +#vp_white_label { + .metabox-holder-footer { + display: none; + } +} + +/** + * Welcome Screen. + */ +.portfolio_page_visual-portfolio-welcome, +.toplevel_page_visual-portfolio-welcome { + background-color: #fff; +} + +.vpf-welcome-screen { + box-sizing: border-box; + padding-right: 20px; + padding-left: 20px; + margin-left: -20px; + font-size: 16px; + line-height: 1.6; + + *, + *::before, + *::after { + box-sizing: inherit; + } + + @media screen and (max-width: 782px) { + margin-left: -10px; + } + + p { + font-size: inherit; + line-height: 1.6; + } + + h2 { + margin-top: 0; + font-size: 2em; + font-weight: 700; + line-height: 1.2; + text-align: center; + } + + .vpf-welcome-head { + position: relative; + z-index: 0; + padding: 120px 0; + margin-bottom: 120px; + color: #fff; + background-color: #000; + + @media screen and (max-width: 600px) { + margin-bottom: 60px; + } + + .vpf-welcome-head-background { + position: absolute; + top: 0; + right: -20px; + z-index: -1; + display: block; + width: calc(100% + 40px); + height: 100%; + object-fit: cover; + } + + // Logo. + .vpf-welcome-head-logo { + display: flex; + align-items: center; + justify-content: center; + margin: 0; + margin-bottom: 20px; + color: #fff; + + @media screen and (max-width: 600px) { + font-size: 20px; + } + } + + .dashicons-visual-portfolio { + display: inline-block; + width: 60px; + height: 60px; + margin-top: 3px; + margin-right: 20px; + background-size: 60px; + opacity: 1; + + @media screen and (max-width: 600px) { + width: 30px; + height: 30px; + margin-right: 10px; + background-size: 30px; + } + } + + // Subtitle. + .vpf-welcome-subtitle { + max-width: 480px; + margin: 0 auto; + font-weight: 500; + line-height: 1.7; + color: #c1c1c1; + text-align: center; + } + + // Pro info. + .vpf-welcome-head-pro-info { + position: absolute; + bottom: -50px; + left: 50%; + width: 100%; + max-width: 570px; + padding: 25px 45px; + color: #131313; + text-align: center; + background-color: #fff; + border-radius: 3px; + box-shadow: 0 5px 10px 0 rgba(#000, 0.1); + transform: translateX(-50%); + + @media screen and (max-width: 1120px) { + max-width: 530px; + } + @media screen and (max-width: 600px) { + position: relative; + } + } + } + + .vpf-welcome-content { + max-width: 900px; + margin: 0 auto; + + // Title. + .vpf-welcome-content-title { + max-width: 600px; + margin: 0 auto; + margin-bottom: 80px; + } + + // Features. + .vpf-welcome-content-features { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 30px; + list-style: none; + + @media screen and (max-width: 600px) { + grid-template-columns: 1fr; + } + + li { + position: relative; + padding-left: 50px; + + > span { + position: absolute; + top: 0; + left: 0; + display: block; + font-size: 1.6em; + line-height: 1.3; + } + + > strong { + font-size: 1.6em; + font-weight: 400; + line-height: 1.3; + color: #1d2327; + } + } + } + + hr { + max-width: 200px; + margin-top: 30px; + margin-bottom: 60px; + } + + // Buttons. + .vpf-welcome-content-buttons { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 10px; + max-width: 450px; + margin: 0 auto; + margin-bottom: 50px; + text-align: center; + + @media screen and (max-width: 600px) { + grid-template-columns: 1fr; + } + + a { + padding: 11px; + font-size: 14px; + font-weight: 600; + color: #fff; + text-decoration: none; + text-transform: uppercase; + background-color: #2540cc; + border-radius: 3px; + + &:hover, + &:focus { + background-color: #13289a; + } + + &:last-child { + color: #000; + background-color: #d2d2d2; + + &:hover, + &:focus { + background-color: #bdbdbd; + } + } + } + } + } + + // Pro. + .vpf-welcome-foot-pro-info { + max-width: 700px; + padding: 40px; + margin: 60px auto; + background-color: #fff; + border: 1px solid #e0e0e0; + + ul { + max-width: 500px; + margin: 40px auto; + column-count: 2; + font-weight: 500; + + @media screen and (max-width: 600px) { + column-count: 1; + } + + > li::before { + display: inline-block; + width: 20px; + height: 20px; + margin-right: 6px; + vertical-align: -0.25em; + content: ""; + background-image: url("#{escape-svg('data:image/svg+xml,')}"); + } + } + + > a { + display: block; + padding: 16px; + font-size: 16px; + font-weight: 600; + color: #fff; + text-align: center; + text-decoration: none; + text-transform: uppercase; + background-color: #1e9059; + border-radius: 3px; + + &:hover, + &:focus { + background-color: #0f7544; + } + } + } +} diff --git a/src/assets/admin/images/admin-welcome-background.jpg b/assets/admin/images/admin-welcome-background.jpg similarity index 100% rename from src/assets/admin/images/admin-welcome-background.jpg rename to assets/admin/images/admin-welcome-background.jpg diff --git a/src/assets/admin/images/example-grid.png b/assets/admin/images/example-grid.png similarity index 100% rename from src/assets/admin/images/example-grid.png rename to assets/admin/images/example-grid.png diff --git a/src/assets/admin/images/example-justified.png b/assets/admin/images/example-justified.png similarity index 100% rename from src/assets/admin/images/example-justified.png rename to assets/admin/images/example-justified.png diff --git a/src/assets/admin/images/example-masonry.png b/assets/admin/images/example-masonry.png similarity index 100% rename from src/assets/admin/images/example-masonry.png rename to assets/admin/images/example-masonry.png diff --git a/src/assets/admin/images/example-slider.png b/assets/admin/images/example-slider.png similarity index 100% rename from src/assets/admin/images/example-slider.png rename to assets/admin/images/example-slider.png diff --git a/src/assets/admin/images/example-tiles.png b/assets/admin/images/example-tiles.png similarity index 100% rename from src/assets/admin/images/example-tiles.png rename to assets/admin/images/example-tiles.png diff --git a/src/assets/admin/images/icon-gray.svg b/assets/admin/images/icon-gray.svg similarity index 100% rename from src/assets/admin/images/icon-gray.svg rename to assets/admin/images/icon-gray.svg diff --git a/src/assets/admin/images/icon-gutenberg.svg b/assets/admin/images/icon-gutenberg.svg similarity index 100% rename from src/assets/admin/images/icon-gutenberg.svg rename to assets/admin/images/icon-gutenberg.svg diff --git a/src/assets/admin/images/icon-mce.svg b/assets/admin/images/icon-mce.svg similarity index 100% rename from src/assets/admin/images/icon-mce.svg rename to assets/admin/images/icon-mce.svg diff --git a/src/assets/admin/images/icon-vc.png b/assets/admin/images/icon-vc.png similarity index 100% rename from src/assets/admin/images/icon-vc.png rename to assets/admin/images/icon-vc.png diff --git a/src/assets/admin/images/icon.svg b/assets/admin/images/icon.svg similarity index 100% rename from src/assets/admin/images/icon.svg rename to assets/admin/images/icon.svg diff --git a/src/assets/admin/images/items-style-preview-caption-move.png b/assets/admin/images/items-style-preview-caption-move.png similarity index 100% rename from src/assets/admin/images/items-style-preview-caption-move.png rename to assets/admin/images/items-style-preview-caption-move.png diff --git a/src/assets/admin/images/items-style-preview-classic.png b/assets/admin/images/items-style-preview-classic.png similarity index 100% rename from src/assets/admin/images/items-style-preview-classic.png rename to assets/admin/images/items-style-preview-classic.png diff --git a/src/assets/admin/images/items-style-preview-emerge.png b/assets/admin/images/items-style-preview-emerge.png similarity index 100% rename from src/assets/admin/images/items-style-preview-emerge.png rename to assets/admin/images/items-style-preview-emerge.png diff --git a/src/assets/admin/images/items-style-preview-fade.png b/assets/admin/images/items-style-preview-fade.png similarity index 100% rename from src/assets/admin/images/items-style-preview-fade.png rename to assets/admin/images/items-style-preview-fade.png diff --git a/src/assets/admin/images/items-style-preview-fly.png b/assets/admin/images/items-style-preview-fly.png similarity index 100% rename from src/assets/admin/images/items-style-preview-fly.png rename to assets/admin/images/items-style-preview-fly.png diff --git a/assets/admin/js/archive-page-selector.js b/assets/admin/js/archive-page-selector.js new file mode 100644 index 00000000..473c29d4 --- /dev/null +++ b/assets/admin/js/archive-page-selector.js @@ -0,0 +1,50 @@ +const { jQuery: $, ajaxurl, VPAdminVariables } = window; + +// multiple select with AJAX search +$('select[name="vp_general[portfolio_archive_page]"]').select2({ + ajax: { + url: ajaxurl, // AJAX URL is predefined in WordPress admin + dataType: 'json', + delay: 250, // delay in ms while typing when to perform a AJAX search + data(params) { + return { + q: params.term, // search query + selected: this[0].value, + nonce: VPAdminVariables.nonce, + action: 'vp_get_pages_list', // AJAX action for admin-ajax.php + }; + }, + processResults(ajaxData) { + const options = []; + const data = this.$element.select2('data'); + let alreadyAddedID = false; + + // add selected value. + if (data && data[0] && data[0].selected) { + alreadyAddedID = Number(data[0].id); + options.push({ + id: alreadyAddedID, + text: data[0].text, + }); + } + + // parse new options. + if (ajaxData) { + // ajaxData is the array of arrays, and each of them contains ID and the Label of the option + $.each(ajaxData, (index, itemData) => { + if (!alreadyAddedID || alreadyAddedID !== itemData[0]) { + options.push({ + id: itemData[0], + text: itemData[1], + }); + } + }); + } + + return { + results: options, + }; + }, + cache: true, + }, +}); diff --git a/assets/admin/js/ask-review-notice.js b/assets/admin/js/ask-review-notice.js new file mode 100644 index 00000000..6f399138 --- /dev/null +++ b/assets/admin/js/ask-review-notice.js @@ -0,0 +1,23 @@ +const { jQuery: $, ajaxurl, VPAskReviewNotice } = window; + +const $body = $('body'); + +$body.on('click', '.vpf-review-plugin-notice-dismiss', function (e) { + const $this = $(this); + const type = $this.attr('data-vpf-review-action'); + + // Don't prevent click on Yes link, as it is URL for rate. + if (type !== 'yes') { + e.preventDefault(); + } + + // Hide notice. + $this.closest('.notice').slideUp('slow'); + + // Save user answer in DB. + $.post(ajaxurl, { + action: 'vpf_dismiss_ask_review_notice', + type, + nonce: VPAskReviewNotice.nonce, + }); +}); diff --git a/assets/admin/js/elementor.js b/assets/admin/js/elementor.js new file mode 100644 index 00000000..06cae0a9 --- /dev/null +++ b/assets/admin/js/elementor.js @@ -0,0 +1,92 @@ +/*! + * Additional js for Elementor + * + * Name : Visual Portfolio + * Author : nK https://nkdev.info + */ +//import { throttle } from '@wordpress/compose'; +import { throttle } from 'throttle-debounce'; +import rafSchd from 'raf-schd'; + +const { elementorFrontend, VPAdminElementorVariables: variables } = window; + +const $ = window.jQuery; +const $wnd = $(window); + +$wnd.on('elementor/frontend/init', ($data) => { + if (!variables) { + return; + } + + const { target: elementorWindow } = $data; + + // add fake iframe width, so @media styles will work fine. + function maybeResizePreviews() { + const elementorWidth = elementorWindow + .jQuery(elementorWindow.document) + .width(); + + elementorWindow.jQuery + .find('.visual-portfolio-elementor-preview iframe') + .forEach((item) => { + const $this = $(item); + const parentWidth = $this.parent().width(); + + $this.css({ + width: elementorWidth, + }); + + if (item.iFrameResizer) { + item.iFrameResizer.sendMessage({ + name: 'resize', + width: parentWidth, + }); + item.iFrameResizer.resize(); + } + }); + } + + // window resize. + $wnd.on('resize', throttle(300, rafSchd(maybeResizePreviews))); + + // added/changed widget. + elementorFrontend.hooks.addAction( + 'frontend/element_ready/visual-portfolio.default', + ($scope) => { + const $block = $($scope).find( + '.visual-portfolio-elementor-preview' + ); + const $frame = $block.find('iframe'); + const id = $block.attr('data-id'); + const iframeURL = `${ + variables.preview_url + + (variables.preview_url.split('?')[1] ? '&' : '?') + }vp_preview_frame=true&vp_preview_type=elementor&vp_preview_frame_id=${id}&vp_preview_nonce=${ + variables.nonce + }`; + + $frame.attr('src', iframeURL); + + // resize iframe + if ($.fn.iFrameResize) { + $frame.iFrameResize({ + onInit() { + maybeResizePreviews(); + }, + onMessage({ message }) { + // select current block on click message. + if (message === 'clicked') { + // Select current widget to display settings. + $frame + .closest('.elementor-element') + .find('.elementor-editor-element-edit') + .click(); + + window.focus(); + } + }, + }); + } + } + ); +}); diff --git a/assets/admin/js/mce-dropdown.js b/assets/admin/js/mce-dropdown.js new file mode 100644 index 00000000..69e88e55 --- /dev/null +++ b/assets/admin/js/mce-dropdown.js @@ -0,0 +1,52 @@ +/*! + * Name : Visual Portfolio + * Author : nK https://nkdev.info + */ +const { tinymce, VPTinyMCEData } = window; + +if (typeof VPTinyMCEData !== 'undefined' && VPTinyMCEData.layouts.length) { + const options = [ + { + text: '', + value: '', + }, + ]; + + Object.keys(VPTinyMCEData.layouts).forEach((k) => { + options.push({ + text: VPTinyMCEData.layouts[k].title, + value: VPTinyMCEData.layouts[k].id, + }); + }); + + tinymce.create('tinymce.plugins.visual_portfolio', { + init(editor) { + editor.addButton('visual_portfolio', { + type: 'listbox', + title: VPTinyMCEData.plugin_name, + icon: 'visual-portfolio', + classes: 'visual-portfolio-btn', + onclick() { + if (this.menu) { + this.menu.$el.find('.mce-first').hide(); + } + }, + onselect() { + if (this.value()) { + editor.insertContent( + `[visual_portfolio id="${this.value()}"]` + ); + } + this.value(''); + }, + values: options, + value: '', + }); + }, + }); + + tinymce.PluginManager.add( + 'visual_portfolio', + tinymce.plugins.visual_portfolio + ); +} diff --git a/src/assets/admin/js/mce-localize.js b/assets/admin/js/mce-localize.js similarity index 100% rename from src/assets/admin/js/mce-localize.js rename to assets/admin/js/mce-localize.js diff --git a/assets/admin/js/script.js b/assets/admin/js/script.js new file mode 100644 index 00000000..8ab18c90 --- /dev/null +++ b/assets/admin/js/script.js @@ -0,0 +1,109 @@ +/*! + * Name : Visual Portfolio + * Author : nK https://nkdev.info + */ +import { debounce } from '@wordpress/compose'; +import rafSchd from 'raf-schd'; + +const { jQuery: $, ajaxurl, VPAdminVariables } = window; + +const $body = $('body'); + +// select shortcode text in input +$body.on( + 'focus', + '[name="vp_list_shortcode"], [name="vp_filter_shortcode"], [name="vp_sort_shortcode"]', + function () { + this.select(); + } +); +$body.on('click', '.vp-onclick-selection', function () { + // eslint-disable-next-line @wordpress/no-global-get-selection + window.getSelection().selectAllChildren(this); +}); +// fix the problem with Gutenberg shortcode transform (allowed only plain text pasted). +$body.on('copy cut', '.vp-onclick-selection', (e) => { + // eslint-disable-next-line @wordpress/no-global-get-selection + const copyText = window + .getSelection() + .toString() + .replace(/[\n\r]+/g, ''); + + e.originalEvent.clipboardData.setData('text/plain', copyText); + e.originalEvent.preventDefault(); +}); + +// Post format metabox show/hide +const $videoMetabox = $('#vp_format_video'); +const $videoFormatCheckbox = $('#post-format-video'); +let isVideoFormat = null; + +function toggleVideoMetabox(show) { + if (isVideoFormat === null || isVideoFormat !== show) { + isVideoFormat = show; + $videoMetabox[show ? 'show' : 'hide'](); + } +} + +if ($videoMetabox.length) { + if ($videoFormatCheckbox.length) { + toggleVideoMetabox($videoFormatCheckbox.is(':checked')); + + $body.on('change', '[name=post_format]', () => { + toggleVideoMetabox($videoFormatCheckbox.is(':checked')); + }); + } +} + +let oembedAjax = null; +let runAjaxVideoOembed = function ($this) { + oembedAjax = $.ajax({ + url: ajaxurl, + method: 'POST', + dataType: 'json', + data: { + action: 'vp_find_oembed', + q: $this.val(), + nonce: VPAdminVariables.nonce, + }, + complete(data) { + const json = data.responseJSON; + if (json && typeof json.html !== 'undefined') { + $this.next('.vp-oembed-preview').html(json.html); + } + }, + }); +}; +runAjaxVideoOembed = debounce(300, rafSchd(runAjaxVideoOembed)); + +$body.on('change input', '.vp-input[name="_vp_format_video_url"]', function () { + if (oembedAjax !== null) { + oembedAjax.abort(); + } + + const $this = $(this); + $this.next('.vp-oembed-preview').html(''); + + runAjaxVideoOembed($this); +}); + +/** + * When attempting to disable registration of portfolio post type, + * We inform the user of the possible consequences and provide them with the opportunity to cancel this operation. + */ +$body.on( + 'change', + "input[name='vp_general[register_portfolio_post_type]']", + function () { + // Does some stuff and logs the event to the console + if (!$(this).is(':checked')) { + // eslint-disable-next-line no-restricted-globals, no-alert, no-undef + const confirmation = confirm( + "Are you sure you want to turn off the Portfolio custom post type and related taxonomies? Make sure you don't use this post type on your site, otherwise you might see errors on the frontend." + ); + if (!confirmation) { + $(this).prop('checked', true); + } + } + } +); diff --git a/assets/admin/js/vc-frontend.js b/assets/admin/js/vc-frontend.js new file mode 100644 index 00000000..1882e279 --- /dev/null +++ b/assets/admin/js/vc-frontend.js @@ -0,0 +1,29 @@ +/*! + * Additional js for frontend VC + * + * Name : Visual Portfolio + * Author : nK https://nkdev.info + */ +const { jQuery: $, vc } = window; + +$(() => { + // shortcode frontend editor + if (typeof vc !== 'undefined') { + // on shortcode add and update events + vc.events.on('shortcodes:add shortcodeView:updated', (e) => { + if (e.settings.base !== 'visual_portfolio') { + return; + } + + const wnd = vc.$frame[0].contentWindow; + const jQframe = wnd ? wnd.jQuery : false; + + if (jQframe) { + const $vp = jQframe(e.view.el).children('.vp-portfolio'); + if ($vp.length && typeof $vp.vpf !== 'undefined') { + $vp.vpf(); + } + } + }); + } +}); diff --git a/assets/css/_variables-lazyload.scss b/assets/css/_variables-lazyload.scss new file mode 100644 index 00000000..89e20086 --- /dev/null +++ b/assets/css/_variables-lazyload.scss @@ -0,0 +1,14 @@ +/** + * Lazyload CSS Variables + */ + +:root { + // Lazy. + --vp-lazyload-images__background: linear-gradient(270deg, rgba(140, 140, 140, 15%), rgba(140, 140, 140, 5%), rgba(140, 140, 140, 5%), rgba(140, 140, 140, 15%)); + --vp-lazyload-images__background-size: 400% 100%; + --vp-lazyload-images__animation-duration: 7s; + + // Transitions. + --vp-lazyload-transition-duration: 0.3s; + --vp-lazyload-transition-easing: ease-in-out; +} diff --git a/assets/css/_variables-main.scss b/assets/css/_variables-main.scss new file mode 100644 index 00000000..24bc6d3e --- /dev/null +++ b/assets/css/_variables-main.scss @@ -0,0 +1,47 @@ +/** + * Global CSS Variables + */ +:root { + // Main colors. + --vp-color-brand: #2540cc; + --vp-color-gray: #6c7781; + --vp-color-gray-darken: #4b4b4b; + --vp-color-gray-light: #e8e8e8; + --vp-color-gray-lighten: #f7f7f7; + --vp-color-red: #b71515; + + // Radius. + --vp-border-radius: 5px; + + // Gap. + --vp-items__gap: 0; + + // Transitions. + --vp-transition-duration: 0.3s; + --vp-transition-easing: ease-in-out; + --vp-interactive__transition-duration: 0.2s; + --vp-interactive__transition-easing: ease-in-out; +} + +// Vertical gap. +.vp-portfolio__items { + --vp-items__gap-vertical: var(--vp-items__gap); +} + +.vp-portfolio { + --vp-wrap__min-height: 114px; + --vp-elements__gap: 20px; + + // Images. + --vp-images__object-fit: cover; + --vp-images__object-position: 50% 50%; +} + +.vp-spinner { + --vp-spinner__color: currentcolor; + --vp-spinner__size: 20px; + --vp-spinner__border-size: 2px; + --vp-spinner__speed: 0.3s; + --vp-spinner--background__color: var(--vp-spinner__color); + --vp-spinner--background__opacity: 0.3; +} diff --git a/assets/css/_variables-popup.scss b/assets/css/_variables-popup.scss new file mode 100644 index 00000000..c2709fde --- /dev/null +++ b/assets/css/_variables-popup.scss @@ -0,0 +1,17 @@ +/** + * Popup Galleries CSS Variables + */ +.vp-pswp, +.vp-fancybox { + --vp-popup__z-index: 1500; + + // Thumbnails. + --vp-popup--thumbnails__size: 160px; + --vp-popup--thumbnails__aspect-ratio: 10 / 7; + --vp-popup--thumbnails__background-color: #1e1e1e; + --vp-popup--thumbnails--items__border-color: var(--vp-color-brand); + --vp-popup--thumbnails--scrollbar__size: 7px; + --vp-popup--thumbnails--scrollbar-track__background-color: #1f1f1f; + --vp-popup--thumbnails--scrollbar-thumb__background-color: #424242; + --vp-popup--thumbnails--scrollbar-thumb__border-radius: 10px; +} diff --git a/assets/css/_variables-slider.scss b/assets/css/_variables-slider.scss new file mode 100644 index 00000000..179000ab --- /dev/null +++ b/assets/css/_variables-slider.scss @@ -0,0 +1,38 @@ +/** + * Slider CSS Variables + */ +.vp-portfolio { + // Arrows. + --vp-layout-slider--arrows__width: 2em; + --vp-layout-slider--arrows__height: 2em; + --vp-layout-slider--arrows__offset: 10px; + --vp-layout-slider--arrows__compensation: 10px; + --vp-layout-slider--arrows__color: var(--vp-color-gray); + --vp-layout-slider--arrows__background-color: #fff; + --vp-layout-slider--arrows__border-radius: 50%; + --vp-layout-slider--arrows__box-shadow: 0 0 7px 2px rgba(0, 0, 0, 4%); + --vp-layout-slider--arrows__opacity: 0.5; + --vp-layout-slider--arrows-hover__box-shadow: 0 2px 15px 2px rgba(0, 0, 0, 4%); + --vp-layout-slider--arrows-hover__opacity: 1; + + // Bullets. + --vp-layout-slider--bullets__margin-top: 2em; + --vp-layout-slider--bullets__width: 0.4em; + --vp-layout-slider--bullets__height: 0.4em; + --vp-layout-slider--bullets__gap: 0.4em; + --vp-layout-slider--bullets__compensation: 4px; + --vp-layout-slider--bullets__background-color: currentcolor; + --vp-layout-slider--bullets__border-radius: 50%; + --vp-layout-slider--bullets__opacity: 0.2; + --vp-layout-slider--bullets-hover__opacity: 0.5; + --vp-layout-slider--bullets-active__opacity: 1; + + // Thumbnails. + --vp-layout-slider--thumbnails__opacity: 0.5; + --vp-layout-slider--thumbnails-hover__opacity: 1; + --vp-layout-slider--thumbnails-active__opacity: 1; + + // Transitions. + --vp-layout-slider__transition-duration: var(--vp-interactive__transition-duration); + --vp-layout-slider__transition-easing: var(--vp-interactive__transition-easing); +} diff --git a/assets/css/custom-scrollbar.scss b/assets/css/custom-scrollbar.scss new file mode 100644 index 00000000..9fcd7c6a --- /dev/null +++ b/assets/css/custom-scrollbar.scss @@ -0,0 +1,10 @@ +/* + * Visual Portfolio custom scrollbar. + */ +.vp-portfolio__custom-scrollbar { + --vp-custom-scrollbar__background-color: #888; + + .simplebar-scrollbar::before { + background-color: var(--vp-custom-scrollbar__background-color); + } +} diff --git a/assets/css/elementor.scss b/assets/css/elementor.scss new file mode 100644 index 00000000..6f442b6d --- /dev/null +++ b/assets/css/elementor.scss @@ -0,0 +1,13 @@ +/* + * Additional styles to fix Elementor page builder conflicts. + */ + +// Gaps. +[data-vp-layout="grid"], +[data-vp-layout="masonry"], +[data-vp-layout="tiles"] { + .elementor & .vp-portfolio__items .vp-portfolio__item-wrap .vp-portfolio__item { + margin-top: var(--vp-items__gap-vertical); + margin-left: var(--vp-items__gap); + } +} diff --git a/assets/css/layout-grid.scss b/assets/css/layout-grid.scss new file mode 100644 index 00000000..1e51bf69 --- /dev/null +++ b/assets/css/layout-grid.scss @@ -0,0 +1,47 @@ +/* + * Visual Portfolio layout Grid. + */ + +// Gaps. +[data-vp-layout="grid"] { + .vp-portfolio__items { + margin-top: calc(-1 * var(--vp-items__gap-vertical)); + margin-left: calc(-1 * var(--vp-items__gap)); + } + + .vp-portfolio__item-wrap .vp-portfolio__item { + margin-top: var(--vp-items__gap-vertical); + margin-left: var(--vp-items__gap); + } +} + +// Images. +[data-vp-grid-images-aspect-ratio*=":"] { + .vp-portfolio__item-img img, + .vp-portfolio__item-img { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } + + .vp-portfolio__item-img-wrap { + position: relative; + display: block; + overflow: hidden; + + &::before { + display: block; + padding-top: 56%; + content: ""; + } + } + + .vp-portfolio__item-img img { + width: 100%; + height: 100%; + object-fit: var(--vp-images__object-fit); + object-position: var(--vp-images__object-position); + } +} diff --git a/src/assets/css/layout-justified.scss b/assets/css/layout-justified.scss similarity index 50% rename from src/assets/css/layout-justified.scss rename to assets/css/layout-justified.scss index 2553336e..69128959 100644 --- a/src/assets/css/layout-justified.scss +++ b/assets/css/layout-justified.scss @@ -2,9 +2,9 @@ * Visual Portfolio layout Justified. */ [data-vp-layout="justified"] { - .vp-portfolio__item-wrap { - top: 0; - left: 0; - float: left; - } + .vp-portfolio__item-wrap { + top: 0; + left: 0; + float: left; + } } diff --git a/assets/css/layout-masonry.scss b/assets/css/layout-masonry.scss new file mode 100644 index 00000000..c58d8938 --- /dev/null +++ b/assets/css/layout-masonry.scss @@ -0,0 +1,47 @@ +/* + * Visual Portfolio layout Masonry. + */ + +// Gaps. +[data-vp-layout="masonry"] { + .vp-portfolio__items { + margin-top: calc(-1 * var(--vp-items__gap-vertical)); + margin-left: calc(-1 * var(--vp-items__gap)); + } + + .vp-portfolio__item-wrap .vp-portfolio__item { + margin-top: var(--vp-items__gap-vertical); + margin-left: var(--vp-items__gap); + } +} + +// Images. +[data-vp-masonry-images-aspect-ratio*=":"] { + .vp-portfolio__item-img img, + .vp-portfolio__item-img { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } + + .vp-portfolio__item-img-wrap { + position: relative; + display: block; + overflow: hidden; + + &::before { + display: block; + padding-top: 56%; + content: ""; + } + } + + .vp-portfolio__item-img img { + width: 100%; + height: 100%; + object-fit: var(--vp-images__object-fit); + object-position: var(--vp-images__object-position); + } +} diff --git a/assets/css/layout-slider.scss b/assets/css/layout-slider.scss new file mode 100644 index 00000000..5027bc06 --- /dev/null +++ b/assets/css/layout-slider.scss @@ -0,0 +1,167 @@ +@import "./variables-slider"; + +/* + * Visual Portfolio layout Slider. + */ +[data-vp-layout="slider"] { + // Initial styles before Swiper init. + .vp-portfolio__items-wrap:not(.swiper) .vp-portfolio__items, + .vp-portfolio__thumbnails-wrap:not(.swiper) .vp-portfolio__thumbnails { + position: relative; + box-sizing: content-box; + display: flex; + } + + .vp-portfolio__items-wrap:not(.swiper) .vp-portfolio__items .vp-portfolio__item-wrap, + .vp-portfolio__thumbnails-wrap:not(.swiper) .vp-portfolio__thumbnails .vp-portfolio__thumbnail-wrap { + width: calc(100% / var(--vp-layout-slider__initial-slides-per-view, 8)); + min-width: calc(100% / var(--vp-layout-slider__initial-slides-per-view, 8)); + } + + @for $i from 1 through 8 { + &[data-vp-slider-slides-per-view="#{$i}"] .vp-portfolio__items-wrap:not(.swiper), + &[data-vp-slider-thumbnails-per-view="#{$i}"] .vp-portfolio__thumbnails-wrap:not(.swiper) { + --vp-layout-slider__initial-slides-per-view: #{$i}; + } + } + + .vp-portfolio__thumbnail-img-wrap { + position: relative; + display: block; + overflow: hidden; + } + + .vp-portfolio__item-img-wrap::before, + .vp-portfolio__thumbnail-img-wrap::before { + display: block; + content: ""; + } + + .vp-portfolio__item-img img, + .vp-portfolio__thumbnail-img img { + object-fit: var(--vp-images__object-fit); + object-position: var(--vp-images__object-position); + } + + // arrows + .vp-portfolio__items-arrow { + position: absolute; + top: 50%; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + width: var(--vp-layout-slider--arrows__width); + height: var(--vp-layout-slider--arrows__height); + margin-top: calc(-1 * var(--vp-layout-slider--arrows__height) / 2); + color: var(--vp-layout-slider--arrows__color); + cursor: pointer; + background-color: var(--vp-layout-slider--arrows__background-color); + border-radius: var(--vp-layout-slider--arrows__border-radius); + box-shadow: var(--vp-layout-slider--arrows__box-shadow); + opacity: var(--vp-layout-slider--arrows__opacity); + transition: var(--vp-layout-slider__transition-duration) opacity var(--vp-layout-slider__transition-easing), var(--vp-layout-slider__transition-duration) box-shadow var(--vp-layout-slider__transition-easing); + + // additional element to make the buttons clickable also in outside. + &::after { + position: absolute; + top: calc(-1 * var(--vp-layout-slider--arrows__compensation)); + right: calc(-1 * var(--vp-layout-slider--arrows__compensation)); + bottom: calc(-1 * var(--vp-layout-slider--arrows__compensation)); + left: calc(-1 * var(--vp-layout-slider--arrows__compensation)); + display: block; + content: ""; + } + + &:hover { + box-shadow: var(--vp-layout-slider--arrows-hover__box-shadow); + opacity: var(--vp-layout-slider--arrows-hover__opacity); + } + + // RTL. + @if variable-exists(rtl) and $rtl { + svg { + transform: scaleX(-1); + } + } + } + + .vp-portfolio__items-arrow-prev { + left: var(--vp-layout-slider--arrows__offset); + } + + .vp-portfolio__items-arrow-next { + right: var(--vp-layout-slider--arrows__offset); + } + + // bullets + &[data-vp-slider-bullets="true"] .vp-portfolio__items-wrap { + padding-bottom: var(--vp-layout-slider--bullets__margin-top); + + .vp-portfolio__items-arrow { + transform: translateY(calc(var(--vp-layout-slider--bullets__margin-top) / -2)); + } + } + + .vp-portfolio__items-bullets { + position: absolute; + bottom: 0; + z-index: 1; + text-align: center; + + // Fixes Swiper default font-size + &.swiper-pagination-bullets-dynamic { + font-size: 1em; + } + + > .swiper-pagination-bullet { + position: relative; + width: var(--vp-layout-slider--bullets__width); + height: var(--vp-layout-slider--bullets__height); + margin: 0 calc(var(--vp-layout-slider--bullets__gap) / 2); + cursor: pointer; + background-color: var(--vp-layout-slider--bullets__background-color); + border-radius: var(--vp-layout-slider--bullets__border-radius); + opacity: var(--vp-layout-slider--bullets__opacity); + transition: var(--vp-layout-slider__transition-duration) opacity var(--vp-layout-slider__transition-easing); + + &:hover, + &:focus { + opacity: var(--vp-layout-slider--bullets-hover__opacity); + } + + &.swiper-pagination-bullet-active { + opacity: var(--vp-layout-slider--bullets-active__opacity); + } + + // additional element to make the buttons clickable also in outside. + &::after { + position: absolute; + top: calc(-1 * var(--vp-layout-slider--bullets__compensation)); + right: calc(-1 * var(--vp-layout-slider--bullets__compensation)); + bottom: calc(-1 * var(--vp-layout-slider--bullets__compensation)); + left: calc(-1 * var(--vp-layout-slider--bullets__compensation)); + display: block; + content: ""; + } + } + } + + // thumbnails + .vp-portfolio__thumbnails-wrap { + .vp-portfolio__thumbnail-wrap { + cursor: pointer; + opacity: var(--vp-layout-slider--thumbnails__opacity); + transition: var(--vp-layout-slider__transition-duration) opacity; + + &:hover, + &:focus { + opacity: var(--vp-layout-slider--thumbnails-hover__opacity); + } + + &.swiper-slide-thumb-active { + opacity: var(--vp-layout-slider--thumbnails-active__opacity); + } + } + } +} diff --git a/assets/css/layout-tiles.scss b/assets/css/layout-tiles.scss new file mode 100644 index 00000000..e52fdc0d --- /dev/null +++ b/assets/css/layout-tiles.scss @@ -0,0 +1,50 @@ +/* + * Visual Portfolio layout Tiles. + */ +[data-vp-layout="tiles"] { + // Gaps. + .vp-portfolio__items, + .vp-portfolio__item-wrap .vp-portfolio__item-img-wrap { + margin-top: calc(-1 * var(--vp-items__gap-vertical)); + margin-left: calc(-1 * var(--vp-items__gap)); + } + + .vp-portfolio__item-wrap .vp-portfolio__item { + margin-top: var(--vp-items__gap-vertical); + margin-left: var(--vp-items__gap); + } + + // Images + .vp-portfolio__item-img img, + .vp-portfolio__item-img { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } + + .vp-portfolio__item-img { + top: var(--vp-items__gap-vertical); + left: var(--vp-items__gap); + } + + .vp-portfolio__item-img-wrap { + position: relative; + display: block; + overflow: hidden; + + &::before { + display: block; + padding-top: 56%; + content: ""; + } + } + + .vp-portfolio__item-img img { + width: 100%; + height: 100%; + object-fit: var(--vp-images__object-fit); + object-position: var(--vp-images__object-position); + } +} diff --git a/assets/css/lazyload-fallback.scss b/assets/css/lazyload-fallback.scss new file mode 100644 index 00000000..44919284 --- /dev/null +++ b/assets/css/lazyload-fallback.scss @@ -0,0 +1,32 @@ +/* + * Visual Portfolio Lazyload Images fallback for old browsers + * which does not support CSS :has() + */ +:is(.vp-portfolio__item-img, .vp-portfolio__thumbnail-img) { + // Extra check for lazy loading class on the inner image + // to make sure image will be lazy loaded by Visual Portfolio. + &:is(.vp-has-lazyload, .vp-has-lazyloading, .vp-has-lazyloaded)::before { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + // Fixed possible bug with inaccessible links on images in the Classic style. + pointer-events: none; + visibility: visible; + content: ""; + background-image: var(--vp-lazyload-images__background); + background-size: var(--vp-lazyload-images__background-size); + opacity: 1; + transition: var(--vp-lazyload-transition-duration) var(--vp-lazyload-transition-duration) opacity, var(--vp-lazyload-transition-duration) var(--vp-lazyload-transition-duration) visibility; + } + + &:is(.vp-has-lazyloading)::before { + animation: vp-lazyload-placeholder var(--vp-lazyload-images__animation-duration) linear infinite; + } + + &:is(.vp-has-lazyloaded)::before { + visibility: hidden; + opacity: 0; + } +} diff --git a/assets/css/lazyload.scss b/assets/css/lazyload.scss new file mode 100644 index 00000000..33227d58 --- /dev/null +++ b/assets/css/lazyload.scss @@ -0,0 +1,59 @@ +@import "./variables-lazyload"; + +/* + * Visual Portfolio Lazyload Images + * + * - :first-of-type is used to prevent conflicts with hover image (Pro feature) + */ + +// LazyLoad image +img.vp-lazyload, +img.vp-lazyloaded, +img.vp-lazypreload, +img.vp-lazyloading { + opacity: 0; + transition: var(--vp-lazyload-transition-duration) opacity; +} + +img.vp-lazyloaded { + opacity: 1; +} + +:is(.vp-portfolio__item-img, .vp-portfolio__thumbnail-img) { + // Extra check for lazy loading class on the inner image + // to make sure image will be lazy loaded by Visual Portfolio. + &:has(img:first-of-type:is(.vp-lazyload, .vp-lazyloading, .vp-lazyloaded))::before { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + // Fixed possible bug with inaccessible links on images in the Classic style. + pointer-events: none; + visibility: visible; + content: ""; + background-image: var(--vp-lazyload-images__background); + background-size: var(--vp-lazyload-images__background-size); + opacity: 1; + transition: var(--vp-lazyload-transition-duration) var(--vp-lazyload-transition-duration) opacity, var(--vp-lazyload-transition-duration) var(--vp-lazyload-transition-duration) visibility; + } + + &:has(img:first-of-type.vp-lazyloading)::before { + animation: vp-lazyload-placeholder var(--vp-lazyload-images__animation-duration) ease-in-out infinite; + } + + &:has(img:first-of-type.vp-lazyloaded)::before { + visibility: hidden; + opacity: 0; + } +} + +@keyframes vp-lazyload-placeholder { + 0% { + background-position: 200% 0; + } + + 100% { + background-position: -200% 0; + } +} diff --git a/assets/css/main.scss b/assets/css/main.scss new file mode 100644 index 00000000..5b670350 --- /dev/null +++ b/assets/css/main.scss @@ -0,0 +1,306 @@ +@import "./variables-main"; + +/* + * Visual Portfolio main style. + */ +.vp-portfolio { + position: relative; + box-sizing: border-box; + min-height: var(--vp-wrap__min-height); + overflow-wrap: break-word; + + *, + *::before, + *::after { + box-sizing: inherit; + } + + // Fixes Swiper box-sizing conflict. + // https://github.com/nk-crew/visual-portfolio/issues/147 + .swiper-wrapper { + box-sizing: inherit; + } +} + +.vp-portfolio__items { + transition: var(--vp-transition-duration) height var(--vp-transition-easing), var(--vp-transition-duration) transform var(--vp-transition-easing); +} + +.vp-portfolio::after, +.vp-portfolio__items::after { + display: block; + clear: both; + content: ""; +} + +.vp-portfolio__items-wrap, +.vp-portfolio__thumbnails-wrap, +.vp-portfolio__filter-wrap, +.vp-portfolio__sort-wrap, +.vp-portfolio__pagination-wrap, +.vp-portfolio__item { + position: relative; + overflow: hidden; +} + +.vp-portfolio__items-wrap, +.vp-portfolio__thumbnails-wrap, +.vp-portfolio__layout-elements { + margin-bottom: var(--vp-elements__gap); + visibility: hidden; + opacity: 0; + transition: var(--vp-transition-duration) opacity, var(--vp-transition-duration) visibility; +} + +.vp-portfolio > :last-child { + margin-bottom: 0; +} + +.vp-portfolio__item-wrap { + position: relative; + float: left; + width: 33.333%; +} + +// icons. +.vp-svg-icon { + display: inline-block; + width: 1em; + height: 1em; + overflow: visible; + font-size: inherit; + vertical-align: -0.125em; +} + +// screen readers only. +.vp-screen-reader-text { + position: absolute !important; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); + word-break: normal; + word-wrap: normal !important; + border: 0; + + &:focus { + top: 5px; + right: 5px; + z-index: 100000; + display: block; + width: auto; + height: auto; + padding: 15px 23px 14px; + clip: auto !important; + clip-path: none; + font-size: 14px; + font-size: 0.875rem; + font-weight: 700; + line-height: normal; + color: var(--vp-color-brand); + text-decoration: none; + background-color: #f1f1f1; + border-radius: 3px; + box-shadow: 0 0 2px 2px rgba(0, 0, 0, 60%); + } +} + +// fix for default themes styles. +[data-vp-layout]:not([data-vp-layout="slider"]) .vp-portfolio__item-wrap { + padding: 0 !important; + margin: 0 !important; +} + +[data-vp-layout="slider"] .vp-portfolio__item-wrap { + // In some plugins there is a style with `display: flex`, which breaks our items. + display: block; + float: none; + padding: 0 !important; + margin-top: 0 !important; + margin-bottom: 0 !important; + margin-left: 0 !important; +} + +.vp-portfolio__item .vp-portfolio__item-img img, +.vp-portfolio__item .vp-portfolio__item-img a, +.vp-portfolio__item .vp-portfolio__thumbnail-img img { + display: block; + width: 100%; + height: auto; +} + +.vp-portfolio__item-img, +.vp-portfolio__thumbnail-img { + position: relative; +} + +// Fix conflict with such theme styles: +// a { position: relative; } +// https://wordpress.org/support/topic/gallery-images-grey/ +.vp-portfolio__item .vp-portfolio__item-img a { + position: unset; +} + +// layout elements +.vp-portfolio__layout-elements { + display: flex; + flex-wrap: wrap; + gap: var(--vp-elements__gap); + + &-align-left { + justify-content: flex-start; + } + + &-align-center { + justify-content: center; + } + + &-align-right { + justify-content: flex-end; + } + + &-align-between { + justify-content: space-between; + } +} + +// Fix custom theme styles for figures +.vp-portfolio figure.vp-portfolio__item { + display: block; + margin: 0; +} + +[class^="wp-block-"]:not(.wp-block-gallery) figcaption.vp-portfolio__item-meta, +.vp-portfolio figcaption.vp-portfolio__item-meta { + margin-bottom: 0; + font-style: inherit; +} + +// Preloader +.vp-portfolio__preloader-wrap { + visibility: visible; + opacity: 1; + transition: var(--vp-transition-duration) opacity, var(--vp-transition-duration) visibility; +} + +.vp-portfolio__preloader { + position: absolute; + left: 50%; + width: 20px; + height: 20px; + margin-top: 45px; + margin-left: -10px; + + svg, + img { + display: block; + width: 100%; + height: 100%; + border-radius: 20px; + } + + &::after { + position: absolute; + top: -2px; + left: -2px; + display: block; + width: 24px; + height: 24px; + text-indent: -9999em; + content: ""; + border: 1px solid rgba(#000, 0.2); + border-left: 1px solid #000; + border-radius: 50%; + animation: vp-preloader-spinner 0.3s infinite linear; + } +} + +@keyframes vp-preloader-spinner { + 100% { + transform: rotate(360deg); + } +} + +// On loaded portfolio +.vp-portfolio.vp-portfolio__ready { + min-height: initial; + + .vp-portfolio__items-wrap, + .vp-portfolio__thumbnails-wrap, + .vp-portfolio__layout-elements { + visibility: visible; + opacity: 1; + } + + .vp-portfolio__preloader-wrap { + visibility: hidden; + opacity: 0; + + .vp-portfolio__preloader { + animation: none; + } + } +} + +.vp-portfolio__layout-elements__ready { + visibility: visible; + opacity: 1; +} + +.vp-single-filter.vp-single-filter__ready { + .vp-portfolio__filter-wrap { + visibility: visible; + opacity: 1; + } +} + +.vp-single-sort.vp-single-sort__ready { + .vp-portfolio__sort-wrap { + visibility: visible; + opacity: 1; + } +} + +// Loading +.vp-portfolio.vp-portfolio__loading .vp-portfolio__layout-elements { + opacity: 0.5; +} + +// Popup Galleries +.vp-portfolio__item-popup { + display: none; +} + +// loading spinner. +.vp-spinner { + position: relative; + display: block; + width: var(--vp-spinner__size); + height: var(--vp-spinner__size); + text-indent: -9999em; + border: var(--vp-spinner__border-size) solid transparent; + border-left: var(--vp-spinner__border-size) solid var(--vp-spinner__color); + border-radius: 50%; + animation: vp-spinner var(--vp-spinner__speed) infinite linear; + + &::after { + position: absolute; + top: calc(-1 * var(--vp-spinner__border-size)); + right: calc(-1 * var(--vp-spinner__border-size)); + bottom: calc(-1 * var(--vp-spinner__border-size)); + left: calc(-1 * var(--vp-spinner__border-size)); + display: block; + content: ""; + border: var(--vp-spinner__border-size) solid var(--vp-spinner--background__color); + border-radius: 50%; + opacity: var(--vp-spinner--background__opacity); + } +} +@keyframes vp-spinner { + 100% { + transform: rotate(360deg); + } +} diff --git a/src/assets/css/noscript.scss b/assets/css/noscript.scss similarity index 83% rename from src/assets/css/noscript.scss rename to assets/css/noscript.scss index bceb32a7..9763d4ea 100644 --- a/src/assets/css/noscript.scss +++ b/assets/css/noscript.scss @@ -5,7 +5,7 @@ // preloader .vp-portfolio__preloader-wrap { - display: none; + display: none; } // hidden items before load. @@ -13,16 +13,16 @@ .vp-portfolio__filter-wrap, .vp-portfolio__sort-wrap, .vp-portfolio__pagination-wrap { - visibility: visible; - opacity: 1; + visibility: visible; + opacity: 1; } // image tag without script .vp-portfolio__item .vp-portfolio__item-img noscript + img { - display: none; + display: none; } // Slider thumbnails .vp-portfolio__thumbnails-wrap { - display: none; + display: none; } diff --git a/assets/css/popup-fancybox.scss b/assets/css/popup-fancybox.scss new file mode 100644 index 00000000..35dbf11c --- /dev/null +++ b/assets/css/popup-fancybox.scss @@ -0,0 +1,88 @@ +@import "./variables-popup"; + +/* + * Visual Portfolio styles for Fancybox. + */ +.vp-fancybox { + top: var(--wp-admin--admin-bar--height, 0); + z-index: var(--vp-popup__z-index); + height: calc(100% - var(--wp-admin--admin-bar--height, 0px)); + + // removed white background to improve vertical videos displaying. + .fancybox-slide--iframe .fancybox-content { + background: none; + } + + // Caption. + .fancybox-caption__body { + font-size: 12px; + color: #fff; + + h3 { + color: inherit; + } + + a { + color: inherit; + + &:hover { + opacity: 0.8; + } + } + + .vp-portfolio__item-meta-title { + margin-top: 0; + margin-bottom: 3px; + font-size: 14px; + color: inherit; + + a { + text-decoration: none; + } + } + } + + // Thumbnails. + .fancybox-thumbs { + width: var(--vp-popup--thumbnails__size); + background: var(--vp-popup--thumbnails__background-color); + } + + &.fancybox-show-thumbs .fancybox-inner { + right: var(--vp-popup--thumbnails__size); + } + + .fancybox-thumbs__list a { + width: var(--vp-popup--thumbnails__size); + max-width: calc(100% - 4px); + height: calc(var(--vp-popup--thumbnails__size) / calc(var(--vp-popup--thumbnails__aspect-ratio))); + + &::before { + border: 2px solid var(--vp-popup--thumbnails--items__border-color); + } + + @supports (aspect-ratio: 16 / 9) { + width: calc(100% - 4px); + max-width: none; + height: auto; + max-height: none; + aspect-ratio: var(--vp-popup--thumbnails__aspect-ratio); + } + } + + .fancybox-thumbs-y .fancybox-thumbs__list { + &::-webkit-scrollbar { + width: var(--vp-popup--thumbnails--scrollbar__size); + } + + &::-webkit-scrollbar-track { + background: var(--vp-popup--thumbnails--scrollbar-track__background-color); + box-shadow: none; + } + + &::-webkit-scrollbar-thumb { + background: var(--vp-popup--thumbnails--scrollbar-thumb__background-color); + border-radius: var(--vp-popup--thumbnails--scrollbar-thumb__border-radius); + } + } +} diff --git a/assets/css/popup-photoswipe.scss b/assets/css/popup-photoswipe.scss new file mode 100644 index 00000000..90cc4799 --- /dev/null +++ b/assets/css/popup-photoswipe.scss @@ -0,0 +1,99 @@ +@import "./variables-popup"; + +/* + * Visual Portfolio styles for PhotoSwipe. + */ +.vp-pswp { + top: var(--wp-admin--admin-bar--height, 0); + z-index: var(--vp-popup__z-index); + height: calc(100% - var(--wp-admin--admin-bar--height, 0px)); + + .pswp__caption { + background-color: rgba(0, 0, 0, 75%); + + > div { + max-width: 600px; + font-size: 12px; + color: #fff; + } + + h3 { + color: inherit; + } + + a { + color: inherit; + + &:hover { + opacity: 0.8; + } + } + + .vp-portfolio__item-meta-title { + margin-top: 0; + margin-bottom: 3px; + font-size: 14px; + color: inherit; + + a { + text-decoration: none; + } + } + } + + .pswp__preloader { + position: absolute; + right: 0; + bottom: 0; + z-index: 1; + } + + .vp-pswp-video { + position: relative; + z-index: 1045; + display: flex; + align-items: center; + width: 100%; + max-width: 1920px; + height: 100%; + margin: 0 auto; + line-height: 0; + text-align: left; + vertical-align: middle; + + > div { + position: relative; + width: 100%; + height: 0; + padding-bottom: 56.25%; + + iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + margin: 0; + } + + video, + audio { + position: absolute; + // stylelint-disable-next-line declaration-no-important + width: 100% !important; + // stylelint-disable-next-line declaration-no-important + height: 100% !important; + margin: 0; + } + + audio { + padding: 20px; + } + } + } + + // Default cursor when click to zoom option disabled. + &.vp-pswp-no-zoom .pswp__img { + cursor: default; + } +} diff --git a/src/assets/css/theme-airtifact.scss b/assets/css/theme-airtifact.scss similarity index 83% rename from src/assets/css/theme-airtifact.scss rename to assets/css/theme-airtifact.scss index 24201571..19a345a2 100644 --- a/src/assets/css/theme-airtifact.scss +++ b/assets/css/theme-airtifact.scss @@ -1,5 +1,5 @@ // Fix images tags. // https://wordpress.org/support/topic/portfolio-page-doesnt-display-images/ .vp-portfolio__item .vp-portfolio__item-img a { - position: unset !important; + position: unset !important; } diff --git a/assets/css/theme-betheme.scss b/assets/css/theme-betheme.scss new file mode 100644 index 00000000..26244809 --- /dev/null +++ b/assets/css/theme-betheme.scss @@ -0,0 +1,33 @@ +// Fix popup buttons styles. +// https://wordpress.org/support/topic/your-plugin-does-not-load-the-css-correctly/ +.vp-pswp { + .pswp__button, + .pswp__button--arrow--left::before, + .pswp__button--arrow--right::before, + .pswp__button:hover, + .pswp__button--arrow--left:hover::before, + .pswp__button--arrow--right:hover::before { + padding: 0; + background-color: transparent; + border: none; + border-radius: 0; + } +} + +.vp-fancybox { + .fancybox-button, + .fancybox-button:hover { + background-color: rgba(30, 30, 30, 60%); + border: none; + border-radius: 0; + box-shadow: none; + + &::after { + content: none; + } + } + + .fancybox-button:not(:hover) { + color: #ccc !important; + } +} diff --git a/src/assets/css/theme-twentyfifteen.scss b/assets/css/theme-twentyfifteen.scss similarity index 82% rename from src/assets/css/theme-twentyfifteen.scss rename to assets/css/theme-twentyfifteen.scss index 6ace37e6..34e870ba 100644 --- a/src/assets/css/theme-twentyfifteen.scss +++ b/assets/css/theme-twentyfifteen.scss @@ -1,11 +1,11 @@ // Fix headings margin. .entry-content .vp-portfolio__item-meta-title { - margin-top: 0; + margin-top: 0; } // Fix preview background color. .vp-preview-body { - background: none; + background: none; } // Fix font family. @@ -14,7 +14,7 @@ .vp-sort select, .vp-portfolio__item-meta-category, .vp-pagination__item { - font-family: "Noto Sans", sans-serif; + font-family: "Noto Sans", sans-serif; } // Fix links border. @@ -24,13 +24,13 @@ .vp-portfolio__item-meta-category, .vp-pagination__item, .vp-portfolio__item-img { - .entry-content & a { - border: none; - } + .entry-content & a { + border: none; + } } // Fix figcaption padding. .wp-block-visual-portfolio[class^="wp-block-"].vp-portfolio__items-style-fade figcaption, .wp-block-visual-portfolio[class^="wp-block-"].vp-portfolio__items-style-fly figcaption { - padding: 0; + padding: 0; } diff --git a/src/assets/css/theme-twentynineteen.scss b/assets/css/theme-twentynineteen.scss similarity index 61% rename from src/assets/css/theme-twentynineteen.scss rename to assets/css/theme-twentynineteen.scss index 6d711408..08a6ac34 100644 --- a/src/assets/css/theme-twentynineteen.scss +++ b/assets/css/theme-twentynineteen.scss @@ -1,16 +1,16 @@ // Fix links decoration. .entry .entry-content .vp-portfolio a { - text-decoration: none; + text-decoration: none; } // Fix headings margin. .entry-content .vp-portfolio__item-meta-title { - margin-top: 0; + margin-top: 0; } // Fix headings decorators. .vp-portfolio__item-meta-title::before { - content: none; + content: none; } // Fix font family. @@ -19,5 +19,5 @@ .vp-sort select, .vp-portfolio__item-meta-category, .vp-pagination__item { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; } diff --git a/src/assets/css/theme-twentyseventeen.scss b/assets/css/theme-twentyseventeen.scss similarity index 67% rename from src/assets/css/theme-twentyseventeen.scss rename to assets/css/theme-twentyseventeen.scss index b7abcf87..c632a560 100644 --- a/src/assets/css/theme-twentyseventeen.scss +++ b/assets/css/theme-twentyseventeen.scss @@ -1,24 +1,24 @@ // Fix links border. .entry-content .vp-portfolio { - a, - a:hover, - a:focus { - box-shadow: none; - } + a, + a:hover, + a:focus { + box-shadow: none; + } } // Fix headings padding and font. .entry-content .vp-portfolio__item-meta-title { - padding-top: 0; - font-family: "Libre Franklin", "Helvetica Neue", helvetica, arial, sans-serif; - font-weight: 600; + padding-top: 0; + font-family: "Libre Franklin", "Helvetica Neue", helvetica, arial, sans-serif; + font-weight: 600; } // Fix for figcaption in block. .wp-block-visual-portfolio[class^="wp-block-"]:not(.wp-block-gallery) figcaption { - margin-bottom: 0; - font-style: inherit; - text-align: center; + margin-bottom: 0; + font-style: inherit; + text-align: center; } // Fix color on hover. @@ -26,5 +26,5 @@ .vp-portfolio__items-style-fade .vp-portfolio__item-meta:focus, .vp-portfolio__items-style-fly .vp-portfolio__item-meta:hover, .vp-portfolio__items-style-fly .vp-portfolio__item-meta:focus { - color: inherit; + color: inherit; } diff --git a/src/assets/css/theme-twentysixteen.scss b/assets/css/theme-twentysixteen.scss similarity index 75% rename from src/assets/css/theme-twentysixteen.scss rename to assets/css/theme-twentysixteen.scss index a6b6e2d2..150c6031 100644 --- a/src/assets/css/theme-twentysixteen.scss +++ b/assets/css/theme-twentysixteen.scss @@ -1,11 +1,11 @@ // Fix preview background color. .vp-preview-body { - background: none; + background: none; } // Fix headings margin. .entry-content .vp-portfolio__item-meta-title { - margin-top: 0; + margin-top: 0; } // Fix font family. @@ -15,5 +15,5 @@ .vp-sort select, .vp-portfolio__item-meta-category, .vp-pagination__item { - font-family: Montserrat, "Helvetica Neue", sans-serif; + font-family: Montserrat, "Helvetica Neue", sans-serif; } diff --git a/src/assets/css/theme-twentytwenty.scss b/assets/css/theme-twentytwenty.scss similarity index 52% rename from src/assets/css/theme-twentytwenty.scss rename to assets/css/theme-twentytwenty.scss index 3464c725..3627c471 100644 --- a/src/assets/css/theme-twentytwenty.scss +++ b/assets/css/theme-twentytwenty.scss @@ -1,16 +1,16 @@ // Fix headings margin. .entry-content .vp-portfolio__item-meta-title { - margin-top: 0; + margin-top: 0; } // Fix caption margin. .vp-portfolio__item figcaption { - margin-top: 0; + margin-top: 0; } // Fix preview background color. .vp-preview-body { - background: none; + background: none; } // Fix font family. @@ -19,38 +19,38 @@ .vp-sort select, .vp-portfolio__item-meta-category, .vp-pagination__item { - font-family: "Inter var", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, sans-serif; + font-family: "Inter var", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, sans-serif; } // Filters, Sort and Pagination. .vp-filter__style-default { - --vp-filter-default--items__font-size: 0.7em; + --vp-filter-default--items__font-size: 0.7em; } .vp-filter__style-minimal { - --vp-filter-minimal--items__font-size: 0.7em; + --vp-filter-minimal--items__font-size: 0.7em; } .vp-filter__style-dropdown { - --vp-filter-dropdown--items__font-size: 0.7em; + --vp-filter-dropdown--items__font-size: 0.7em; } .vp-sort__style-default { - --vp-sort-default--items__font-size: 0.7em; + --vp-sort-default--items__font-size: 0.7em; } .vp-sort__style-minimal { - --vp-sort-minimal--items__font-size: 0.7em; + --vp-sort-minimal--items__font-size: 0.7em; } .vp-sort__style-dropdown { - --vp-sort-dropdown--items__font-size: 0.7em; + --vp-sort-dropdown--items__font-size: 0.7em; } .vp-pagination__style-default { - --vp-pagination-default--items__font-size: 0.7em; + --vp-pagination-default--items__font-size: 0.7em; } .vp-pagination__style-minimal { - --vp-pagination-minimal--items__font-size: 0.7em; + --vp-pagination-minimal--items__font-size: 0.7em; } diff --git a/src/assets/css/theme-twentytwentyone.scss b/assets/css/theme-twentytwentyone.scss similarity index 76% rename from src/assets/css/theme-twentytwentyone.scss rename to assets/css/theme-twentytwentyone.scss index b3c2fab8..2b590cbc 100644 --- a/src/assets/css/theme-twentytwentyone.scss +++ b/assets/css/theme-twentytwentyone.scss @@ -1,17 +1,17 @@ // Fix headings margin. .entry-content .vp-portfolio__item-meta-title { - margin-top: 0; + margin-top: 0; } // Fix caption margin. .vp-portfolio__item figcaption { - margin-top: 0; - margin-bottom: 0; + margin-top: 0; + margin-bottom: 0; } // Fix preview background color. .vp-preview-body { - background: none; + background: none; } // Fix images size. @@ -19,7 +19,7 @@ [data-vp-layout="tiles"] .vp-portfolio__item-img img, [data-vp-masonry-images-aspect-ratio*=":"] .vp-portfolio__item-img img, [data-vp-grid-images-aspect-ratio*=":"] .vp-portfolio__item-img img { - width: 100% !important; - max-width: none !important; - height: 100% !important; + width: 100% !important; + max-width: none !important; + height: 100% !important; } diff --git a/src/assets/css/theme-twentytwentytwo.scss b/assets/css/theme-twentytwentytwo.scss similarity index 68% rename from src/assets/css/theme-twentytwentytwo.scss rename to assets/css/theme-twentytwentytwo.scss index 404d0d83..457ff35c 100644 --- a/src/assets/css/theme-twentytwentytwo.scss +++ b/assets/css/theme-twentytwentytwo.scss @@ -1,11 +1,11 @@ // Fix headings margin. .vp-portfolio__item-meta-title { - margin-top: 0; + margin-top: 0; } // Fix top and bottom padding of main content in the popup iframe. .vp-popup-iframe .wp-site-blocks > main { - padding-top: 80px; - padding-bottom: 80px; - margin-top: 0; + padding-top: 80px; + padding-bottom: 80px; + margin-top: 0; } diff --git a/src/assets/images/logo-dark.svg b/assets/images/logo-dark.svg similarity index 100% rename from src/assets/images/logo-dark.svg rename to assets/images/logo-dark.svg diff --git a/src/assets/images/logo-white.svg b/assets/images/logo-white.svg similarity index 100% rename from src/assets/images/logo-white.svg rename to assets/images/logo-white.svg diff --git a/src/assets/images/placeholder.png b/assets/images/placeholder.png similarity index 100% rename from src/assets/images/placeholder.png rename to assets/images/placeholder.png diff --git a/assets/js/3rd/plugin-jetpack.js b/assets/js/3rd/plugin-jetpack.js new file mode 100644 index 00000000..3d4bc0f1 --- /dev/null +++ b/assets/js/3rd/plugin-jetpack.js @@ -0,0 +1,40 @@ +import { debounce } from 'throttle-debounce'; + +const { jQuery: $ } = window; + +let jetpackLazyImagesLoadEvent; +try { + jetpackLazyImagesLoadEvent = new Event('jetpack-lazy-images-load', { + bubbles: true, + cancelable: true, + }); +} catch (e) { + jetpackLazyImagesLoadEvent = document.createEvent('Event'); + jetpackLazyImagesLoadEvent.initEvent( + 'jetpack-lazy-images-load', + true, + true + ); +} + +// Fix AJAX loaded images. +$(document).on('loadedNewItems.vpf', function (event) { + if (event.namespace !== 'vpf') { + return; + } + + $('body').get(0).dispatchEvent(jetpackLazyImagesLoadEvent); +}); + +// Fix masonry reloading when Jetpack images lazy loaded. +const runReLayout = debounce(200, ($gallery) => { + $gallery.vpf('imagesLoaded'); +}); + +$(document.body).on('jetpack-lazy-loaded-image', '.vp-portfolio', function () { + const $this = $(this).closest('.vp-portfolio'); + + if ($this && $this.length) { + runReLayout($this); + } +}); diff --git a/assets/js/custom-scrollbar.js b/assets/js/custom-scrollbar.js new file mode 100644 index 00000000..f35b14eb --- /dev/null +++ b/assets/js/custom-scrollbar.js @@ -0,0 +1,119 @@ +/* + * Visual Portfolio custom scrollbar extension. + */ +const { jQuery: $, SimpleBar } = window; + +const $doc = $(document); + +// Don't run on Mac and mobile devices. +const allowScrollbar = + !/Mac|Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( + // eslint-disable-next-line no-undef + navigator.userAgent + ); + +if (allowScrollbar && typeof SimpleBar !== 'undefined') { + // Extend VP class. + $doc.on('extendClass.vpf', (event, VP) => { + if (event.namespace !== 'vpf') { + return; + } + + /** + * Init Simplebar plugin + */ + VP.prototype.initCustomScrollbar = function () { + const self = this; + + self.emitEvent('beforeInitCustomScrollbar'); + + self.$items_wrap + .find('.vp-portfolio__custom-scrollbar') + .each(function () { + const instance = SimpleBar.instances.get(this); + + if (!instance) { + // eslint-disable-next-line no-new + new SimpleBar(this); + } + }); + + self.emitEvent('initCustomScrollbar'); + }; + + /** + * Destroy Simplebar plugin + */ + VP.prototype.destroyCustomScrollbar = function () { + const self = this; + + self.$items_wrap + .find('[data-simplebar="init"].vp-portfolio__custom-scrollbar') + .each(function () { + const instance = SimpleBar.instances.get(this); + + if (instance) { + instance.unMount(); + } + }); + + self.emitEvent('destroyCustomScrollbar'); + }; + }); + + // Add Items. + $doc.on('addItems.vpf', (event, self, $items, removeExisting) => { + if (event.namespace !== 'vpf') { + return; + } + + if (removeExisting) { + self.destroyCustomScrollbar(); + } + + self.initCustomScrollbar(); + }); + + // Init. + $doc.on('init.vpf', (event, self) => { + if (event.namespace !== 'vpf') { + return; + } + + self.initCustomScrollbar(); + }); + + // Destroy. + $doc.on('destroy.vpf', (event, self) => { + if (event.namespace !== 'vpf') { + return; + } + + self.destroyCustomScrollbar(); + }); + + // Init Swiper duplicated slides scrollbars. + $doc.on('initSwiper.vpf', (event, self) => { + if (event.namespace !== 'vpf') { + return; + } + + if (self.options.sliderLoop === 'true') { + self.initCustomScrollbar(); + } + }); + + // Fix Simplebar content size in some themes. + // For example, in Astra theme in content with enabled sidebar, Simplebar calculate wrong height automatically. + $(() => { + $('[data-simplebar="init"].vp-portfolio__custom-scrollbar').each( + function () { + const instance = SimpleBar.instances.get(this); + + if (instance) { + instance.recalculate(); + } + } + ); + }); +} diff --git a/assets/js/items-style-fly.js b/assets/js/items-style-fly.js new file mode 100644 index 00000000..8a26b6f1 --- /dev/null +++ b/assets/js/items-style-fly.js @@ -0,0 +1,167 @@ +/* + * Visual Portfolio items style Fly. + */ +const $ = window.jQuery; +const $wnd = $(window); + +/** + * Check if lines cross + * + * @param {Object} a - first point of the first line + * @param {Object} b - second point of the first line + * @param {Object} c - first point of the second line + * @param {Object} d - second point of the second line + * + * @return {boolean} cross lines + */ +function isCrossLine(a, b, c, d) { + // Working code #1: + // + // var common = (b.x - a.x)*(d.y - c.y) - (b.y - a.y)*(d.x - c.x); + // if (common === 0) { + // return false; + // } + // + // var rH = (a.y - c.y)*(d.x - c.x) - (a.x - c.x)*(d.y - c.y); + // var sH = (a.y - c.y)*(b.x - a.x) - (a.x - c.x)*(b.y - a.y); + // + // var r = rH / common; + // var s = sH / common; + // + // return r >= 0 && r <= 1 && s >= 0 && s <= 1; + + // Working code #2: + const v1 = (d.x - c.x) * (a.y - c.y) - (d.y - c.y) * (a.x - c.x); + const v2 = (d.x - c.x) * (b.y - c.y) - (d.y - c.y) * (b.x - c.x); + const v3 = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); + const v4 = (b.x - a.x) * (d.y - a.y) - (b.y - a.y) * (d.x - a.x); + return v1 * v2 <= 0 && v3 * v4 <= 0; +} + +// Init Events. +$(document).on('initEvents.vpf', (event, self) => { + if (event.namespace !== 'vpf' || self.options.itemsStyle !== 'fly') { + return; + } + + const evp = `.vpf-uid-${self.uid}`; + + // determine cursor position + let lastCursorPos = {}; + $wnd.on(`mousemove${evp}`, (e) => { + lastCursorPos = { + x: e.clientX, + y: e.clientY, + }; + }); + + self.$item.on( + `mouseenter${evp} mouseleave${evp}`, + '.vp-portfolio__item', + function (e) { + const $this = $(this); + const itemRect = $this[0].getBoundingClientRect(); + const $overlay = $this.find('.vp-portfolio__item-overlay'); + const enter = e.type === 'mouseenter'; + let endX = '0%'; + let endY = '0%'; + const curCursorPos = { + x: e.clientX, + y: e.clientY, + }; + + // find the corner that placed on cursor path. + let isUp = isCrossLine( + { x: itemRect.left, y: itemRect.top }, + { x: itemRect.left + itemRect.width, y: itemRect.top }, + curCursorPos, + lastCursorPos + ); + let isDown = isCrossLine( + { x: itemRect.left, y: itemRect.top + itemRect.height }, + { + x: itemRect.left + itemRect.width, + y: itemRect.top + itemRect.height, + }, + curCursorPos, + lastCursorPos + ); + let isLeft = isCrossLine( + { x: itemRect.left, y: itemRect.top }, + { x: itemRect.left, y: itemRect.top + itemRect.height }, + curCursorPos, + lastCursorPos + ); + let isRight = isCrossLine( + { x: itemRect.left + itemRect.width, y: itemRect.top }, + { + x: itemRect.left + itemRect.width, + y: itemRect.top + itemRect.height, + }, + curCursorPos, + lastCursorPos + ); + + // Sometimes isCrossLine returned false, so we need to check direction manually (less accurate, but it is not a big problem). + if (!isUp && !isDown && !isLeft && !isRight) { + const x = + (itemRect.width / 2 - curCursorPos.x + itemRect.left) / + (itemRect.width / 2); + const y = + (itemRect.height / 2 - curCursorPos.y + itemRect.top) / + (itemRect.height / 2); + if (Math.abs(x) > Math.abs(y)) { + if (x > 0) { + isLeft = true; + } else { + isRight = true; + } + } else if (y > 0) { + isUp = true; + } else { + isDown = true; + } + } + + if (isUp) { + endY = '-100.1%'; + } else if (isDown) { + endY = '100.1%'; + } else if (isLeft) { + endX = '-100.1%'; + } else if (isRight) { + endX = '100.1%'; + } + + if (enter) { + $overlay.css({ + transition: 'none', + transform: `translateX(${endX}) translateY(${endY}) translateZ(0)`, + }); + // Trigger a reflow, flushing the CSS changes. This need to fix some glithes in Safari and Firefox. + // Info here - https://stackoverflow.com/questions/11131875/what-is-the-cleanest-way-to-disable-css-transition-effects-temporarily + // eslint-disable-next-line no-unused-expressions + $overlay[0].offsetHeight; + } + + $overlay.css({ + transition: '.2s transform ease-in-out', + transform: `translateX(${enter ? '0%' : endX}) translateY(${ + enter ? '0%' : endY + }) translateZ(0)`, + }); + } + ); +}); + +// Destroy Events. +$(document).on('destroyEvents.vpf', (event, self) => { + if (event.namespace !== 'vpf' || self.options.itemsStyle !== 'fly') { + return; + } + + const evp = `.vpf-uid-${self.uid}`; + + $wnd.off(`mousemove${evp}`); + self.$item.off(`mouseenter${evp} mouseleave${evp}`); +}); diff --git a/assets/js/layout-grid.js b/assets/js/layout-grid.js new file mode 100644 index 00000000..a911c3f8 --- /dev/null +++ b/assets/js/layout-grid.js @@ -0,0 +1,187 @@ +/* eslint-disable no-underscore-dangle */ +/* + * Visual Portfolio layout Grid. + */ +const $ = window.jQuery; + +const { screenSizes } = window.VPData; + +// +// Our custom Grid layout for Isotope. +// +// * fixes grid items position in FireFox - https://wordpress.org/support/topic/gallery-difference-between-firefox-and-all-other-browsers/ +// +if ( + typeof window.Isotope !== 'undefined' && + typeof window.Isotope.LayoutMode !== 'undefined' +) { + const VPRows = window.Isotope.LayoutMode.create('vpRows'); + const proto = VPRows.prototype; + + proto.measureColumns = function () { + // set items, used if measuring first item + this.items = this.isotope.filteredItems; + + this.getContainerWidth(); + + // if columnWidth is 0, default to outerWidth of first item + if (!this.columnWidth) { + const firstItem = this.items[0]; + const firstItemElem = firstItem && firstItem.element; + + // columnWidth fall back to item of first element + this.columnWidth = + (firstItemElem && window.getSize(firstItemElem).outerWidth) || + // if first elem has no width, default to size of container + this.containerWidth; + } + + this.columnWidth += this.gutter; + + // calculate columns + const containerWidth = this.containerWidth + this.gutter; + let cols = containerWidth / this.columnWidth; + + // fix rounding errors, typically with gutters + const excess = this.columnWidth - (containerWidth % this.columnWidth); + + // if overshoot is less than a pixel, round up, otherwise floor it + const mathMethod = excess && excess < 1 ? 'round' : 'floor'; + + cols = Math[mathMethod](cols); + this.cols = Math.max(cols, 1); + }; + + proto.getContainerWidth = function () { + // container is parent if fit width + const isFitWidth = this._getOption + ? this._getOption('fitWidth') + : false; + const container = isFitWidth ? this.element.parentNode : this.element; + + // check that this.size and size are there + // IE8 triggers resize on body size change, so they might not be + const size = window.getSize(container); + this.containerWidth = size && size.innerWidth; + }; + + proto._resetLayout = function () { + this.x = 0; + this.y = 0; + this.maxY = 0; + this.horizontalColIndex = 0; + + this._getMeasurement('columnWidth', 'outerWidth'); + this._getMeasurement('gutter', 'outerWidth'); + this.measureColumns(); + }; + + proto._getItemLayoutPosition = function (item) { + item.getSize(); + + // how many columns does this brick span + const remainder = item.size.outerWidth % this.columnWidth; + const mathMethod = remainder && remainder < 1 ? 'round' : 'ceil'; + + // round if off by 1 pixel, otherwise use ceil + let colSpan = Math[mathMethod](item.size.outerWidth / this.columnWidth); + colSpan = Math.min(colSpan, this.cols); + + let col = this.horizontalColIndex % this.cols; + const isOver = colSpan > 1 && col + colSpan > this.cols; + + // shift to next row if item can't fit on current row + col = isOver ? 0 : col; + + // don't let zero-size items take up space + const hasSize = item.size.outerWidth && item.size.outerHeight; + this.horizontalColIndex = hasSize + ? col + colSpan + : this.horizontalColIndex; + + const itemWidth = item.size.outerWidth + this.gutter; + + // if this element cannot fit in the current row + if (this.x !== 0 && this.horizontalColIndex === 1) { + this.x = 0; + this.y = this.maxY; + } + + const position = { + x: this.x, + y: this.y, + }; + + this.maxY = Math.max(this.maxY, this.y + item.size.outerHeight); + this.x += itemWidth; + + return position; + }; + + proto._getContainerSize = function () { + return { height: this.maxY }; + }; +} + +// Init Options. +$(document).on('initOptions.vpf', (event, self) => { + if (event.namespace !== 'vpf') { + return; + } + + self.defaults.gridColumns = 3; + + if (!self.options.gridColumns) { + self.options.gridColumns = self.defaults.gridColumns; + } + if (!self.options.gridImagesAspectRatio) { + self.options.gridImagesAspectRatio = + self.defaults.gridImagesAspectRatio; + } +}); + +// Init Layout. +$(document).on('initLayout.vpf', (event, self) => { + if (event.namespace !== 'vpf') { + return; + } + + if (self.options.layout !== 'grid') { + return; + } + + // columns. + self.addStyle('.vp-portfolio__item-wrap', { + width: `${100 / self.options.gridColumns}%`, + }); + + // calculate responsive. + let count = self.options.gridColumns - 1; + let currentPoint = Math.min(screenSizes.length - 1, count); + + for (; currentPoint >= 0; currentPoint -= 1) { + if (count > 0 && typeof screenSizes[currentPoint] !== 'undefined') { + self.addStyle( + '.vp-portfolio__item-wrap', + { + width: `${100 / count}%`, + }, + `screen and (max-width: ${screenSizes[currentPoint]}px)` + ); + } + count -= 1; + } +}); + +// Change Isotope Layout Mode. +$(document).on('beforeInitIsotope.vpf', (event, self, initOptions) => { + if (event.namespace !== 'vpf') { + return; + } + + if (self.options.layout !== 'grid') { + return; + } + + initOptions.layoutMode = 'vpRows'; +}); diff --git a/assets/js/layout-justified.js b/assets/js/layout-justified.js new file mode 100644 index 00000000..d244faef --- /dev/null +++ b/assets/js/layout-justified.js @@ -0,0 +1,22 @@ +/* + * Visual Portfolio layout Justified. + */ +const $ = window.jQuery; + +// Init Options. +$(document).on('initOptions.vpf', (event, self) => { + if (event.namespace !== 'vpf') { + return; + } + + self.defaults.justifiedRowHeight = 250; + self.defaults.justifiedRowHeightTolerance = 0.25; + + if (!self.options.justifiedRowHeight) { + self.options.justifiedRowHeight = self.defaults.justifiedRowHeight; + } + if (!self.options.justifiedRowHeightTolerance) { + self.options.justifiedRowHeightTolerance = + self.defaults.justifiedRowHeightTolerance; + } +}); diff --git a/assets/js/layout-masonry.js b/assets/js/layout-masonry.js new file mode 100644 index 00000000..52a9807b --- /dev/null +++ b/assets/js/layout-masonry.js @@ -0,0 +1,57 @@ +/* eslint-disable no-param-reassign */ +/* + * Visual Portfolio layout Masonry. + */ +const $ = window.jQuery; + +const { screenSizes } = window.VPData; + +// Init Options. +$(document).on('initOptions.vpf', (event, self) => { + if (event.namespace !== 'vpf') { + return; + } + + self.defaults.masonryColumns = 3; + + if (!self.options.masonryColumns) { + self.options.masonryColumns = self.defaults.masonryColumns; + } + if (!self.options.masonryImagesAspectRatio) { + self.options.masonryImagesAspectRatio = + self.defaults.masonryImagesAspectRatio; + } +}); + +// Init Layout. +$(document).on('initLayout.vpf', (event, self) => { + if (event.namespace !== 'vpf') { + return; + } + + if (self.options.layout !== 'masonry') { + return; + } + + // columns. + self.addStyle('.vp-portfolio__item-wrap', { + width: `${100 / self.options.masonryColumns}%`, + }); + + // calculate responsive. + let count = self.options.masonryColumns - 1; + let currentPoint = Math.min(screenSizes.length - 1, count); + + for (; currentPoint >= 0; currentPoint -= 1) { + if (count > 0 && typeof screenSizes[currentPoint] !== 'undefined') { + self.addStyle( + '.vp-portfolio__item-wrap', + { + width: `${100 / count}%`, + }, + `screen and (max-width: ${screenSizes[currentPoint]}px)` + ); + } + count -= 1; + } +}); diff --git a/assets/js/layout-slider.js b/assets/js/layout-slider.js new file mode 100644 index 00000000..4d397776 --- /dev/null +++ b/assets/js/layout-slider.js @@ -0,0 +1,159 @@ +/* + * External dependencies. + */ +import isNumber from 'is-number'; +//import { throttle } from '@wordpress/compose'; +import { throttle } from 'throttle-debounce'; + +/* + * Visual Portfolio layout Slider. + */ +const $ = window.jQuery; + +// Listen for slider width change to calculate dynamic height of images. +// eslint-disable-next-line no-undef +const dynamicHeightObserver = new ResizeObserver( + throttle(100, (entries) => { + entries.forEach(({ target }) => { + if (target && target.vpf) { + const self = target.vpf; + + const calculatedHeight = + (self.$item.width() * + parseFloat(self.options.sliderItemsHeight)) / + 100; + + target + .querySelector('.vp-portfolio__items-wrap') + .style.setProperty( + '--vp-layout-slider--auto-items__height', + `${calculatedHeight}px` + ); + } + }); + }) +); + +// Init Layout. +$(document).on('initLayout.vpf', (event, self) => { + if (event.namespace !== 'vpf') { + return; + } + + if (self.options.layout !== 'slider') { + return; + } + + ['items', 'thumbnails'].forEach((type) => { + let itemsHeight = + type === 'items' + ? self.options.sliderItemsHeight + : self.options.sliderThumbnailsHeight; + let itemsMinHeight = + type === 'items' ? self.options.sliderItemsMinHeight : 0; + // eslint-disable-next-line @wordpress/no-unused-vars-before-return + const typeSingle = type.replace(/s$/g, ''); + + if (itemsHeight === 'auto') { + return; + } + + itemsHeight = isNumber(itemsHeight) ? `${itemsHeight}px` : itemsHeight; + + // prevent minHeight option in preview, when used 'vh' units. + if (itemsMinHeight && self.isPreview() && /vh/.test(itemsMinHeight)) { + itemsMinHeight = 0; + } + + const itemsPerView = + type === 'items' + ? self.options.sliderSlidesPerView + : self.options.sliderThumbnailsPerView; + + if (itemsPerView === 'auto') { + // fix fade slider items width. + // https://github.com/nk-crew/visual-portfolio/issues/95. + let itemsWidth = 'auto'; + if (type === 'items' && self.options.sliderEffect === 'fade') { + itemsWidth = '100%'; + } + + // Calculate dynamic height. + // Previously we tried the pure CSS solution, but there was couple bugs like: + // - Classic styles items wrong height + // - FireFox wrong images width render + if (itemsHeight.indexOf('%') === itemsHeight.length - 1) { + dynamicHeightObserver.observe(self.$item[0]); + + // Static height. + } else { + self.addStyle(`.vp-portfolio__${type}-wrap`, { + '--vp-layout-slider--auto-items__height': itemsHeight, + }); + } + + self.addStyle(`.vp-portfolio__${typeSingle}-wrap`, { + width: 'auto', + }); + self.addStyle( + `.vp-portfolio__${typeSingle} .vp-portfolio__${typeSingle}-img img`, + { + width: itemsWidth, + height: 'var(--vp-layout-slider--auto-items__height)', + } + ); + + // min height. + if (itemsMinHeight) { + self.addStyle( + `.vp-portfolio__${typeSingle} .vp-portfolio__${typeSingle}-img img`, + { + 'min-height': itemsMinHeight, + } + ); + } + } else { + // We have to use this hack with Before to support Dynamic height. + // Also, previously we used the `margin-top`, + // but it is not working correctly with Items Mininmal Height option. + self.addStyle(`.vp-portfolio__${typeSingle}-img-wrap::before`, { + 'padding-top': itemsHeight, + }); + self.addStyle(`.vp-portfolio__${typeSingle}-img img`, { + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + }); + self.addStyle(`.vp-portfolio__${typeSingle}-img`, { + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + }); + self.addStyle( + `.vp-portfolio__${typeSingle} .vp-portfolio__${typeSingle}-img img`, + { + width: '100%', + height: '100%', + } + ); + + // min height. + if (itemsMinHeight) { + self.addStyle(`.vp-portfolio__${typeSingle}-img-wrap::before`, { + 'min-height': itemsMinHeight, + }); + } + } + }); + + // thumbnails top gap. + if (self.options.sliderThumbnailsGap) { + self.addStyle('.vp-portfolio__thumbnails-wrap', { + 'margin-top': `${self.options.sliderThumbnailsGap}px`, + }); + } +}); diff --git a/assets/js/layout-tiles.js b/assets/js/layout-tiles.js new file mode 100644 index 00000000..b24a2f98 --- /dev/null +++ b/assets/js/layout-tiles.js @@ -0,0 +1,182 @@ +/* + * Visual Portfolio layout Tiles. + */ +const $ = window.jQuery; + +const { screenSizes } = window.VPData; + +// fix masonry items position for Tiles layout. +// https://github.com/nk-crew/visual-portfolio/issues/111 +if ( + typeof window.Isotope !== 'undefined' && + typeof window.Isotope.LayoutMode !== 'undefined' +) { + const MasonryMode = window.Isotope.LayoutMode.modes.masonry; + + if (MasonryMode) { + const defaultMeasureColumns = MasonryMode.prototype.measureColumns; + MasonryMode.prototype.measureColumns = function () { + let runDefault = true; + + // if columnWidth is 0, default to columns count size. + if (!this.columnWidth) { + const $vp = $(this.element).closest( + '.vp-portfolio[data-vp-layout="tiles"]' + ); + + // change column size for Tiles type only. + if ($vp.length && $vp[0].vpf) { + this.getContainerWidth(); + + const { vpf } = $vp[0]; + const settings = vpf.getTilesSettings(); + + // get columns number + let columns = parseInt(settings[0], 10) || 1; + + // calculate responsive. + let count = columns - 1; + let currentPoint = Math.min(screenSizes.length - 1, count); + + for (; currentPoint >= 0; currentPoint -= 1) { + if ( + count > 0 && + typeof screenSizes[currentPoint] !== 'undefined' + ) { + if ( + window.innerWidth <= screenSizes[currentPoint] + ) { + columns = count; + } + } + count -= 1; + } + + if (columns) { + this.columnWidth = this.containerWidth / columns; + this.columnWidth += this.gutter; + this.cols = columns; + runDefault = false; + } + } + } + + if (runDefault) { + defaultMeasureColumns.call(this); + } + }; + } +} + +// Extend VP class. +$(document).on('extendClass.vpf', (event, VP) => { + if (event.namespace !== 'vpf') { + return; + } + + /** + * Get Tiles Layout Settings + * + * @return {string} tiles layout + */ + VP.prototype.getTilesSettings = function () { + const self = this; + + const layoutArr = self.options.tilesType.split(/[:|]/); + + // remove last empty item + if ( + typeof layoutArr[layoutArr.length - 1] !== 'undefined' && + !layoutArr[layoutArr.length - 1] + ) { + layoutArr.pop(); + } + + return layoutArr; + }; +}); + +// Init Options. +$(document).on('initOptions.vpf', (event, self) => { + if (event.namespace !== 'vpf') { + return; + } + + self.defaults.tilesType = '3|1,1|'; + + if (!self.options.tilesType) { + self.options.tilesType = self.defaults.tilesType; + } +}); + +// Init Layout. +$(document).on('initLayout.vpf', (event, self) => { + if (event.namespace !== 'vpf') { + return; + } + + if (self.options.layout !== 'tiles') { + return; + } + + const settings = self.getTilesSettings(); + + // get columns number + const columns = parseInt(settings[0], 10) || 1; + settings.shift(); + + // set columns + self.addStyle('.vp-portfolio__item-wrap', { + width: `${100 / columns}%`, + }); + + // set items sizes + if (settings && settings.length) { + for (let k = 0; k < settings.length; k += 1) { + const size = settings[k].split(','); + const w = parseFloat(size[0]) || 1; + const h = parseFloat(size[1]) || 1; + + let itemSelector = '.vp-portfolio__item-wrap'; + if (settings.length > 1) { + itemSelector += `:nth-of-type(${settings.length}n+${k + 1})`; + } + + if (w && w !== 1) { + self.addStyle(itemSelector, { + width: `${(w * 100) / columns}%`, + }); + } + self.addStyle( + `${itemSelector} .vp-portfolio__item-img-wrap::before`, + { + 'padding-top': `${h * 100}%`, + } + ); + } + } + + // calculate responsive. + let count = columns - 1; + let currentPoint = Math.min(screenSizes.length - 1, count); + + for (; currentPoint >= 0; currentPoint -= 1) { + if (count > 0 && typeof screenSizes[currentPoint] !== 'undefined') { + self.addStyle( + '.vp-portfolio__item-wrap', + { + width: `${100 / count}%`, + }, + `screen and (max-width: ${screenSizes[currentPoint]}px)` + ); + self.addStyle( + '.vp-portfolio__item-wrap:nth-of-type(n)', + { + width: `${100 / count}%`, + }, + `screen and (max-width: ${screenSizes[currentPoint]}px)` + ); + } + count -= 1; + } +}); diff --git a/assets/js/lazyload-fallback.js b/assets/js/lazyload-fallback.js new file mode 100644 index 00000000..73f01eb2 --- /dev/null +++ b/assets/js/lazyload-fallback.js @@ -0,0 +1,26 @@ +/* + * Visual Portfolio images lazy load fallback for browsers + * which does not support CSS :has() + */ + +// Lazyloaded - remove preloader images placeholder effect. +document.addEventListener('lazybeforeunveil', (e) => { + const vpfImgWrapper = e.target.closest( + '.vp-portfolio__item-img, .vp-portfolio__thumbnail-img' + ); + + if (vpfImgWrapper) { + vpfImgWrapper.classList.add('vp-has-lazyloading'); + } +}); + +document.addEventListener('lazyloaded', (e) => { + const vpfImgWrapper = e.target.closest( + '.vp-portfolio__item-img, .vp-portfolio__thumbnail-img' + ); + + if (vpfImgWrapper) { + vpfImgWrapper.classList.add('vp-has-lazyloaded'); + vpfImgWrapper.classList.add('vp-has-lazyloading'); + } +}); diff --git a/src/assets/js/lazyload.js b/assets/js/lazyload.js similarity index 52% rename from src/assets/js/lazyload.js rename to assets/js/lazyload.js index 40a2484e..7cd127bf 100644 --- a/src/assets/js/lazyload.js +++ b/assets/js/lazyload.js @@ -4,16 +4,16 @@ // Recalculate image size if parent is document.addEventListener('lazybeforesizes', (e) => { - // for some reason sometimes e.detail is undefined, so we need to check it. - if (!e.detail || !e.detail.width || !e.target) { - return; - } + // for some reason sometimes e.detail is undefined, so we need to check it. + if (!e.detail || !e.detail.width || !e.target) { + return; + } - const parent = e.target.closest(':not(picture)'); + const parent = e.target.closest(':not(picture)'); - if (parent) { - e.detail.width = parent.clientWidth || e.detail.width; - } + if (parent) { + e.detail.width = parent.clientWidth || e.detail.width; + } }); /** @@ -22,9 +22,9 @@ document.addEventListener('lazybeforesizes', (e) => { * Related topic: https://wordpress.org/support/topic/visual-portfolio-and-sg-optimizer-dont-play-well/ */ document.addEventListener('lazybeforeunveil', (e) => { - const prevEl = e.target.previousElementSibling; + const prevEl = e.target.previousElementSibling; - if (prevEl && prevEl.matches('noscript')) { - prevEl.remove(); - } + if (prevEl && prevEl.matches('noscript')) { + prevEl.remove(); + } }); diff --git a/assets/js/lazysizes-cfg.js b/assets/js/lazysizes-cfg.js new file mode 100644 index 00000000..ba410bf4 --- /dev/null +++ b/assets/js/lazysizes-cfg.js @@ -0,0 +1,15 @@ +/* + * Visual Portfolio images lazy load. + */ +window.lazySizesConfig = window.lazySizesConfig || {}; + +window.lazySizesConfig = { + ...window.lazySizesConfig, + lazyClass: 'vp-lazyload', + loadedClass: 'vp-lazyloaded', + preloadClass: 'vp-lazypreload', + loadingClass: 'vp-lazyloading', + srcAttr: 'data-src', + srcsetAttr: 'data-srcset', + sizesAttr: 'data-sizes', +}; diff --git a/assets/js/lazysizes-object-fit-cover.js b/assets/js/lazysizes-object-fit-cover.js new file mode 100644 index 00000000..9daea632 --- /dev/null +++ b/assets/js/lazysizes-object-fit-cover.js @@ -0,0 +1,68 @@ +/* eslint-disable wrap-iife */ +(function (window, factory) { + const globalInstall = function () { + factory(window.lazySizes); + window.removeEventListener('lazyunveilread', globalInstall, true); + }; + factory = factory.bind(null, window, window.document); + + if (window.lazySizes) { + globalInstall(); + } else { + window.addEventListener('lazyunveilread', globalInstall, true); + } +})(window, (window, document, lazySizes) => { + if (!window.addEventListener) { + return; + } + + const getCSS = function (elem) { + return window.getComputedStyle(elem, null) || {}; + }; + + const objectFitCover = { + calculateSize(element, width) { + const CSS = getCSS(element); + + if (CSS && CSS.objectFit && CSS.objectFit === 'cover') { + const blockHeight = parseInt( + element.getAttribute('height'), + 10 + ); + const blockWidth = parseInt(element.getAttribute('width'), 10); + + if (blockHeight) { + if ( + blockWidth / blockHeight > + element.clientWidth / element.clientHeight + ) { + width = parseInt( + (element.clientHeight * blockWidth) / blockHeight, + 10 + ); + } + } + } + + return width; + }, + }; + + lazySizes.objectFitCover = objectFitCover; + + document.addEventListener('lazybeforesizes', (e) => { + // for some reason sometimes e.detail is undefined, so we need to check it. + if ( + e.defaultPrevented || + !e.detail || + !e.detail.width || + !e.target || + e.detail.instance !== lazySizes + ) { + return; + } + + const element = e.target; + e.detail.width = objectFitCover.calculateSize(element, e.detail.width); + }); +}); diff --git a/assets/js/lazysizes-swiper-duplicates-load.js b/assets/js/lazysizes-swiper-duplicates-load.js new file mode 100644 index 00000000..223ef3ee --- /dev/null +++ b/assets/js/lazysizes-swiper-duplicates-load.js @@ -0,0 +1,92 @@ +/* eslint-disable wrap-iife */ +/** + * Load duplicated Swiper slides to prevent images "blink" effect after swipe. + * + * @param window + * @param factory + */ +(function (window, factory) { + const globalInstall = function () { + factory(window.lazySizes); + window.removeEventListener('lazyunveilread', globalInstall, true); + }; + factory = factory.bind(null, window, window.document); + + if (window.lazySizes) { + globalInstall(); + } else { + window.addEventListener('lazyunveilread', globalInstall, true); + } +})(window, (window, document, lazySizes) => { + if (!window.addEventListener) { + return; + } + + const { unveil } = lazySizes.loader; + + const getSiblings = (el, filter) => + [...el.parentNode.children].filter( + (child) => + child.nodeType === 1 && + child !== el && + (!filter || child.matches(filter)) + ); + + const swiperDuplicatesLoad = { + getSlideData(element) { + const $el = element.closest('.swiper-slide'); + const slideIndex = $el + ? $el.getAttribute('data-swiper-slide-index') + : false; + + return { + $el, + slideIndex, + }; + }, + run(element) { + const slideData = this.getSlideData(element); + + if (slideData.slideIndex) { + const $siblingDuplicates = getSiblings( + slideData.$el, + `[data-swiper-slide-index="${slideData.slideIndex}"]` + ); + + $siblingDuplicates.forEach((el) => { + // We should also get images in `loading` state, because in some rare situations + // duplicated images by default has this class and not displaying correctly. + const $images = el.querySelectorAll( + 'img.vp-lazyload, img.vp-lazyloading' + ); + + if ($images) { + $images.forEach(($img) => { + unveil($img); + }); + } + }); + } + + return true; + }, + }; + + lazySizes.swiperDuplicatesLoad = swiperDuplicatesLoad; + + document.addEventListener('lazyloaded', (e) => { + // for some reason sometimes e.detail is undefined, so we need to check it. + if ( + e.defaultPrevented || + !e.detail || + e.detail.swiperDuplicatesChecked || + !e.target || + e.detail.instance !== lazySizes + ) { + return; + } + + const element = e.target; + e.detail.swiperDuplicatesChecked = swiperDuplicatesLoad.run(element); + }); +}); diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 00000000..bf790323 --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,1004 @@ +/* + * Visual Portfolio main script. + */ +//import { throttle } from '@wordpress/compose'; +import { throttle } from 'throttle-debounce'; +import rafSchd from 'raf-schd'; + +/** + * Global Variables + */ +const { jQuery: $, VPData } = window; + +const { __ } = VPData; + +const $wnd = $(window); + +/** + * Emit Resize Event. + */ +function windowResizeEmit() { + if (typeof window.Event === 'function') { + // modern browsers + window.dispatchEvent(new window.Event('resize')); + } else { + // for IE and other old browsers + // causes deprecation warning on modern browsers + const evt = window.document.createEvent('UIEvents'); + evt.initUIEvent('resize', true, false, window, 0); + window.dispatchEvent(evt); + } +} + +const visibilityData = {}; +let shouldCheckVisibility = false; +let checkVisibilityTimeout = false; +let isFocusVisible = false; + +// fix portfolio inside Tabs and Accordions +// check visibility by timer https://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom/33456469 +// +// https://github.com/nk-crew/visual-portfolio/issues/11 +// https://github.com/nk-crew/visual-portfolio/issues/113 +function checkVisibility() { + clearTimeout(checkVisibilityTimeout); + + if (!shouldCheckVisibility) { + return; + } + + const $items = $('.vp-portfolio__ready'); + + if ($items.length) { + let isVisibilityChanged = false; + + $items.each(function () { + const { vpf } = this; + + if (!vpf) { + return; + } + + const currentState = visibilityData[vpf.uid] || 'none'; + + visibilityData[vpf.uid] = + this.offsetParent === null ? 'hidden' : 'visible'; + + // changed from hidden to visible. + if ( + currentState === 'hidden' && + visibilityData[vpf.uid] === 'visible' + ) { + isVisibilityChanged = true; + } + }); + + // resize, if visibility changed. + if (isVisibilityChanged) { + windowResizeEmit(); + } + } else { + shouldCheckVisibility = false; + } + + // run again. + checkVisibilityTimeout = setTimeout(checkVisibility, 500); +} + +// run check function only after portfolio inited. +$(document).on('inited.vpf', (event) => { + if (event.namespace !== 'vpf') { + return; + } + + shouldCheckVisibility = true; + + checkVisibility(); +}); + +/** + * If the most recent user interaction was via the keyboard; + * and the key press did not include a meta, alt/option, or control key; + * then the modality is keyboard. Otherwise, the modality is not keyboard. + */ +document.addEventListener( + 'keydown', + function (e) { + if (e.metaKey || e.altKey || e.ctrlKey) { + return; + } + + isFocusVisible = true; + }, + true +); + +/** + * If at any point a user clicks with a pointing device, ensure that we change + * the modality away from keyboard. + * This avoids the situation where a user presses a key on an already focused + * element, and then clicks on a different element, focusing it with a + * pointing device, while we still think we're in keyboard modality. + */ +document.addEventListener( + 'mousedown', + () => { + isFocusVisible = false; + }, + true +); +document.addEventListener( + 'pointerdown', + () => { + isFocusVisible = false; + }, + true +); +document.addEventListener( + 'touchstart', + () => { + isFocusVisible = false; + }, + true +); + +/** + * Main VP class + */ +class VP { + constructor($item, userOptions) { + const self = this; + + self.$item = $item; + + // get id from class + const classes = $item[0].className.split(/\s+/); + for (let k = 0; k < classes.length; k += 1) { + if (classes[k] && /^vp-uid-/.test(classes[k])) { + self.uid = classes[k].replace(/^vp-uid-/, ''); + } + if (classes[k] && /^vp-id-/.test(classes[k])) { + self.id = classes[k].replace(/^vp-id-/, ''); + } + } + if (!self.uid) { + // eslint-disable-next-line no-console + console.error(__.couldnt_retrieve_vp); + return; + } + + self.href = window.location.href; + + self.$items_wrap = $item.find('.vp-portfolio__items'); + self.$slider_thumbnails_wrap = $item.find('.vp-portfolio__thumbnails'); + self.$pagination = $item.find('.vp-portfolio__pagination-wrap'); + self.$filter = $item.find('.vp-portfolio__filter-wrap'); + self.$sort = $item.find('.vp-portfolio__sort-wrap'); + + // find single filter block. + if (self.id) { + self.$filter = self.$filter.add( + `.vp-single-filter.vp-id-${self.id} .vp-portfolio__filter-wrap` + ); + } + + // find single sort block. + if (self.id) { + self.$sort = self.$sort.add( + `.vp-single-sort.vp-id-${self.id} .vp-portfolio__sort-wrap` + ); + } + + // user options + self.userOptions = userOptions; + + self.firstRun = true; + + self.init(); + } + + // emit event + // Example: + // $(document).on('init.vpf', function (event, infiniteObject) { + // console.log(infiniteObject); + // }); + emitEvent(event, data) { + data = data ? [this].concat(data) : [this]; + this.$item.trigger(`${event}.vpf`, data); + this.$item.trigger(`${event}.vpf-uid-${this.uid}`, data); + } + + /** + * Init + */ + init() { + const self = this; + + // destroy if already inited + if (!self.firstRun) { + self.destroy(); + } + + self.destroyed = false; + + self.$item.addClass('vp-portfolio__ready'); + + // init options + self.initOptions(); + + // init events + self.initEvents(); + + // init layout + self.initLayout(); + + // init custom colors + self.initCustomColors(); + + self.emitEvent('init'); + + if (self.id) { + $(`.vp-single-filter.vp-id-${self.id}`) + .addClass('vp-single-filter__ready') + .parent('.vp-portfolio__layout-elements') + .addClass('vp-portfolio__layout-elements__ready'); + $(`.vp-single-sort.vp-id-${self.id}`) + .addClass('vp-single-sort__ready') + .parent('.vp-portfolio__layout-elements') + .addClass('vp-portfolio__layout-elements__ready'); + } + + // resized + self.resized(); + + // images loaded + self.imagesLoaded(); + + self.emitEvent('inited'); + + self.firstRun = false; + } + + /** + * Check if script loaded in preview. + * + * @return {boolean} is in preview. + */ + isPreview() { + const self = this; + + return !!self.$item.closest('#vp_preview').length; + } + + /** + * Called after resized container. + */ + resized() { + windowResizeEmit(); + + this.emitEvent('resized'); + } + + /** + * Images loaded. + */ + imagesLoaded() { + const self = this; + + if (!self.$items_wrap.imagesLoaded) { + return; + } + + self.$items_wrap.imagesLoaded().progress(() => { + this.emitEvent('imagesLoaded'); + }); + } + + /** + * Destroy + */ + destroy() { + const self = this; + + // remove loaded class + self.$item.removeClass('vp-portfolio__ready'); + + if (self.id) { + $(`.vp-single-filter.vp-id-${self.id}`) + .removeClass('vp-single-filter__ready') + .parent('.vp-portfolio__layout-elements') + .removeClass('vp-portfolio__layout-elements__ready'); + $(`.vp-single-sort.vp-id-${self.id}`) + .removeClass('vp-single-sort__ready') + .parent('.vp-portfolio__layout-elements') + .removeClass('vp-portfolio__layout-elements__ready'); + } + + // destroy events + self.destroyEvents(); + + // remove all generated styles + self.removeStyle(); + self.renderStyle(); + + self.emitEvent('destroy'); + + self.destroyed = true; + } + + /** + * Add style to the current portfolio list + * + * @param {string} selector css selector + * @param {string} styles object with styles + * @param {string} media string with media query + */ + addStyle(selector, styles, media) { + media = media || ''; + + const self = this; + const { uid } = self; + + if (!self.stylesList) { + self.stylesList = {}; + } + + if (typeof self.stylesList[uid] === 'undefined') { + self.stylesList[uid] = {}; + } + if (typeof self.stylesList[uid][media] === 'undefined') { + self.stylesList[uid][media] = {}; + } + if (typeof self.stylesList[uid][media][selector] === 'undefined') { + self.stylesList[uid][media][selector] = {}; + } + + self.stylesList[uid][media][selector] = $.extend( + self.stylesList[uid][media][selector], + styles + ); + + self.emitEvent('addStyle', [selector, styles, media, self.stylesList]); + } + + /** + * Remove style from the current portfolio list + * + * @param {string} selector css selector (if not set - removed all styles) + * @param {string} styles object with styles + * @param {string} media string with media query + */ + removeStyle(selector, styles, media) { + media = media || ''; + + const self = this; + const { uid } = self; + + if (!self.stylesList) { + self.stylesList = {}; + } + + if (typeof self.stylesList[uid] !== 'undefined' && !selector) { + self.stylesList[uid] = {}; + } + + if ( + typeof self.stylesList[uid] !== 'undefined' && + typeof self.stylesList[uid][media] !== 'undefined' && + typeof self.stylesList[uid][media][selector] !== 'undefined' && + selector + ) { + delete self.stylesList[uid][media][selector]; + } + + self.emitEvent('removeStyle', [selector, styles, self.stylesList]); + } + + /** + * Render style for the current portfolio list + */ + renderStyle() { + const self = this; + + // timeout for the case, when styles added one by one + const { uid } = self; + let stylesString = ''; + + if (!self.stylesList) { + self.stylesList = {}; + } + + // create string with styles + if (typeof self.stylesList[uid] !== 'undefined') { + Object.keys(self.stylesList[uid]).forEach((m) => { + // media + if (m) { + stylesString += `@media ${m} {`; + } + Object.keys(self.stylesList[uid][m]).forEach((s) => { + // selector + const selectorParent = `.vp-uid-${uid}`; + let selector = `${selectorParent} ${s}`; + + // add parent selector after `,`. + selector = selector.replace( + /, |,/g, + `, ${selectorParent} ` + ); + + stylesString += `${selector} {`; + Object.keys(self.stylesList[uid][m][s]).forEach((p) => { + // property and value + stylesString += `${p}:${self.stylesList[uid][m][s][p]};`; + }); + stylesString += '}'; + }); + // media + if (m) { + stylesString += '}'; + } + }); + } + + // add in style tag + let $style = $(`#vp-style-${uid}`); + if (!$style.length) { + $style = $('`).appendTo( + 'head' + ); + } + + dynamicCSScache[styleId] = data.styles; + + $style.text(data.styles); + break; + } + // no default + } + }, +}; diff --git a/assets/vendor/conditionize/conditionize.min.js b/assets/vendor/conditionize/conditionize.min.js new file mode 100644 index 00000000..970a113c --- /dev/null +++ b/assets/vendor/conditionize/conditionize.min.js @@ -0,0 +1,7 @@ +/*! + * Name : Conditionize - jQuery conditions for forms + * Version : 1.0.5 + * Author : nK + * GitHub : https://github.com/nk-o/conditionize + */!function(n){const o={};function r(t){if(o[t])return o[t].exports;const e=o[t]={i:t,l:!1,exports:{}};return n[t].call(e.exports,e,e.exports,r),e.l=!0,e.exports}r.m=n,r.c=o,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;const n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!==typeof e)for(const o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(t){const e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=0)}([function(t,e,n){t.exports=n(1)},function(t,e,n){"use strict";n.r(e);const r=n(2),o=n(3);function u(t){return(u="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"===typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function c(e,t){let n,o=Object.keys(e);return Object.getOwnPropertySymbols&&(n=Object.getOwnPropertySymbols(e),t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),o.push.apply(o,n)),o}function i(r){for(let t=1;t=":{eval(t,e){return e<=t},sort:d},"<":{eval(t,e){return t":{eval(t,e){return e',preload:!0,css:{},attr:{scrolling:"auto"}},video:{tpl:'download and watch with your favorite video player!',format:"",autoStart:!0},defaultType:"image",animationEffect:"zoom",animationDuration:366,zoomOpacity:"auto",transitionEffect:"fade",transitionDuration:366,slideClass:"",baseClass:"",baseTpl:'',spinnerTpl:'
',errorTpl:'

{{ERROR}}

',btnTpl:{download:'',zoom:'',close:'',arrowLeft:'',arrowRight:'',smallBtn:''},parentEl:"body",hideScrollbar:!0,autoFocus:!0,backFocus:!0,trapFocus:!0,fullScreen:{autoStart:!1},touch:{vertical:!0,momentum:!0},hash:null,media:{},slideShow:{autoStart:!1,speed:3e3},thumbs:{autoStart:!1,hideOnClose:!0,parentEl:".fancybox-container",axis:"y"},wheel:"auto",onInit:n.noop,beforeLoad:n.noop,afterLoad:n.noop,beforeShow:n.noop,afterShow:n.noop,beforeClose:n.noop,afterClose:n.noop,onActivate:n.noop,onDeactivate:n.noop,clickContent:function(t,e){return"image"===t.type&&"zoom"},clickSlide:"close",clickOutside:"close",dblclickContent:!1,dblclickSlide:!1,dblclickOutside:!1,mobile:{preventCaptionOverlap:!1,idleTime:!1,clickContent:function(t,e){return"image"===t.type&&"toggleControls"},clickSlide:function(t,e){return"image"===t.type?"toggleControls":"close"},dblclickContent:function(t,e){return"image"===t.type&&"zoom"},dblclickSlide:function(t,e){return"image"===t.type&&"zoom"}},lang:"en",i18n:{en:{CLOSE:"Close",NEXT:"Next",PREV:"Previous",ERROR:"The requested content cannot be loaded.
Please try again later.",PLAY_START:"Start slideshow",PLAY_STOP:"Pause slideshow",FULL_SCREEN:"Full screen",THUMBS:"Thumbnails",DOWNLOAD:"Download",SHARE:"Share",ZOOM:"Zoom"},de:{CLOSE:"Schließen",NEXT:"Weiter",PREV:"Zurück",ERROR:"Die angeforderten Daten konnten nicht geladen werden.
Bitte versuchen Sie es später nochmal.",PLAY_START:"Diaschau starten",PLAY_STOP:"Diaschau beenden",FULL_SCREEN:"Vollbild",THUMBS:"Vorschaubilder",DOWNLOAD:"Herunterladen",SHARE:"Teilen",ZOOM:"Vergrößern"}}},s=n(t),r=n(e),c=0,l=function(t){return t&&t.hasOwnProperty&&t instanceof n},d=function(){return t.requestAnimationFrame||t.webkitRequestAnimationFrame||t.mozRequestAnimationFrame||t.oRequestAnimationFrame||function(e){return t.setTimeout(e,1e3/60)}}(),u=function(){return t.cancelAnimationFrame||t.webkitCancelAnimationFrame||t.mozCancelAnimationFrame||t.oCancelAnimationFrame||function(e){t.clearTimeout(e)}}(),f=function(){var t,n=e.createElement("fakeelement"),o={transition:"transitionend",OTransition:"oTransitionEnd",MozTransition:"transitionend",WebkitTransition:"webkitTransitionEnd"};for(t in o)if(void 0!==n.style[t])return o[t];return"transitionend"}(),p=function(t){return t&&t.length&&t[0].offsetHeight},h=function(t,e){var o=n.extend(!0,{},t,e);return n.each(e,function(t,e){n.isArray(e)&&(o[t]=e)}),o},g=function(t){var o,i;return!(!t||t.ownerDocument!==e)&&(n(".fancybox-container").css("pointer-events","none"),o={x:t.getBoundingClientRect().left+t.offsetWidth/2,y:t.getBoundingClientRect().top+t.offsetHeight/2},i=e.elementFromPoint(o.x,o.y)===t,n(".fancybox-container").css("pointer-events",""),i)},b=function(t,e,o){var i=this;i.opts=h({index:o},n.fancybox.defaults),n.isPlainObject(e)&&(i.opts=h(i.opts,e)),n.fancybox.isMobile&&(i.opts=h(i.opts,i.opts.mobile)),i.id=i.opts.id||++c,i.currIndex=parseInt(i.opts.index,10)||0,i.prevIndex=null,i.prevPos=null,i.currPos=0,i.firstRun=!0,i.group=[],i.slides={},i.addContent(t),i.group.length&&i.init()};n.extend(b.prototype,{init:function(){var o,i,a=this,s=a.group[a.currIndex],r=s.opts;r.closeExisting&&n.fancybox.close(!0),n("body").addClass("fancybox-active"),!n.fancybox.getInstance()&&!1!==r.hideScrollbar&&!n.fancybox.isMobile&&e.body.scrollHeight>t.innerHeight&&(n("head").append('"),n("body").addClass("compensate-for-scrollbar")),i="",n.each(r.buttons,function(t,e){i+=r.btnTpl[e]||""}),o=n(a.translate(a,r.baseTpl.replace("{{buttons}}",i).replace("{{arrows}}",r.btnTpl.arrowLeft+r.btnTpl.arrowRight))).attr("id","fancybox-container-"+a.id).addClass(r.baseClass).data("FancyBox",a).appendTo(r.parentEl),a.$refs={container:o},["bg","inner","infobar","toolbar","stage","caption","navigation"].forEach(function(t){a.$refs[t]=o.find(".fancybox-"+t)}),a.trigger("onInit"),a.activate(),a.jumpTo(a.currIndex)},translate:function(t,e){var n=t.opts.i18n[t.opts.lang]||t.opts.i18n.en;return e.replace(/\{\{(\w+)\}\}/g,function(t,e){return void 0===n[e]?t:n[e]})},addContent:function(t){var e,o=this,i=n.makeArray(t);n.each(i,function(t,e){var i,a,s,r,c,l={},d={};n.isPlainObject(e)?(l=e,d=e.opts||e):"object"===n.type(e)&&n(e).length?(i=n(e),d=i.data()||{},d=n.extend(!0,{},d,d.options),d.$orig=i,l.src=o.opts.src||d.src||i.attr("href"),l.type||l.src||(l.type="inline",l.src=e)):l={type:"html",src:e+""},l.opts=n.extend(!0,{},o.opts,d),n.isArray(d.buttons)&&(l.opts.buttons=d.buttons),n.fancybox.isMobile&&l.opts.mobile&&(l.opts=h(l.opts,l.opts.mobile)),a=l.type||l.opts.type,r=l.src||"",!a&&r&&((s=r.match(/\.(mp4|mov|ogv|webm)((\?|#).*)?$/i))?(a="video",l.opts.video.format||(l.opts.video.format="video/"+("ogv"===s[1]?"ogg":s[1]))):r.match(/(^data:image\/[a-z0-9+\/=]*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg|ico)((\?|#).*)?$)/i)?a="image":r.match(/\.(pdf)((\?|#).*)?$/i)?(a="iframe",l=n.extend(!0,l,{contentType:"pdf",opts:{iframe:{preload:!1}}})):"#"===r.charAt(0)&&(a="inline")),a?l.type=a:o.trigger("objectNeedsType",l),l.contentType||(l.contentType=n.inArray(l.type,["html","inline","ajax"])>-1?"html":l.type),l.index=o.group.length,"auto"==l.opts.smallBtn&&(l.opts.smallBtn=n.inArray(l.type,["html","inline","ajax"])>-1),"auto"===l.opts.toolbar&&(l.opts.toolbar=!l.opts.smallBtn),l.$thumb=l.opts.$thumb||null,l.opts.$trigger&&l.index===o.opts.index&&(l.$thumb=l.opts.$trigger.find("img:first"),l.$thumb.length&&(l.opts.$orig=l.opts.$trigger)),l.$thumb&&l.$thumb.length||!l.opts.$orig||(l.$thumb=l.opts.$orig.find("img:first")),l.$thumb&&!l.$thumb.length&&(l.$thumb=null),l.thumb=l.opts.thumb||(l.$thumb?l.$thumb[0].src:null),"function"===n.type(l.opts.caption)&&(l.opts.caption=l.opts.caption.apply(e,[o,l])),"function"===n.type(o.opts.caption)&&(l.opts.caption=o.opts.caption.apply(e,[o,l])),l.opts.caption instanceof n||(l.opts.caption=void 0===l.opts.caption?"":l.opts.caption+""),"ajax"===l.type&&(c=r.split(/\s+/,2),c.length>1&&(l.src=c.shift(),l.opts.filter=c.shift())),l.opts.modal&&(l.opts=n.extend(!0,l.opts,{trapFocus:!0,infobar:0,toolbar:0,smallBtn:0,keyboard:0,slideShow:0,fullScreen:0,thumbs:0,touch:0,clickContent:!1,clickSlide:!1,clickOutside:!1,dblclickContent:!1,dblclickSlide:!1,dblclickOutside:!1})),o.group.push(l)}),Object.keys(o.slides).length&&(o.updateControls(),(e=o.Thumbs)&&e.isActive&&(e.create(),e.focus()))},addEvents:function(){var e=this;e.removeEvents(),e.$refs.container.on("click.fb-close","[data-fancybox-close]",function(t){t.stopPropagation(),t.preventDefault(),e.close(t)}).on("touchstart.fb-prev click.fb-prev","[data-fancybox-prev]",function(t){t.stopPropagation(),t.preventDefault(),e.previous()}).on("touchstart.fb-next click.fb-next","[data-fancybox-next]",function(t){t.stopPropagation(),t.preventDefault(),e.next()}).on("click.fb","[data-fancybox-zoom]",function(t){e[e.isScaledDown()?"scaleToActual":"scaleToFit"]()}),s.on("orientationchange.fb resize.fb",function(t){t&&t.originalEvent&&"resize"===t.originalEvent.type?(e.requestId&&u(e.requestId),e.requestId=d(function(){e.update(t)})):(e.current&&"iframe"===e.current.type&&e.$refs.stage.hide(),setTimeout(function(){e.$refs.stage.show(),e.update(t)},n.fancybox.isMobile?600:250))}),r.on("keydown.fb",function(t){var o=n.fancybox?n.fancybox.getInstance():null,i=o.current,a=t.keyCode||t.which;if(9==a)return void(i.opts.trapFocus&&e.focus(t));if(!(!i.opts.keyboard||t.ctrlKey||t.altKey||t.shiftKey||n(t.target).is("input,textarea,video,audio,select")))return 8===a||27===a?(t.preventDefault(),void e.close(t)):37===a||38===a?(t.preventDefault(),void e.previous()):39===a||40===a?(t.preventDefault(),void e.next()):void e.trigger("afterKeydown",t,a)}),e.group[e.currIndex].opts.idleTime&&(e.idleSecondsCounter=0,r.on("mousemove.fb-idle mouseleave.fb-idle mousedown.fb-idle touchstart.fb-idle touchmove.fb-idle scroll.fb-idle keydown.fb-idle",function(t){e.idleSecondsCounter=0,e.isIdle&&e.showControls(),e.isIdle=!1}),e.idleInterval=t.setInterval(function(){++e.idleSecondsCounter>=e.group[e.currIndex].opts.idleTime&&!e.isDragging&&(e.isIdle=!0,e.idleSecondsCounter=0,e.hideControls())},1e3))},removeEvents:function(){var e=this;s.off("orientationchange.fb resize.fb"),r.off("keydown.fb .fb-idle"),this.$refs.container.off(".fb-close .fb-prev .fb-next"),e.idleInterval&&(t.clearInterval(e.idleInterval),e.idleInterval=null)},previous:function(t){return this.jumpTo(this.currPos-1,t)},next:function(t){return this.jumpTo(this.currPos+1,t)},jumpTo:function(t,e){var o,i,a,s,r,c,l,d,u,f=this,h=f.group.length;if(!(f.isDragging||f.isClosing||f.isAnimating&&f.firstRun)){if(t=parseInt(t,10),!(a=f.current?f.current.opts.loop:f.opts.loop)&&(t<0||t>=h))return!1;if(o=f.firstRun=!Object.keys(f.slides).length,r=f.current,f.prevIndex=f.currIndex,f.prevPos=f.currPos,s=f.createSlide(t),h>1&&((a||s.index0)&&f.createSlide(t-1)),f.current=s,f.currIndex=s.index,f.currPos=s.pos,f.trigger("beforeShow",o),f.updateControls(),s.forcedDuration=void 0,n.isNumeric(e)?s.forcedDuration=e:e=s.opts[o?"animationDuration":"transitionDuration"],e=parseInt(e,10),i=f.isMoved(s),s.$slide.addClass("fancybox-slide--current"),o)return s.opts.animationEffect&&e&&f.$refs.container.css("transition-duration",e+"ms"),f.$refs.container.addClass("fancybox-is-open").trigger("focus"),f.loadSlide(s),void f.preload("image");c=n.fancybox.getTranslate(r.$slide),l=n.fancybox.getTranslate(f.$refs.stage),n.each(f.slides,function(t,e){n.fancybox.stop(e.$slide,!0)}),r.pos!==s.pos&&(r.isComplete=!1),r.$slide.removeClass("fancybox-slide--complete fancybox-slide--current"),i?(u=c.left-(r.pos*c.width+r.pos*r.opts.gutter),n.each(f.slides,function(t,o){o.$slide.removeClass("fancybox-animated").removeClass(function(t,e){return(e.match(/(^|\s)fancybox-fx-\S+/g)||[]).join(" ")});var i=o.pos*c.width+o.pos*o.opts.gutter;n.fancybox.setTranslate(o.$slide,{top:0,left:i-l.left+u}),o.pos!==s.pos&&o.$slide.addClass("fancybox-slide--"+(o.pos>s.pos?"next":"previous")),p(o.$slide),n.fancybox.animate(o.$slide,{top:0,left:(o.pos-s.pos)*c.width+(o.pos-s.pos)*o.opts.gutter},e,function(){o.$slide.css({transform:"",opacity:""}).removeClass("fancybox-slide--next fancybox-slide--previous"),o.pos===f.currPos&&f.complete()})})):e&&s.opts.transitionEffect&&(d="fancybox-animated fancybox-fx-"+s.opts.transitionEffect,r.$slide.addClass("fancybox-slide--"+(r.pos>s.pos?"next":"previous")),n.fancybox.animate(r.$slide,d,e,function(){r.$slide.removeClass(d).removeClass("fancybox-slide--next fancybox-slide--previous")},!1)),s.isLoaded?f.revealContent(s):f.loadSlide(s),f.preload("image")}},createSlide:function(t){var e,o,i=this;return o=t%i.group.length,o=o<0?i.group.length+o:o,!i.slides[t]&&i.group[o]&&(e=n('
').appendTo(i.$refs.stage),i.slides[t]=n.extend(!0,{},i.group[o],{pos:t,$slide:e,isLoaded:!1}),i.updateSlide(i.slides[t])),i.slides[t]},scaleToActual:function(t,e,o){var i,a,s,r,c,l=this,d=l.current,u=d.$content,f=n.fancybox.getTranslate(d.$slide).width,p=n.fancybox.getTranslate(d.$slide).height,h=d.width,g=d.height;l.isAnimating||l.isMoved()||!u||"image"!=d.type||!d.isLoaded||d.hasError||(l.isAnimating=!0,n.fancybox.stop(u),t=void 0===t?.5*f:t,e=void 0===e?.5*p:e,i=n.fancybox.getTranslate(u),i.top-=n.fancybox.getTranslate(d.$slide).top,i.left-=n.fancybox.getTranslate(d.$slide).left,r=h/i.width,c=g/i.height,a=.5*f-.5*h,s=.5*p-.5*g,h>f&&(a=i.left*r-(t*r-t),a>0&&(a=0),ap&&(s=i.top*c-(e*c-e),s>0&&(s=0),se-.5&&(l=e),d>o-.5&&(d=o),"image"===t.type?(u.top=Math.floor(.5*(o-d))+parseFloat(c.css("paddingTop")),u.left=Math.floor(.5*(e-l))+parseFloat(c.css("paddingLeft"))):"video"===t.contentType&&(a=t.opts.width&&t.opts.height?l/d:t.opts.ratio||16/9,d>l/a?d=l/a:l>d*a&&(l=d*a)),u.width=l,u.height=d,u)},update:function(t){var e=this;n.each(e.slides,function(n,o){e.updateSlide(o,t)})},updateSlide:function(t,e){var o=this,i=t&&t.$content,a=t.width||t.opts.width,s=t.height||t.opts.height,r=t.$slide;o.adjustCaption(t),i&&(a||s||"video"===t.contentType)&&!t.hasError&&(n.fancybox.stop(i),n.fancybox.setTranslate(i,o.getFitPos(t)),t.pos===o.currPos&&(o.isAnimating=!1,o.updateCursor())),o.adjustLayout(t),r.length&&(r.trigger("refresh"),t.pos===o.currPos&&o.$refs.toolbar.add(o.$refs.navigation.find(".fancybox-button--arrow_right")).toggleClass("compensate-for-scrollbar",r.get(0).scrollHeight>r.get(0).clientHeight)),o.trigger("onUpdate",t,e)},centerSlide:function(t){var e=this,o=e.current,i=o.$slide;!e.isClosing&&o&&(i.siblings().css({transform:"",opacity:""}),i.parent().children().removeClass("fancybox-slide--previous fancybox-slide--next"),n.fancybox.animate(i,{top:0,left:0,opacity:1},void 0===t?0:t,function(){i.css({transform:"",opacity:""}),o.isComplete||e.complete()},!1))},isMoved:function(t){var e,o,i=t||this.current;return!!i&&(o=n.fancybox.getTranslate(this.$refs.stage),e=n.fancybox.getTranslate(i.$slide),!i.$slide.hasClass("fancybox-animated")&&(Math.abs(e.top-o.top)>.5||Math.abs(e.left-o.left)>.5))},updateCursor:function(t,e){var o,i,a=this,s=a.current,r=a.$refs.container;s&&!a.isClosing&&a.Guestures&&(r.removeClass("fancybox-is-zoomable fancybox-can-zoomIn fancybox-can-zoomOut fancybox-can-swipe fancybox-can-pan"),o=a.canPan(t,e),i=!!o||a.isZoomable(),r.toggleClass("fancybox-is-zoomable",i),n("[data-fancybox-zoom]").prop("disabled",!i),o?r.addClass("fancybox-can-pan"):i&&("zoom"===s.opts.clickContent||n.isFunction(s.opts.clickContent)&&"zoom"==s.opts.clickContent(s))?r.addClass("fancybox-can-zoomIn"):s.opts.touch&&(s.opts.touch.vertical||a.group.length>1)&&"video"!==s.contentType&&r.addClass("fancybox-can-swipe"))},isZoomable:function(){var t,e=this,n=e.current;if(n&&!e.isClosing&&"image"===n.type&&!n.hasError){if(!n.isLoaded)return!0;if((t=e.getFitPos(n))&&(n.width>t.width||n.height>t.height))return!0}return!1},isScaledDown:function(t,e){var o=this,i=!1,a=o.current,s=a.$content;return void 0!==t&&void 0!==e?i=t1.5||Math.abs(a.height-s.height)>1.5)),s},loadSlide:function(t){var e,o,i,a=this;if(!t.isLoading&&!t.isLoaded){if(t.isLoading=!0,!1===a.trigger("beforeLoad",t))return t.isLoading=!1,!1;switch(e=t.type,o=t.$slide,o.off("refresh").trigger("onReset").addClass(t.opts.slideClass),e){case"image":a.setImage(t);break;case"iframe":a.setIframe(t);break;case"html":a.setContent(t,t.src||t.content);break;case"video":a.setContent(t,t.opts.video.tpl.replace(/\{\{src\}\}/gi,t.src).replace("{{format}}",t.opts.videoFormat||t.opts.video.format||"").replace("{{poster}}",t.thumb||""));break;case"inline":n(t.src).length?a.setContent(t,n(t.src)):a.setError(t);break;case"ajax":a.showLoading(t),i=n.ajax(n.extend({},t.opts.ajax.settings,{url:t.src,success:function(e,n){"success"===n&&a.setContent(t,e)},error:function(e,n){e&&"abort"!==n&&a.setError(t)}})),o.one("onReset",function(){i.abort()});break;default:a.setError(t)}return!0}},setImage:function(t){var o,i=this;setTimeout(function(){var e=t.$image;i.isClosing||!t.isLoading||e&&e.length&&e[0].complete||t.hasError||i.showLoading(t)},50),i.checkSrcset(t),t.$content=n('
').addClass("fancybox-is-hidden").appendTo(t.$slide.addClass("fancybox-slide--image")),!1!==t.opts.preload&&t.opts.width&&t.opts.height&&t.thumb&&(t.width=t.opts.width,t.height=t.opts.height,o=e.createElement("img"),o.onerror=function(){n(this).remove(),t.$ghost=null},o.onload=function(){i.afterLoad(t)},t.$ghost=n(o).addClass("fancybox-image").appendTo(t.$content).attr("src",t.thumb)),i.setBigImage(t)},checkSrcset:function(e){var n,o,i,a,s=e.opts.srcset||e.opts.image.srcset;if(s){i=t.devicePixelRatio||1,a=t.innerWidth*i,o=s.split(",").map(function(t){var e={};return t.trim().split(/\s+/).forEach(function(t,n){var o=parseInt(t.substring(0,t.length-1),10);if(0===n)return e.url=t;o&&(e.value=o,e.postfix=t[t.length-1])}),e}),o.sort(function(t,e){return t.value-e.value});for(var r=0;r=a||"x"===c.postfix&&c.value>=i){n=c;break}}!n&&o.length&&(n=o[o.length-1]),n&&(e.src=n.url,e.width&&e.height&&"w"==n.postfix&&(e.height=e.width/e.height*n.value,e.width=n.value),e.opts.srcset=s)}},setBigImage:function(t){var o=this,i=e.createElement("img"),a=n(i);t.$image=a.one("error",function(){o.setError(t)}).one("load",function(){var e;t.$ghost||(o.resolveImageSlideSize(t,this.naturalWidth,this.naturalHeight),o.afterLoad(t)),o.isClosing||(t.opts.srcset&&(e=t.opts.sizes,e&&"auto"!==e||(e=(t.width/t.height>1&&s.width()/s.height()>1?"100":Math.round(t.width/t.height*100))+"vw"),a.attr("sizes",e).attr("srcset",t.opts.srcset)),t.$ghost&&setTimeout(function(){t.$ghost&&!o.isClosing&&t.$ghost.hide()},Math.min(300,Math.max(1e3,t.height/1600))),o.hideLoading(t))}).addClass("fancybox-image").attr("src",t.src).appendTo(t.$content),(i.complete||"complete"==i.readyState)&&a.naturalWidth&&a.naturalHeight?a.trigger("load"):i.error&&a.trigger("error")},resolveImageSlideSize:function(t,e,n){var o=parseInt(t.opts.width,10),i=parseInt(t.opts.height,10);t.width=e,t.height=n,o>0&&(t.width=o,t.height=Math.floor(o*n/e)),i>0&&(t.width=Math.floor(i*e/n),t.height=i)},setIframe:function(t){var e,o=this,i=t.opts.iframe,a=t.$slide;t.$content=n('
').css(i.css).appendTo(a),a.addClass("fancybox-slide--"+t.contentType),t.$iframe=e=n(i.tpl.replace(/\{rnd\}/g,(new Date).getTime())).attr(i.attr).appendTo(t.$content),i.preload?(o.showLoading(t),e.on("load.fb error.fb",function(e){this.isReady=1,t.$slide.trigger("refresh"),o.afterLoad(t)}),a.on("refresh.fb",function(){var n,o,s=t.$content,r=i.css.width,c=i.css.height;if(1===e[0].isReady){try{n=e.contents(),o=n.find("body")}catch(t){}o&&o.length&&o.children().length&&(a.css("overflow","visible"),s.css({width:"100%","max-width":"100%",height:"9999px"}),void 0===r&&(r=Math.ceil(Math.max(o[0].clientWidth,o.outerWidth(!0)))),s.css("width",r||"").css("max-width",""),void 0===c&&(c=Math.ceil(Math.max(o[0].clientHeight,o.outerHeight(!0)))),s.css("height",c||""),a.css("overflow","auto")),s.removeClass("fancybox-is-hidden")}})):o.afterLoad(t),e.attr("src",t.src),a.one("onReset",function(){try{n(this).find("iframe").hide().unbind().attr("src","//about:blank")}catch(t){}n(this).off("refresh.fb").empty(),t.isLoaded=!1,t.isRevealed=!1})},setContent:function(t,e){var o=this;o.isClosing||(o.hideLoading(t),t.$content&&n.fancybox.stop(t.$content),t.$slide.empty(),l(e)&&e.parent().length?((e.hasClass("fancybox-content")||e.parent().hasClass("fancybox-content"))&&e.parents(".fancybox-slide").trigger("onReset"),t.$placeholder=n("
").hide().insertAfter(e),e.css("display","inline-block")):t.hasError||("string"===n.type(e)&&(e=n("
").append(n.trim(e)).contents()),t.opts.filter&&(e=n("
").html(e).find(t.opts.filter))),t.$slide.one("onReset",function(){n(this).find("video,audio").trigger("pause"),t.$placeholder&&(t.$placeholder.after(e.removeClass("fancybox-content").hide()).remove(),t.$placeholder=null),t.$smallBtn&&(t.$smallBtn.remove(),t.$smallBtn=null),t.hasError||(n(this).empty(),t.isLoaded=!1,t.isRevealed=!1)}),n(e).appendTo(t.$slide),n(e).is("video,audio")&&(n(e).addClass("fancybox-video"),n(e).wrap("
"),t.contentType="video",t.opts.width=t.opts.width||n(e).attr("width"),t.opts.height=t.opts.height||n(e).attr("height")),t.$content=t.$slide.children().filter("div,form,main,video,audio,article,.fancybox-content").first(),t.$content.siblings().hide(),t.$content.length||(t.$content=t.$slide.wrapInner("
").children().first()),t.$content.addClass("fancybox-content"),t.$slide.addClass("fancybox-slide--"+t.contentType),o.afterLoad(t))},setError:function(t){t.hasError=!0,t.$slide.trigger("onReset").removeClass("fancybox-slide--"+t.contentType).addClass("fancybox-slide--error"),t.contentType="html",this.setContent(t,this.translate(t,t.opts.errorTpl)),t.pos===this.currPos&&(this.isAnimating=!1)},showLoading:function(t){var e=this;(t=t||e.current)&&!t.$spinner&&(t.$spinner=n(e.translate(e,e.opts.spinnerTpl)).appendTo(t.$slide).hide().fadeIn("fast"))},hideLoading:function(t){var e=this;(t=t||e.current)&&t.$spinner&&(t.$spinner.stop().remove(),delete t.$spinner)},afterLoad:function(t){var e=this;e.isClosing||(t.isLoading=!1,t.isLoaded=!0,e.trigger("afterLoad",t),e.hideLoading(t),!t.opts.smallBtn||t.$smallBtn&&t.$smallBtn.length||(t.$smallBtn=n(e.translate(t,t.opts.btnTpl.smallBtn)).appendTo(t.$content)),t.opts.protect&&t.$content&&!t.hasError&&(t.$content.on("contextmenu.fb",function(t){return 2==t.button&&t.preventDefault(),!0}),"image"===t.type&&n('
').appendTo(t.$content)),e.adjustCaption(t),e.adjustLayout(t),t.pos===e.currPos&&e.updateCursor(),e.revealContent(t))},adjustCaption:function(t){var e,n=this,o=t||n.current,i=o.opts.caption,a=o.opts.preventCaptionOverlap,s=n.$refs.caption,r=!1;s.toggleClass("fancybox-caption--separate",a),a&&i&&i.length&&(o.pos!==n.currPos?(e=s.clone().appendTo(s.parent()),e.children().eq(0).empty().html(i),r=e.outerHeight(!0),e.empty().remove()):n.$caption&&(r=n.$caption.outerHeight(!0)),o.$slide.css("padding-bottom",r||""))},adjustLayout:function(t){var e,n,o,i,a=this,s=t||a.current;s.isLoaded&&!0!==s.opts.disableLayoutFix&&(s.$content.css("margin-bottom",""),s.$content.outerHeight()>s.$slide.height()+.5&&(o=s.$slide[0].style["padding-bottom"],i=s.$slide.css("padding-bottom"),parseFloat(i)>0&&(e=s.$slide[0].scrollHeight,s.$slide.css("padding-bottom",0),Math.abs(e-s.$slide[0].scrollHeight)<1&&(n=i),s.$slide.css("padding-bottom",o))),s.$content.css("margin-bottom",n))},revealContent:function(t){var e,o,i,a,s=this,r=t.$slide,c=!1,l=!1,d=s.isMoved(t),u=t.isRevealed;return t.isRevealed=!0,e=t.opts[s.firstRun?"animationEffect":"transitionEffect"],i=t.opts[s.firstRun?"animationDuration":"transitionDuration"],i=parseInt(void 0===t.forcedDuration?i:t.forcedDuration,10),!d&&t.pos===s.currPos&&i||(e=!1),"zoom"===e&&(t.pos===s.currPos&&i&&"image"===t.type&&!t.hasError&&(l=s.getThumbPos(t))?c=s.getFitPos(t):e="fade"),"zoom"===e?(s.isAnimating=!0,c.scaleX=c.width/l.width,c.scaleY=c.height/l.height,a=t.opts.zoomOpacity,"auto"==a&&(a=Math.abs(t.width/t.height-l.width/l.height)>.1),a&&(l.opacity=.1,c.opacity=1),n.fancybox.setTranslate(t.$content.removeClass("fancybox-is-hidden"),l),p(t.$content),void n.fancybox.animate(t.$content,c,i,function(){s.isAnimating=!1,s.complete()})):(s.updateSlide(t),e?(n.fancybox.stop(r),o="fancybox-slide--"+(t.pos>=s.prevPos?"next":"previous")+" fancybox-animated fancybox-fx-"+e,r.addClass(o).removeClass("fancybox-slide--current"),t.$content.removeClass("fancybox-is-hidden"),p(r),"image"!==t.type&&t.$content.hide().show(0),void n.fancybox.animate(r,"fancybox-slide--current",i,function(){r.removeClass(o).css({transform:"",opacity:""}),t.pos===s.currPos&&s.complete()},!0)):(t.$content.removeClass("fancybox-is-hidden"),u||!d||"image"!==t.type||t.hasError||t.$content.hide().fadeIn("fast"),void(t.pos===s.currPos&&s.complete())))},getThumbPos:function(t){var e,o,i,a,s,r=!1,c=t.$thumb;return!(!c||!g(c[0]))&&(e=n.fancybox.getTranslate(c),o=parseFloat(c.css("border-top-width")||0),i=parseFloat(c.css("border-right-width")||0),a=parseFloat(c.css("border-bottom-width")||0),s=parseFloat(c.css("border-left-width")||0),r={top:e.top+o,left:e.left+s,width:e.width-i-s,height:e.height-o-a,scaleX:1,scaleY:1},e.width>0&&e.height>0&&r)},complete:function(){var t,e=this,o=e.current,i={};!e.isMoved()&&o.isLoaded&&(o.isComplete||(o.isComplete=!0,o.$slide.siblings().trigger("onReset"),e.preload("inline"),p(o.$slide),o.$slide.addClass("fancybox-slide--complete"),n.each(e.slides,function(t,o){o.pos>=e.currPos-1&&o.pos<=e.currPos+1?i[o.pos]=o:o&&(n.fancybox.stop(o.$slide),o.$slide.off().remove())}),e.slides=i),e.isAnimating=!1,e.updateCursor(),e.trigger("afterShow"),o.opts.video.autoStart&&o.$slide.find("video,audio").filter(":visible:first").trigger("play").one("ended",function(){Document.exitFullscreen?Document.exitFullscreen():this.webkitExitFullscreen&&this.webkitExitFullscreen(),e.next()}),o.opts.autoFocus&&"html"===o.contentType&&(t=o.$content.find("input[autofocus]:enabled:visible:first"),t.length?t.trigger("focus"):e.focus(null,!0)),o.$slide.scrollTop(0).scrollLeft(0))},preload:function(t){var e,n,o=this;o.group.length<2||(n=o.slides[o.currPos+1],e=o.slides[o.currPos-1],e&&e.type===t&&o.loadSlide(e),n&&n.type===t&&o.loadSlide(n))},focus:function(t,o){var i,a,s=this,r=["a[href]","area[href]",'input:not([disabled]):not([type="hidden"]):not([aria-hidden])',"select:not([disabled]):not([aria-hidden])","textarea:not([disabled]):not([aria-hidden])","button:not([disabled]):not([aria-hidden])","iframe","object","embed","video","audio","[contenteditable]",'[tabindex]:not([tabindex^="-"])'].join(",");s.isClosing||(i=!t&&s.current&&s.current.isComplete?s.current.$slide.find("*:visible"+(o?":not(.fancybox-close-small)":"")):s.$refs.container.find("*:visible"),i=i.filter(r).filter(function(){return"hidden"!==n(this).css("visibility")&&!n(this).hasClass("disabled")}),i.length?(a=i.index(e.activeElement),t&&t.shiftKey?(a<0||0==a)&&(t.preventDefault(),i.eq(i.length-1).trigger("focus")):(a<0||a==i.length-1)&&(t&&t.preventDefault(),i.eq(0).trigger("focus"))):s.$refs.container.trigger("focus"))},activate:function(){var t=this;n(".fancybox-container").each(function(){var e=n(this).data("FancyBox");e&&e.id!==t.id&&!e.isClosing&&(e.trigger("onDeactivate"),e.removeEvents(),e.isVisible=!1)}),t.isVisible=!0,(t.current||t.isIdle)&&(t.update(),t.updateControls()),t.trigger("onActivate"),t.addEvents()},close:function(t,e){var o,i,a,s,r,c,l,u=this,f=u.current,h=function(){u.cleanUp(t)};return!u.isClosing&&(u.isClosing=!0,!1===u.trigger("beforeClose",t)?(u.isClosing=!1,d(function(){u.update()}),!1):(u.removeEvents(),a=f.$content,o=f.opts.animationEffect,i=n.isNumeric(e)?e:o?f.opts.animationDuration:0,f.$slide.removeClass("fancybox-slide--complete fancybox-slide--next fancybox-slide--previous fancybox-animated"),!0!==t?n.fancybox.stop(f.$slide):o=!1,f.$slide.siblings().trigger("onReset").remove(),i&&u.$refs.container.removeClass("fancybox-is-open").addClass("fancybox-is-closing").css("transition-duration",i+"ms"),u.hideLoading(f),u.hideControls(!0),u.updateCursor(),"zoom"!==o||a&&i&&"image"===f.type&&!u.isMoved()&&!f.hasError&&(l=u.getThumbPos(f))||(o="fade"),"zoom"===o?(n.fancybox.stop(a),s=n.fancybox.getTranslate(a),c={top:s.top,left:s.left,scaleX:s.width/l.width,scaleY:s.height/l.height,width:l.width,height:l.height},r=f.opts.zoomOpacity, +"auto"==r&&(r=Math.abs(f.width/f.height-l.width/l.height)>.1),r&&(l.opacity=0),n.fancybox.setTranslate(a,c),p(a),n.fancybox.animate(a,l,i,h),!0):(o&&i?n.fancybox.animate(f.$slide.addClass("fancybox-slide--previous").removeClass("fancybox-slide--current"),"fancybox-animated fancybox-fx-"+o,i,h):!0===t?setTimeout(h,i):h(),!0)))},cleanUp:function(e){var o,i,a,s=this,r=s.current.opts.$orig;s.current.$slide.trigger("onReset"),s.$refs.container.empty().remove(),s.trigger("afterClose",e),s.current.opts.backFocus&&(r&&r.length&&r.is(":visible")||(r=s.$trigger),r&&r.length&&(i=t.scrollX,a=t.scrollY,r.trigger("focus"),n("html, body").scrollTop(a).scrollLeft(i))),s.current=null,o=n.fancybox.getInstance(),o?o.activate():(n("body").removeClass("fancybox-active compensate-for-scrollbar"),n("#fancybox-style-noscroll").remove())},trigger:function(t,e){var o,i=Array.prototype.slice.call(arguments,1),a=this,s=e&&e.opts?e:a.current;if(s?i.unshift(s):s=a,i.unshift(a),n.isFunction(s.opts[t])&&(o=s.opts[t].apply(s,i)),!1===o)return o;"afterClose"!==t&&a.$refs?a.$refs.container.trigger(t+".fb",i):r.trigger(t+".fb",i)},updateControls:function(){var t=this,o=t.current,i=o.index,a=t.$refs.container,s=t.$refs.caption,r=o.opts.caption;o.$slide.trigger("refresh"),r&&r.length?(t.$caption=s,s.children().eq(0).html(r)):t.$caption=null,t.hasHiddenControls||t.isIdle||t.showControls(),a.find("[data-fancybox-count]").html(t.group.length),a.find("[data-fancybox-index]").html(i+1),a.find("[data-fancybox-prev]").prop("disabled",!o.opts.loop&&i<=0),a.find("[data-fancybox-next]").prop("disabled",!o.opts.loop&&i>=t.group.length-1),"image"===o.type?a.find("[data-fancybox-zoom]").show().end().find("[data-fancybox-download]").attr("href",o.opts.image.src||o.src).show():o.opts.toolbar&&a.find("[data-fancybox-download],[data-fancybox-zoom]").hide(),n(e.activeElement).is(":hidden,[disabled]")&&t.$refs.container.trigger("focus")},hideControls:function(t){var e=this,n=["infobar","toolbar","nav"];!t&&e.current.opts.preventCaptionOverlap||n.push("caption"),this.$refs.container.removeClass(n.map(function(t){return"fancybox-show-"+t}).join(" ")),this.hasHiddenControls=!0},showControls:function(){var t=this,e=t.current?t.current.opts:t.opts,n=t.$refs.container;t.hasHiddenControls=!1,t.idleSecondsCounter=0,n.toggleClass("fancybox-show-toolbar",!(!e.toolbar||!e.buttons)).toggleClass("fancybox-show-infobar",!!(e.infobar&&t.group.length>1)).toggleClass("fancybox-show-caption",!!t.$caption).toggleClass("fancybox-show-nav",!!(e.arrows&&t.group.length>1)).toggleClass("fancybox-is-modal",!!e.modal)},toggleControls:function(){this.hasHiddenControls?this.showControls():this.hideControls()}}),n.fancybox={version:"3.5.7",defaults:a,getInstance:function(t){var e=n('.fancybox-container:not(".fancybox-is-closing"):last').data("FancyBox"),o=Array.prototype.slice.call(arguments,1);return e instanceof b&&("string"===n.type(t)?e[t].apply(e,o):"function"===n.type(t)&&t.apply(e,o),e)},open:function(t,e,n){return new b(t,e,n)},close:function(t){var e=this.getInstance();e&&(e.close(),!0===t&&this.close(t))},destroy:function(){this.close(!0),r.add("body").off("click.fb-start","**")},isMobile:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),use3d:function(){var n=e.createElement("div");return t.getComputedStyle&&t.getComputedStyle(n)&&t.getComputedStyle(n).getPropertyValue("transform")&&!(e.documentMode&&e.documentMode<11)}(),getTranslate:function(t){var e;return!(!t||!t.length)&&(e=t[0].getBoundingClientRect(),{top:e.top||0,left:e.left||0,width:e.width,height:e.height,opacity:parseFloat(t.css("opacity"))})},setTranslate:function(t,e){var n="",o={};if(t&&e)return void 0===e.left&&void 0===e.top||(n=(void 0===e.left?t.position().left:e.left)+"px, "+(void 0===e.top?t.position().top:e.top)+"px",n=this.use3d?"translate3d("+n+", 0px)":"translate("+n+")"),void 0!==e.scaleX&&void 0!==e.scaleY?n+=" scale("+e.scaleX+", "+e.scaleY+")":void 0!==e.scaleX&&(n+=" scaleX("+e.scaleX+")"),n.length&&(o.transform=n),void 0!==e.opacity&&(o.opacity=e.opacity),void 0!==e.width&&(o.width=e.width),void 0!==e.height&&(o.height=e.height),t.css(o)},animate:function(t,e,o,i,a){var s,r=this;n.isFunction(o)&&(i=o,o=null),r.stop(t),s=r.getTranslate(t),t.on(f,function(c){(!c||!c.originalEvent||t.is(c.originalEvent.target)&&"z-index"!=c.originalEvent.propertyName)&&(r.stop(t),n.isNumeric(o)&&t.css("transition-duration",""),n.isPlainObject(e)?void 0!==e.scaleX&&void 0!==e.scaleY&&r.setTranslate(t,{top:e.top,left:e.left,width:s.width*e.scaleX,height:s.height*e.scaleY,scaleX:1,scaleY:1}):!0!==a&&t.removeClass(e),n.isFunction(i)&&i(c))}),n.isNumeric(o)&&t.css("transition-duration",o+"ms"),n.isPlainObject(e)?(void 0!==e.scaleX&&void 0!==e.scaleY&&(delete e.width,delete e.height,t.parent().hasClass("fancybox-slide--image")&&t.parent().addClass("fancybox-is-scaling")),n.fancybox.setTranslate(t,e)):t.addClass(e),t.data("timer",setTimeout(function(){t.trigger(f)},o+33))},stop:function(t,e){t&&t.length&&(clearTimeout(t.data("timer")),e&&t.trigger(f),t.off(f).css("transition-duration",""),t.parent().removeClass("fancybox-is-scaling"))}},n.fn.fancybox=function(t){var e;return t=t||{},e=t.selector||!1,e?n("body").off("click.fb-start",e).on("click.fb-start",e,{options:t},i):this.off("click.fb-start").on("click.fb-start",{items:this,options:t},i),this},r.on("click.fb-start","[data-fancybox]",i),r.on("click.fb-start","[data-fancybox-trigger]",function(t){n('[data-fancybox="'+n(this).attr("data-fancybox-trigger")+'"]').eq(n(this).attr("data-fancybox-index")||0).trigger("click.fb-start",{$trigger:n(this)})}),function(){var t=null;r.on("mousedown mouseup focus blur",".fancybox-button",function(e){switch(e.type){case"mousedown":t=n(this);break;case"mouseup":t=null;break;case"focusin":n(".fancybox-button").removeClass("fancybox-focus"),n(this).is(t)||n(this).is("[disabled]")||n(this).addClass("fancybox-focus");break;case"focusout":n(".fancybox-button").removeClass("fancybox-focus")}})}()}}(window,document,jQuery),function(t){"use strict";var e={youtube:{matcher:/(youtube\.com|youtu\.be|youtube\-nocookie\.com)\/(watch\?(.*&)?v=|v\/|u\/|embed\/?)?(videoseries\?list=(.*)|[\w-]{11}|\?listType=(.*)&list=(.*))(.*)/i,params:{autoplay:1,autohide:1,fs:1,rel:0,hd:1,wmode:"transparent",enablejsapi:1,html5:1},paramPlace:8,type:"iframe",url:"https://www.youtube-nocookie.com/embed/$4",thumb:"https://img.youtube.com/vi/$4/hqdefault.jpg"},vimeo:{matcher:/^.+vimeo.com\/(.*\/)?([\d]+)(.*)?/,params:{autoplay:1,hd:1,show_title:1,show_byline:1,show_portrait:0,fullscreen:1},paramPlace:3,type:"iframe",url:"//player.vimeo.com/video/$2"},instagram:{matcher:/(instagr\.am|instagram\.com)\/p\/([a-zA-Z0-9_\-]+)\/?/i,type:"image",url:"//$1/p/$2/media/?size=l"},gmap_place:{matcher:/(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(((maps\/(place\/(.*)\/)?\@(.*),(\d+.?\d+?)z))|(\?ll=))(.*)?/i,type:"iframe",url:function(t){return"//maps.google."+t[2]+"/?ll="+(t[9]?t[9]+"&z="+Math.floor(t[10])+(t[12]?t[12].replace(/^\//,"&"):""):t[12]+"").replace(/\?/,"&")+"&output="+(t[12]&&t[12].indexOf("layer=c")>0?"svembed":"embed")}},gmap_search:{matcher:/(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(maps\/search\/)(.*)/i,type:"iframe",url:function(t){return"//maps.google."+t[2]+"/maps?q="+t[5].replace("query=","q=").replace("api=1","")+"&output=embed"}}},n=function(e,n,o){if(e)return o=o||"","object"===t.type(o)&&(o=t.param(o,!0)),t.each(n,function(t,n){e=e.replace("$"+t,n||"")}),o.length&&(e+=(e.indexOf("?")>0?"&":"?")+o),e};t(document).on("objectNeedsType.fb",function(o,i,a){var s,r,c,l,d,u,f,p=a.src||"",h=!1;s=t.extend(!0,{},e,a.opts.media),t.each(s,function(e,o){if(c=p.match(o.matcher)){if(h=o.type,f=e,u={},o.paramPlace&&c[o.paramPlace]){d=c[o.paramPlace],"?"==d[0]&&(d=d.substring(1)),d=d.split("&");for(var i=0;i1&&("youtube"===n.contentSource||"vimeo"===n.contentSource)&&o.load(n.contentSource)}})}(jQuery),function(t,e,n){"use strict";var o=function(){return t.requestAnimationFrame||t.webkitRequestAnimationFrame||t.mozRequestAnimationFrame||t.oRequestAnimationFrame||function(e){return t.setTimeout(e,1e3/60)}}(),i=function(){return t.cancelAnimationFrame||t.webkitCancelAnimationFrame||t.mozCancelAnimationFrame||t.oCancelAnimationFrame||function(e){t.clearTimeout(e)}}(),a=function(e){var n=[];e=e.originalEvent||e||t.e,e=e.touches&&e.touches.length?e.touches:e.changedTouches&&e.changedTouches.length?e.changedTouches:[e];for(var o in e)e[o].pageX?n.push({x:e[o].pageX,y:e[o].pageY}):e[o].clientX&&n.push({x:e[o].clientX,y:e[o].clientY});return n},s=function(t,e,n){return e&&t?"x"===n?t.x-e.x:"y"===n?t.y-e.y:Math.sqrt(Math.pow(t.x-e.x,2)+Math.pow(t.y-e.y,2)):0},r=function(t){if(t.is('a,area,button,[role="button"],input,label,select,summary,textarea,video,audio,iframe')||n.isFunction(t.get(0).onclick)||t.data("selectable"))return!0;for(var e=0,o=t[0].attributes,i=o.length;ee.clientHeight,a=("scroll"===o||"auto"===o)&&e.scrollWidth>e.clientWidth;return i||a},l=function(t){for(var e=!1;;){if(e=c(t.get(0)))break;if(t=t.parent(),!t.length||t.hasClass("fancybox-stage")||t.is("body"))break}return e},d=function(t){var e=this;e.instance=t,e.$bg=t.$refs.bg,e.$stage=t.$refs.stage,e.$container=t.$refs.container,e.destroy(),e.$container.on("touchstart.fb.touch mousedown.fb.touch",n.proxy(e,"ontouchstart"))};d.prototype.destroy=function(){var t=this;t.$container.off(".fb.touch"),n(e).off(".fb.touch"),t.requestId&&(i(t.requestId),t.requestId=null),t.tapped&&(clearTimeout(t.tapped),t.tapped=null)},d.prototype.ontouchstart=function(o){var i=this,c=n(o.target),d=i.instance,u=d.current,f=u.$slide,p=u.$content,h="touchstart"==o.type;if(h&&i.$container.off("mousedown.fb.touch"),(!o.originalEvent||2!=o.originalEvent.button)&&f.length&&c.length&&!r(c)&&!r(c.parent())&&(c.is("img")||!(o.originalEvent.clientX>c[0].clientWidth+c.offset().left))){if(!u||d.isAnimating||u.$slide.hasClass("fancybox-animated"))return o.stopPropagation(),void o.preventDefault();i.realPoints=i.startPoints=a(o),i.startPoints.length&&(u.touch&&o.stopPropagation(),i.startEvent=o,i.canTap=!0,i.$target=c,i.$content=p,i.opts=u.opts.touch,i.isPanning=!1,i.isSwiping=!1,i.isZooming=!1,i.isScrolling=!1,i.canPan=d.canPan(),i.startTime=(new Date).getTime(),i.distanceX=i.distanceY=i.distance=0,i.canvasWidth=Math.round(f[0].clientWidth),i.canvasHeight=Math.round(f[0].clientHeight),i.contentLastPos=null,i.contentStartPos=n.fancybox.getTranslate(i.$content)||{top:0,left:0},i.sliderStartPos=n.fancybox.getTranslate(f),i.stagePos=n.fancybox.getTranslate(d.$refs.stage),i.sliderStartPos.top-=i.stagePos.top,i.sliderStartPos.left-=i.stagePos.left,i.contentStartPos.top-=i.stagePos.top,i.contentStartPos.left-=i.stagePos.left,n(e).off(".fb.touch").on(h?"touchend.fb.touch touchcancel.fb.touch":"mouseup.fb.touch mouseleave.fb.touch",n.proxy(i,"ontouchend")).on(h?"touchmove.fb.touch":"mousemove.fb.touch",n.proxy(i,"ontouchmove")),n.fancybox.isMobile&&e.addEventListener("scroll",i.onscroll,!0),((i.opts||i.canPan)&&(c.is(i.$stage)||i.$stage.find(c).length)||(c.is(".fancybox-image")&&o.preventDefault(),n.fancybox.isMobile&&c.parents(".fancybox-caption").length))&&(i.isScrollable=l(c)||l(c.parent()),n.fancybox.isMobile&&i.isScrollable||o.preventDefault(),(1===i.startPoints.length||u.hasError)&&(i.canPan?(n.fancybox.stop(i.$content),i.isPanning=!0):i.isSwiping=!0,i.$container.addClass("fancybox-is-grabbing")),2===i.startPoints.length&&"image"===u.type&&(u.isLoaded||u.$ghost)&&(i.canTap=!1,i.isSwiping=!1,i.isPanning=!1,i.isZooming=!0,n.fancybox.stop(i.$content),i.centerPointStartX=.5*(i.startPoints[0].x+i.startPoints[1].x)-n(t).scrollLeft(),i.centerPointStartY=.5*(i.startPoints[0].y+i.startPoints[1].y)-n(t).scrollTop(),i.percentageOfImageAtPinchPointX=(i.centerPointStartX-i.contentStartPos.left)/i.contentStartPos.width,i.percentageOfImageAtPinchPointY=(i.centerPointStartY-i.contentStartPos.top)/i.contentStartPos.height,i.startDistanceBetweenFingers=s(i.startPoints[0],i.startPoints[1]))))}},d.prototype.onscroll=function(t){var n=this;n.isScrolling=!0,e.removeEventListener("scroll",n.onscroll,!0)},d.prototype.ontouchmove=function(t){var e=this;return void 0!==t.originalEvent.buttons&&0===t.originalEvent.buttons?void e.ontouchend(t):e.isScrolling?void(e.canTap=!1):(e.newPoints=a(t),void((e.opts||e.canPan)&&e.newPoints.length&&e.newPoints.length&&(e.isSwiping&&!0===e.isSwiping||t.preventDefault(),e.distanceX=s(e.newPoints[0],e.startPoints[0],"x"),e.distanceY=s(e.newPoints[0],e.startPoints[0],"y"),e.distance=s(e.newPoints[0],e.startPoints[0]),e.distance>0&&(e.isSwiping?e.onSwipe(t):e.isPanning?e.onPan():e.isZooming&&e.onZoom()))))},d.prototype.onSwipe=function(e){var a,s=this,r=s.instance,c=s.isSwiping,l=s.sliderStartPos.left||0;if(!0!==c)"x"==c&&(s.distanceX>0&&(s.instance.group.length<2||0===s.instance.current.index&&!s.instance.current.opts.loop)?l+=Math.pow(s.distanceX,.8):s.distanceX<0&&(s.instance.group.length<2||s.instance.current.index===s.instance.group.length-1&&!s.instance.current.opts.loop)?l-=Math.pow(-s.distanceX,.8):l+=s.distanceX),s.sliderLastPos={top:"x"==c?0:s.sliderStartPos.top+s.distanceY,left:l},s.requestId&&(i(s.requestId),s.requestId=null),s.requestId=o(function(){s.sliderLastPos&&(n.each(s.instance.slides,function(t,e){var o=e.pos-s.instance.currPos;n.fancybox.setTranslate(e.$slide,{top:s.sliderLastPos.top,left:s.sliderLastPos.left+o*s.canvasWidth+o*e.opts.gutter})}),s.$container.addClass("fancybox-is-sliding"))});else if(Math.abs(s.distance)>10){if(s.canTap=!1,r.group.length<2&&s.opts.vertical?s.isSwiping="y":r.isDragging||!1===s.opts.vertical||"auto"===s.opts.vertical&&n(t).width()>800?s.isSwiping="x":(a=Math.abs(180*Math.atan2(s.distanceY,s.distanceX)/Math.PI),s.isSwiping=a>45&&a<135?"y":"x"),"y"===s.isSwiping&&n.fancybox.isMobile&&s.isScrollable)return void(s.isScrolling=!0);r.isDragging=s.isSwiping,s.startPoints=s.newPoints,n.each(r.slides,function(t,e){var o,i;n.fancybox.stop(e.$slide),o=n.fancybox.getTranslate(e.$slide),i=n.fancybox.getTranslate(r.$refs.stage),e.$slide.css({transform:"",opacity:"","transition-duration":""}).removeClass("fancybox-animated").removeClass(function(t,e){return(e.match(/(^|\s)fancybox-fx-\S+/g)||[]).join(" ")}),e.pos===r.current.pos&&(s.sliderStartPos.top=o.top-i.top,s.sliderStartPos.left=o.left-i.left),n.fancybox.setTranslate(e.$slide,{top:o.top-i.top,left:o.left-i.left})}),r.SlideShow&&r.SlideShow.isActive&&r.SlideShow.stop()}},d.prototype.onPan=function(){var t=this;if(s(t.newPoints[0],t.realPoints[0])<(n.fancybox.isMobile?10:5))return void(t.startPoints=t.newPoints);t.canTap=!1,t.contentLastPos=t.limitMovement(),t.requestId&&i(t.requestId),t.requestId=o(function(){n.fancybox.setTranslate(t.$content,t.contentLastPos)})},d.prototype.limitMovement=function(){var t,e,n,o,i,a,s=this,r=s.canvasWidth,c=s.canvasHeight,l=s.distanceX,d=s.distanceY,u=s.contentStartPos,f=u.left,p=u.top,h=u.width,g=u.height;return i=h>r?f+l:f,a=p+d,t=Math.max(0,.5*r-.5*h),e=Math.max(0,.5*c-.5*g),n=Math.min(r-h,.5*r-.5*h),o=Math.min(c-g,.5*c-.5*g),l>0&&i>t&&(i=t-1+Math.pow(-t+f+l,.8)||0),l<0&&i0&&a>e&&(a=e-1+Math.pow(-e+p+d,.8)||0),d<0&&aa?(t=t>0?0:t,t=ts?(e=e>0?0:e,e=e1&&(o.dMs>130&&s>10||s>50);o.sliderLastPos=null,"y"==t&&!e&&Math.abs(o.distanceY)>50?(n.fancybox.animate(o.instance.current.$slide,{top:o.sliderStartPos.top+o.distanceY+150*o.velocityY,opacity:0},200),i=o.instance.close(!0,250)):r&&o.distanceX>0?i=o.instance.previous(300):r&&o.distanceX<0&&(i=o.instance.next(300)),!1!==i||"x"!=t&&"y"!=t||o.instance.centerSlide(200),o.$container.removeClass("fancybox-is-sliding")},d.prototype.endPanning=function(){var t,e,o,i=this;i.contentLastPos&&(!1===i.opts.momentum||i.dMs>350?(t=i.contentLastPos.left,e=i.contentLastPos.top):(t=i.contentLastPos.left+500*i.velocityX,e=i.contentLastPos.top+500*i.velocityY),o=i.limitPosition(t,e,i.contentStartPos.width,i.contentStartPos.height),o.width=i.contentStartPos.width,o.height=i.contentStartPos.height,n.fancybox.animate(i.$content,o,366))},d.prototype.endZooming=function(){var t,e,o,i,a=this,s=a.instance.current,r=a.newWidth,c=a.newHeight;a.contentLastPos&&(t=a.contentLastPos.left,e=a.contentLastPos.top,i={top:e,left:t,width:r,height:c,scaleX:1,scaleY:1},n.fancybox.setTranslate(a.$content,i),rs.width||c>s.height?a.instance.scaleToActual(a.centerPointStartX,a.centerPointStartY,150):(o=a.limitPosition(t,e,r,c),n.fancybox.animate(a.$content,o,150)))},d.prototype.onTap=function(e){var o,i=this,s=n(e.target),r=i.instance,c=r.current,l=e&&a(e)||i.startPoints,d=l[0]?l[0].x-n(t).scrollLeft()-i.stagePos.left:0,u=l[0]?l[0].y-n(t).scrollTop()-i.stagePos.top:0,f=function(t){var o=c.opts[t];if(n.isFunction(o)&&(o=o.apply(r,[c,e])),o)switch(o){case"close":r.close(i.startEvent);break;case"toggleControls":r.toggleControls();break;case"next":r.next();break;case"nextOrClose":r.group.length>1?r.next():r.close(i.startEvent);break;case"zoom":"image"==c.type&&(c.isLoaded||c.$ghost)&&(r.canPan()?r.scaleToFit():r.isScaledDown()?r.scaleToActual(d,u):r.group.length<2&&r.close(i.startEvent))}};if((!e.originalEvent||2!=e.originalEvent.button)&&(s.is("img")||!(d>s[0].clientWidth+s.offset().left))){if(s.is(".fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-container"))o="Outside";else if(s.is(".fancybox-slide"))o="Slide";else{if(!r.current.$content||!r.current.$content.find(s).addBack().filter(s).length)return;o="Content"}if(i.tapped){if(clearTimeout(i.tapped),i.tapped=null,Math.abs(d-i.tapX)>50||Math.abs(u-i.tapY)>50)return this;f("dblclick"+o)}else i.tapX=d,i.tapY=u,c.opts["dblclick"+o]&&c.opts["dblclick"+o]!==c.opts["click"+o]?i.tapped=setTimeout(function(){i.tapped=null,r.isAnimating||f("click"+o)},500):f("click"+o);return this}},n(e).on("onActivate.fb",function(t,e){e&&!e.Guestures&&(e.Guestures=new d(e))}).on("beforeClose.fb",function(t,e){e&&e.Guestures&&e.Guestures.destroy()})}(window,document,jQuery),function(t,e){"use strict";e.extend(!0,e.fancybox.defaults,{btnTpl:{slideShow:''},slideShow:{autoStart:!1,speed:3e3,progress:!0}});var n=function(t){this.instance=t,this.init()};e.extend(n.prototype,{timer:null,isActive:!1,$button:null,init:function(){var t=this,n=t.instance,o=n.group[n.currIndex].opts.slideShow;t.$button=n.$refs.toolbar.find("[data-fancybox-play]").on("click",function(){t.toggle()}),n.group.length<2||!o?t.$button.hide():o.progress&&(t.$progress=e('
').appendTo(n.$refs.inner))},set:function(t){var n=this,o=n.instance,i=o.current;i&&(!0===t||i.opts.loop||o.currIndex'},fullScreen:{autoStart:!1}}),e(t).on(n.fullscreenchange,function(){var t=o.isFullscreen(),n=e.fancybox.getInstance();n&&(n.current&&"image"===n.current.type&&n.isAnimating&&(n.isAnimating=!1,n.update(!0,!0,0),n.isComplete||n.complete()),n.trigger("onFullscreenChange",t),n.$refs.container.toggleClass("fancybox-is-fullscreen",t),n.$refs.toolbar.find("[data-fancybox-fullscreen]").toggleClass("fancybox-button--fsenter",!t).toggleClass("fancybox-button--fsexit",t))})}e(t).on({"onInit.fb":function(t,e){var i;if(!n)return void e.$refs.toolbar.find("[data-fancybox-fullscreen]").remove();e&&e.group[e.currIndex].opts.fullScreen?(i=e.$refs.container,i.on("click.fb-fullscreen","[data-fancybox-fullscreen]",function(t){t.stopPropagation(),t.preventDefault(),o.toggle()}),e.opts.fullScreen&&!0===e.opts.fullScreen.autoStart&&o.request(),e.FullScreen=o):e&&e.$refs.toolbar.find("[data-fancybox-fullscreen]").hide()},"afterKeydown.fb":function(t,e,n,o,i){e&&e.FullScreen&&70===i&&(o.preventDefault(),e.FullScreen.toggle())},"beforeClose.fb":function(t,e){e&&e.FullScreen&&e.$refs.container.hasClass("fancybox-is-fullscreen")&&o.exit()}})}(document,jQuery),function(t,e){"use strict";var n="fancybox-thumbs";e.fancybox.defaults=e.extend(!0,{btnTpl:{thumbs:''},thumbs:{autoStart:!1,hideOnClose:!0,parentEl:".fancybox-container",axis:"y"}},e.fancybox.defaults);var o=function(t){this.init(t)};e.extend(o.prototype,{$button:null,$grid:null,$list:null,isVisible:!1,isActive:!1,init:function(t){var e=this,n=t.group,o=0;e.instance=t,e.opts=n[t.currIndex].opts.thumbs,t.Thumbs=e,e.$button=t.$refs.toolbar.find("[data-fancybox-thumbs]");for(var i=0,a=n.length;i1));i++);o>1&&e.opts?(e.$button.removeAttr("style").on("click",function(){e.toggle()}),e.isActive=!0):e.$button.hide()},create:function(){var t,o=this,i=o.instance,a=o.opts.parentEl,s=[];o.$grid||(o.$grid=e('
').appendTo(i.$refs.container.find(a).addBack().filter(a)),o.$grid.on("click","a",function(){i.jumpTo(e(this).attr("data-index"))})),o.$list||(o.$list=e('
').appendTo(o.$grid)),e.each(i.group,function(e,n){t=n.thumb,t||"image"!==n.type||(t=n.src),s.push('")}),o.$list[0].innerHTML=s.join(""),"x"===o.opts.axis&&o.$list.width(parseInt(o.$grid.css("padding-right"),10)+i.group.length*o.$list.children().eq(0).outerWidth(!0))},focus:function(t){var e,n,o=this,i=o.$list,a=o.$grid;o.instance.current&&(e=i.children().removeClass("fancybox-thumbs-active").filter('[data-index="'+o.instance.current.index+'"]').addClass("fancybox-thumbs-active"),n=e.position(),"y"===o.opts.axis&&(n.top<0||n.top>i.height()-e.outerHeight())?i.stop().animate({scrollTop:i.scrollTop()+n.top},t):"x"===o.opts.axis&&(n.lefta.scrollLeft()+(a.width()-e.outerWidth()))&&i.parent().stop().animate({scrollLeft:n.left},t))},update:function(){var t=this;t.instance.$refs.container.toggleClass("fancybox-show-thumbs",this.isVisible),t.isVisible?(t.$grid||t.create(),t.instance.trigger("onThumbsShow"),t.focus(0)):t.$grid&&t.instance.trigger("onThumbsHide"),t.instance.update()},hide:function(){this.isVisible=!1,this.update()},show:function(){this.isVisible=!0,this.update()},toggle:function(){this.isVisible=!this.isVisible,this.update()}}),e(t).on({"onInit.fb":function(t,e){var n;e&&!e.Thumbs&&(n=new o(e),n.isActive&&!0===n.opts.autoStart&&n.show())},"beforeShow.fb":function(t,e,n,o){var i=e&&e.Thumbs;i&&i.isVisible&&i.focus(o?0:250)},"afterKeydown.fb":function(t,e,n,o,i){var a=e&&e.Thumbs;a&&a.isActive&&71===i&&(o.preventDefault(),a.toggle())},"beforeClose.fb":function(t,e){var n=e&&e.Thumbs;n&&n.isVisible&&!1!==n.opts.hideOnClose&&n.$grid.hide()}})}(document,jQuery),function(t,e){"use strict";function n(t){var e={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="};return String(t).replace(/[&<>"'`=\/]/g,function(t){return e[t]})}e.extend(!0,e.fancybox.defaults,{btnTpl:{share:''},share:{url:function(t,e){return!t.currentHash&&"inline"!==e.type&&"html"!==e.type&&(e.origSrc||e.src)||window.location}, +tpl:''}}),e(t).on("click","[data-fancybox-share]",function(){var t,o,i=e.fancybox.getInstance(),a=i.current||null;a&&("function"===e.type(a.opts.share.url)&&(t=a.opts.share.url.apply(a,[i,a])),o=a.opts.share.tpl.replace(/\{\{media\}\}/g,"image"===a.type?encodeURIComponent(a.src):"").replace(/\{\{url\}\}/g,encodeURIComponent(t)).replace(/\{\{url_raw\}\}/g,n(t)).replace(/\{\{descr\}\}/g,i.$caption?encodeURIComponent(i.$caption.text()):""),e.fancybox.open({src:i.translate(i,o),type:"html",opts:{touch:!1,animationEffect:!1,afterLoad:function(t,e){i.$refs.container.one("beforeClose.fb",function(){t.close(null,0)}),e.$content.find(".fancybox-share__button").click(function(){return window.open(this.href,"Share","width=550, height=450"),!1})},mobile:{autoFocus:!1}}}))})}(document,jQuery),function(t,e,n){"use strict";function o(){var e=t.location.hash.substr(1),n=e.split("-"),o=n.length>1&&/^\+?\d+$/.test(n[n.length-1])?parseInt(n.pop(-1),10)||1:1,i=n.join("-");return{hash:e,index:o<1?1:o,gallery:i}}function i(t){""!==t.gallery&&n("[data-fancybox='"+n.escapeSelector(t.gallery)+"']").eq(t.index-1).focus().trigger("click.fb-start")}function a(t){var e,n;return!!t&&(e=t.current?t.current.opts:t.opts,""!==(n=e.hash||(e.$orig?e.$orig.data("fancybox")||e.$orig.data("fancybox-trigger"):""))&&n)}n.escapeSelector||(n.escapeSelector=function(t){return(t+"").replace(/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g,function(t,e){return e?"\0"===t?"�":t.slice(0,-1)+"\\"+t.charCodeAt(t.length-1).toString(16)+" ":"\\"+t})}),n(function(){!1!==n.fancybox.defaults.hash&&(n(e).on({"onInit.fb":function(t,e){var n,i;!1!==e.group[e.currIndex].opts.hash&&(n=o(),(i=a(e))&&n.gallery&&i==n.gallery&&(e.currIndex=n.index-1))},"beforeShow.fb":function(n,o,i,s){var r;i&&!1!==i.opts.hash&&(r=a(o))&&(o.currentHash=r+(o.group.length>1?"-"+(i.index+1):""),t.location.hash!=="#"+o.currentHash&&(s&&!o.origHash&&(o.origHash=t.location.hash),o.hashTimer&&clearTimeout(o.hashTimer),o.hashTimer=setTimeout(function(){"replaceState"in t.history?(t.history[s?"pushState":"replaceState"]({},e.title,t.location.pathname+t.location.search+"#"+o.currentHash),s&&(o.hasCreatedHistory=!0)):t.location.hash=o.currentHash,o.hashTimer=null},300)))},"beforeClose.fb":function(n,o,i){i&&!1!==i.opts.hash&&(clearTimeout(o.hashTimer),o.currentHash&&o.hasCreatedHistory?t.history.back():o.currentHash&&("replaceState"in t.history?t.history.replaceState({},e.title,t.location.pathname+t.location.search+(o.origHash||"")):t.location.hash=o.origHash),o.currentHash=null)}}),n(t).on("hashchange.fb",function(){var t=o(),e=null;n.each(n(".fancybox-container").get().reverse(),function(t,o){var i=n(o).data("FancyBox");if(i&&i.currentHash)return e=i,!1}),e?e.currentHash===t.gallery+"-"+t.index||1===t.index&&e.currentHash==t.gallery||(e.currentHash=null,e.close()):""!==t.gallery&&i(t)}),setTimeout(function(){n.fancybox.getInstance()||i(o())},50))})}(window,document,jQuery),function(t,e){"use strict";var n=(new Date).getTime();e(t).on({"onInit.fb":function(t,e,o){e.$refs.stage.on("mousewheel DOMMouseScroll wheel MozMousePixelScroll",function(t){var o=e.current,i=(new Date).getTime();e.group.length<2||!1===o.opts.wheel||"auto"===o.opts.wheel&&"image"!==o.type||(t.preventDefault(),t.stopPropagation(),o.$slide.hasClass("fancybox-animated")||(t=t.originalEvent||t,i-n<250||(n=i,e[(-t.deltaY||-t.deltaX||t.wheelDelta||-t.detail)<0?"next":"previous"]())))})}})}(document,jQuery); \ No newline at end of file diff --git a/assets/vendor/flickr-justified-gallery/dist/fjGallery.css b/assets/vendor/flickr-justified-gallery/dist/fjGallery.css new file mode 100644 index 00000000..5b260751 --- /dev/null +++ b/assets/vendor/flickr-justified-gallery/dist/fjGallery.css @@ -0,0 +1,19 @@ +.fj-gallery { + position: relative; + overflow: hidden; +} +.fj-gallery::after { + content: ""; + display: block; + clear: both; +} +.fj-gallery .fj-gallery-item { + float: left; + top: 0; + left: 0; +} +.fj-gallery .fj-gallery-item > img { + display: block; + width: 100%; + height: auto; +} diff --git a/assets/vendor/flickr-justified-gallery/dist/fjGallery.min.js b/assets/vendor/flickr-justified-gallery/dist/fjGallery.min.js new file mode 100644 index 00000000..f15051f2 --- /dev/null +++ b/assets/vendor/flickr-justified-gallery/dist/fjGallery.min.js @@ -0,0 +1,13 @@ +/*! + * Flickr's Justified Gallery [fjGallery] v2.1.2 (https://flickr-justified-gallery.nkdev.info) + * Copyright 2022 nK + * Licensed under MIT (https://github.com/nk-o/flickr-justified-gallery/blob/master/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).fjGallery=e()}(this,(function(){"use strict";let t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};var e=t;function i(t,e,i){var o=(i||{}).atBegin;return function(t,e,i){var o,n=i||{},s=n.noTrailing,a=void 0!==s&&s,r=n.noLeading,h=void 0!==r&&r,c=n.debounceMode,l=void 0===c?void 0:c,g=!1,d=0;function u(){o&&clearTimeout(o)}function p(){for(var i=arguments.length,n=new Array(i),s=0;st?h?(d=Date.now(),a||(o=setTimeout(l?f:p,t))):p():!0!==a&&(o=setTimeout(l?f:p,void 0===l?t-c:t)))}return p.cancel=function(t){var e=(t||{}).upcomingOnly,i=void 0!==e&&e;u(),g=!i},p}(t,e,{debounceMode:!1!==(void 0!==o&&o)})}var o=function(t){var e=[],i=null,o=function(){for(var o=arguments.length,n=new Array(o),s=0;s=1?(this.items.push(t),this.completeLayout(s/t.aspectRatio,"justify"),!0):athis.maxAspectRatio?0===this.items.length?(this.items.push(Object.assign({},t)),this.completeLayout(s/a,"justify"),!0):(e=this.width-(this.items.length-1)*this.spacing,i=this.items.reduce((function(t,e){return t+e.aspectRatio}),0),o=e/this.targetRowHeight,Math.abs(a-r)>Math.abs(i-o)?(this.completeLayout(e/i,"justify"),!1):(this.items.push(Object.assign({},t)),this.completeLayout(s/a,"justify"),!0)):(this.items.push(Object.assign({},t)),this.completeLayout(s/a,"justify"),!0)},isLayoutComplete:function(){return this.height>0},completeLayout:function(t,e){var i,o,n,s,a,r=this.left,h=this.width-(this.items.length-1)*this.spacing;(void 0===e||["justify","center","left"].indexOf(e)<0)&&(e="left"),t!==(o=Math.max(this.edgeCaseMinRowHeight,Math.min(t,this.edgeCaseMaxRowHeight)))?(this.height=o,i=h/o/(h/t)):(this.height=t,i=1),this.items.forEach((function(t){t.top=this.top,t.width=t.aspectRatio*this.height*i,t.height=this.height,t.left=r,r+=t.width+this.spacing}),this),"justify"===e?(r-=this.spacing+this.left,n=(r-this.width)/this.items.length,s=this.items.map((function(t,e){return Math.round((e+1)*n)})),1===this.items.length?this.items[0].width-=Math.round(n):this.items.forEach((function(t,e){e>0?(t.left-=s[e-1],t.width-=s[e]-s[e-1]):t.width-=s[e]}))):"center"===e&&(a=(this.width-r)/2,this.items.forEach((function(t){t.left+=a+this.spacing}),this))},forceComplete:function(t,e){"number"==typeof e?this.completeLayout(e,this.widowLayoutStyle):this.completeLayout(this.targetRowHeight,this.widowLayoutStyle)},getItems:function(){return this.items}}; +/*! + * Copyright 2019 SmugMug, Inc. + * Licensed under the terms of the MIT license. Please see LICENSE file in the project root for terms. + * @license + */ +var s=n.exports;function a(t,e){var i;return!1!==t.fullWidthBreakoutRowCadence&&(e._rows.length+1)%t.fullWidthBreakoutRowCadence==0&&(i=!0),new s({top:e._containerHeight,left:t.containerPadding.left,width:t.containerWidth-t.containerPadding.left-t.containerPadding.right,spacing:t.boxSpacing.horizontal,targetRowHeight:t.targetRowHeight,targetRowHeightTolerance:t.targetRowHeightTolerance,edgeCaseMinRowHeight:.5*t.targetRowHeight,edgeCaseMaxRowHeight:2*t.targetRowHeight,rightToLeft:!1,isBreakoutRow:i,widowLayoutStyle:t.widowLayoutStyle})}function r(t,e,i){return e._rows.push(i),e._layoutItems=e._layoutItems.concat(i.getItems()),e._containerHeight+=i.height+t.boxSpacing.vertical,i.items}var h=function(t,e){var i={},o={},n={containerWidth:1060,containerPadding:10,boxSpacing:10,targetRowHeight:320,targetRowHeightTolerance:.25,maxNumRows:Number.POSITIVE_INFINITY,forceAspectRatio:!1,showWidows:!0,fullWidthBreakoutRowCadence:!1,widowLayoutStyle:"left"},s={},h={};return e=e||{},i=Object.assign(n,e),s.top=isNaN(parseFloat(i.containerPadding.top))?i.containerPadding:i.containerPadding.top,s.right=isNaN(parseFloat(i.containerPadding.right))?i.containerPadding:i.containerPadding.right,s.bottom=isNaN(parseFloat(i.containerPadding.bottom))?i.containerPadding:i.containerPadding.bottom,s.left=isNaN(parseFloat(i.containerPadding.left))?i.containerPadding:i.containerPadding.left,h.horizontal=isNaN(parseFloat(i.boxSpacing.horizontal))?i.boxSpacing:i.boxSpacing.horizontal,h.vertical=isNaN(parseFloat(i.boxSpacing.vertical))?i.boxSpacing:i.boxSpacing.vertical,i.containerPadding=s,i.boxSpacing=h,o._layoutItems=[],o._awakeItems=[],o._inViewportItems=[],o._leadingOrphans=[],o._trailingOrphans=[],o._containerHeight=i.containerPadding.top,o._rows=[],o._orphans=[],i._widowCount=0,function(t,e,i){var o,n,s,h=[];return t.forceAspectRatio&&i.forEach((function(e){e.forcedAspectRatio=!0,e.aspectRatio=t.forceAspectRatio})),i.some((function(i,s){if(isNaN(i.aspectRatio))throw new Error("Item "+s+" has an invalid aspect ratio");if(n||(n=a(t,e)),o=n.addItem(i),n.isLayoutComplete()){if(h=h.concat(r(t,e,n)),e._rows.length>=t.maxNumRows)return n=null,!0;if(n=a(t,e),!o&&(o=n.addItem(i),n.isLayoutComplete())){if(h=h.concat(r(t,e,n)),e._rows.length>=t.maxNumRows)return n=null,!0;n=a(t,e)}}})),n&&n.getItems().length&&t.showWidows&&(e._rows.length?(s=e._rows[e._rows.length-1].isBreakoutRow?e._rows[e._rows.length-1].targetRowHeight:e._rows[e._rows.length-1].height,n.forceComplete(!1,s)):n.forceComplete(!1),h=h.concat(r(t,e,n)),t._widowCount=n.getItems().length),e._containerHeight=e._containerHeight-t.boxSpacing.vertical,e._containerHeight=e._containerHeight+t.containerPadding.bottom,{containerHeight:e._containerHeight,widowCount:t._widowCount,boxes:e._layoutItems}}(i,o,t.map((function(t){return t.width&&t.height?{aspectRatio:t.width/t.height}:{aspectRatio:t}})))};function c(t,e){let i,o=!1,n=!1;const s=()=>{o?e(o):t.naturalWidth&&(o={width:t.naturalWidth,height:t.naturalHeight},e(o),clearInterval(i),n&&c())},a=()=>{s()},r=()=>{s()},h=()=>{0{n=!1,t.removeEventListener("load",a),t.removeEventListener("error",r)};h(),o||(n=!0,t.addEventListener("load",a),t.addEventListener("error",r),i=setInterval(h,100))}const l=[],g=o((()=>{l.forEach((t=>{t.resize()}))}));var d;e.addEventListener("resize",g),e.addEventListener("orientationchange",g),e.addEventListener("load",g),d=()=>{g()},"complete"===document.readyState||"interactive"===document.readyState?d():document.addEventListener("DOMContentLoaded",d,{capture:!0,once:!0,passive:!0});let u=0;class p{constructor(t,e){const n=this;n.instanceID=u,u+=1,n.$container=t,n.images=[],n.defaults={itemSelector:".fj-gallery-item",imageSelector:"img",gutter:10,rowHeight:320,rowHeightTolerance:.25,maxRowsCount:Number.POSITIVE_INFINITY,lastRow:"left",transitionDuration:"0.3s",calculateItemsHeight:!1,resizeDebounce:100,isRtl:"rtl"===n.css(n.$container,"direction"),onInit:null,onDestroy:null,onAppendImages:null,onBeforeJustify:null,onJustify:null};const s=n.$container.dataset||{},a={};Object.keys(s).forEach((t=>{const e=t.substr(0,1).toLowerCase()+t.substr(1);e&&void 0!==n.defaults[e]&&(a[e]=s[t])})),n.options={...n.defaults,...a,...e},n.pureOptions={...n.options},n.resize=i(n.options.resizeDebounce,n.resize),n.justify=o(n.justify.bind(n)),n.init()}css(t,i){return"string"==typeof i?e.getComputedStyle(t).getPropertyValue(i):(Object.keys(i).forEach((e=>{t.style[e]=i[e]})),t)}applyTransition(t,e){const i=this;i.onTransitionEnd(t)(),i.css(t,{"transition-property":e.join(", "),"transition-duration":i.options.transitionDuration}),t.addEventListener("transitionend",i.onTransitionEnd(t,e),!1)}onTransitionEnd(t){const e=this;return()=>{e.css(t,{"transition-property":"","transition-duration":""}),t.removeEventListener("transitionend",e.onTransitionEnd(t))}}addToFjGalleryList(){l.push(this),g()}removeFromFjGalleryList(){const t=this;l.forEach(((e,i)=>{e.instanceID===t.instanceID&&l.splice(i,1)}))}init(){const t=this;t.appendImages(t.$container.querySelectorAll(t.options.itemSelector)),t.addToFjGalleryList(),t.options.onInit&&t.options.onInit.call(t)}appendImages(t){const i=this;e.jQuery&&t instanceof e.jQuery&&(t=t.get()),t&&t.length&&(t.forEach((t=>{if(t&&!t.fjGalleryImage&&t.querySelector){const e=t.querySelector(i.options.imageSelector);if(e){t.fjGalleryImage=i;const o={$item:t,$image:e,width:parseFloat(e.getAttribute("width"))||!1,height:parseFloat(e.getAttribute("height"))||!1,loadSizes(){const t=this;c(e,(e=>{t.width===e.width&&t.height===e.height||(t.width=e.width,t.height=e.height,i.resize())}))}};o.loadSizes(),i.images.push(o)}}})),i.options.onAppendImages&&i.options.onAppendImages.call(i,[t]),i.justify())}justify(){const t=this,e=[];t.justifyCount=(t.justifyCount||0)+1,t.options.onBeforeJustify&&t.options.onBeforeJustify.call(t),t.images.forEach((t=>{t.width&&t.height&&e.push(t.width/t.height)}));const i={containerWidth:t.$container.getBoundingClientRect().width,containerPadding:{top:parseFloat(t.css(t.$container,"padding-top"))||0,right:parseFloat(t.css(t.$container,"padding-right"))||0,bottom:parseFloat(t.css(t.$container,"padding-bottom"))||0,left:parseFloat(t.css(t.$container,"padding-left"))||0},boxSpacing:t.options.gutter,targetRowHeight:t.options.rowHeight,targetRowHeightTolerance:t.options.rowHeightTolerance,maxNumRows:t.options.maxRowsCount,showWidows:"hide"!==t.options.lastRow},o=h(e,i);if(o.widowCount&&("center"===t.options.lastRow||"right"===t.options.lastRow)){const e=o.boxes[o.boxes.length-1];let n=i.containerWidth-e.width-e.left;"center"===t.options.lastRow&&(n/=2),"right"===t.options.lastRow&&(n-=i.containerPadding.right);for(let t=1;t<=o.widowCount;t+=1)o.boxes[o.boxes.length-t].left=o.boxes[o.boxes.length-t].left+n}t.options.isRtl&&o.boxes.forEach(((t,e)=>{o.boxes[e].left=i.containerWidth-o.boxes[e].left-o.boxes[e].width-i.containerPadding.right+i.containerPadding.left}));let n=0,s=0;const a={};t.images.forEach(((e,i)=>{if(o.boxes[n]&&e.width&&e.height){if(t.options.calculateItemsHeight&&void 0===a[o.boxes[n].top]&&Object.keys(a).length&&(s+=a[Object.keys(a).pop()]-o.boxes[i-1].height),t.options.transitionDuration&&1{t.css(e.$item,{position:"",transform:"",transition:"",width:"",height:""})})),t.images.forEach((t=>{delete t.$item.fjGalleryImage})),delete t.$container.fjGallery}resize(){this.justify()}}const f=function(t,e,...i){("object"==typeof HTMLElement?t instanceof HTMLElement:t&&"object"==typeof t&&null!==t&&1===t.nodeType&&"string"==typeof t.nodeName)&&(t=[t]);const o=t.length;let n,s=0;for(;s delay) {\n if (noLeading) {\n /*\n * In throttle mode with noLeading, if `delay` time has\n * been exceeded, update `lastExec` and schedule `callback`\n * to execute after `delay` ms.\n */\n lastExec = Date.now();\n\n if (!noTrailing) {\n timeoutID = setTimeout(debounceMode ? clear : exec, delay);\n }\n } else {\n /*\n * In throttle mode without noLeading, if `delay` time has been exceeded, execute\n * `callback`.\n */\n exec();\n }\n } else if (noTrailing !== true) {\n /*\n * In trailing throttle mode, since `delay` time has not been\n * exceeded, schedule `callback` to execute `delay` ms after most\n * recent execution.\n *\n * If `debounceMode` is true (at begin), schedule `clear` to execute\n * after `delay` ms.\n *\n * If `debounceMode` is false (at end), schedule `callback` to\n * execute after `delay` ms.\n */\n timeoutID = setTimeout(debounceMode ? clear : exec, debounceMode === undefined ? delay - elapsed : delay);\n }\n }\n\n wrapper.cancel = cancel; // Return the wrapper function.\n\n return wrapper;\n}\n\n/* eslint-disable no-undefined */\n/**\n * Debounce execution of a function. Debouncing, unlike throttling,\n * guarantees that a function is only executed a single time, either at the\n * very beginning of a series of calls, or at the very end.\n *\n * @param {number} delay - A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher) are most useful.\n * @param {Function} callback - A function to be executed after delay milliseconds. The `this` context and all arguments are passed through, as-is,\n * to `callback` when the debounced-function is executed.\n * @param {object} [options] - An object to configure options.\n * @param {boolean} [options.atBegin] - Optional, defaults to false. If atBegin is false or unspecified, callback will only be executed `delay` milliseconds\n * after the last debounced-function call. If atBegin is true, callback will be executed only at the first debounced-function call.\n * (After the throttled-function has not been called for `delay` milliseconds, the internal counter is reset).\n *\n * @returns {Function} A new, debounced function.\n */\n\nfunction debounce (delay, callback, options) {\n var _ref = options || {},\n _ref$atBegin = _ref.atBegin,\n atBegin = _ref$atBegin === void 0 ? false : _ref$atBegin;\n\n return throttle(delay, callback, {\n debounceMode: atBegin !== false\n });\n}\n\nexport { debounce, throttle };\n//# sourceMappingURL=index.js.map\n","var rafSchd = function rafSchd(fn) {\n var lastArgs = [];\n var frameId = null;\n\n var wrapperFn = function wrapperFn() {\n for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n args[_key] = arguments[_key];\n }\n\n lastArgs = args;\n\n if (frameId) {\n return;\n }\n\n frameId = requestAnimationFrame(function () {\n frameId = null;\n fn.apply(void 0, lastArgs);\n });\n };\n\n wrapperFn.cancel = function () {\n if (!frameId) {\n return;\n }\n\n cancelAnimationFrame(frameId);\n frameId = null;\n };\n\n return wrapperFn;\n};\n\nexport default rafSchd;\n","/*!\n * Copyright 2019 SmugMug, Inc.\n * Licensed under the terms of the MIT license. Please see LICENSE file in the project root for terms.\n * @license\n */\n\n/**\n * Row\n * Wrapper for each row in a justified layout.\n * Stores relevant values and provides methods for calculating layout of individual rows.\n *\n * @param {Object} layoutConfig - The same as that passed\n * @param {Object} Initialization parameters. The following are all required:\n * @param params.top {Number} Top of row, relative to container\n * @param params.left {Number} Left side of row relative to container (equal to container left padding)\n * @param params.width {Number} Width of row, not including container padding\n * @param params.spacing {Number} Horizontal spacing between items\n * @param params.targetRowHeight {Number} Layout algorithm will aim for this row height\n * @param params.targetRowHeightTolerance {Number} Row heights may vary +/- (`targetRowHeight` x `targetRowHeightTolerance`)\n * @param params.edgeCaseMinRowHeight {Number} Absolute minimum row height for edge cases that cannot be resolved within tolerance.\n * @param params.edgeCaseMaxRowHeight {Number} Absolute maximum row height for edge cases that cannot be resolved within tolerance.\n * @param params.isBreakoutRow {Boolean} Is this row in particular one of those breakout rows? Always false if it's not that kind of photo list\n * @param params.widowLayoutStyle {String} If widows are visible, how should they be laid out?\n * @constructor\n */\n\nvar Row = module.exports = function (params) {\n\n\t// Top of row, relative to container\n\tthis.top = params.top;\n\n\t// Left side of row relative to container (equal to container left padding)\n\tthis.left = params.left;\n\n\t// Width of row, not including container padding\n\tthis.width = params.width;\n\n\t// Horizontal spacing between items\n\tthis.spacing = params.spacing;\n\n\t// Row height calculation values\n\tthis.targetRowHeight = params.targetRowHeight;\n\tthis.targetRowHeightTolerance = params.targetRowHeightTolerance;\n\tthis.minAspectRatio = this.width / params.targetRowHeight * (1 - params.targetRowHeightTolerance);\n\tthis.maxAspectRatio = this.width / params.targetRowHeight * (1 + params.targetRowHeightTolerance);\n\n\t// Edge case row height minimum/maximum\n\tthis.edgeCaseMinRowHeight = params.edgeCaseMinRowHeight;\n\tthis.edgeCaseMaxRowHeight = params.edgeCaseMaxRowHeight;\n\n\t// Widow layout direction\n\tthis.widowLayoutStyle = params.widowLayoutStyle;\n\n\t// Full width breakout rows\n\tthis.isBreakoutRow = params.isBreakoutRow;\n\n\t// Store layout data for each item in row\n\tthis.items = [];\n\n\t// Height remains at 0 until it's been calculated\n\tthis.height = 0;\n\n};\n\nRow.prototype = {\n\n\t/**\n\t * Attempt to add a single item to the row.\n\t * This is the heart of the justified algorithm.\n\t * This method is direction-agnostic; it deals only with sizes, not positions.\n\t *\n\t * If the item fits in the row, without pushing row height beyond min/max tolerance,\n\t * the item is added and the method returns true.\n\t *\n\t * If the item leaves row height too high, there may be room to scale it down and add another item.\n\t * In this case, the item is added and the method returns true, but the row is incomplete.\n\t *\n\t * If the item leaves row height too short, there are too many items to fit within tolerance.\n\t * The method will either accept or reject the new item, favoring the resulting row height closest to within tolerance.\n\t * If the item is rejected, left/right padding will be required to fit the row height within tolerance;\n\t * if the item is accepted, top/bottom cropping will be required to fit the row height within tolerance.\n\t *\n\t * @method addItem\n\t * @param itemData {Object} Item layout data, containing item aspect ratio.\n\t * @return {Boolean} True if successfully added; false if rejected.\n\t */\n\n\taddItem: function (itemData) {\n\n\t\tvar newItems = this.items.concat(itemData),\n\t\t\t// Calculate aspect ratios for items only; exclude spacing\n\t\t\trowWidthWithoutSpacing = this.width - (newItems.length - 1) * this.spacing,\n\t\t\tnewAspectRatio = newItems.reduce(function (sum, item) {\n\t\t\t\treturn sum + item.aspectRatio;\n\t\t\t}, 0),\n\t\t\ttargetAspectRatio = rowWidthWithoutSpacing / this.targetRowHeight,\n\t\t\tpreviousRowWidthWithoutSpacing,\n\t\t\tpreviousAspectRatio,\n\t\t\tpreviousTargetAspectRatio;\n\n\t\t// Handle big full-width breakout photos if we're doing them\n\t\tif (this.isBreakoutRow) {\n\t\t\t// Only do it if there's no other items in this row\n\t\t\tif (this.items.length === 0) {\n\t\t\t\t// Only go full width if this photo is a square or landscape\n\t\t\t\tif (itemData.aspectRatio >= 1) {\n\t\t\t\t\t// Close out the row with a full width photo\n\t\t\t\t\tthis.items.push(itemData);\n\t\t\t\t\tthis.completeLayout(rowWidthWithoutSpacing / itemData.aspectRatio, 'justify');\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (newAspectRatio < this.minAspectRatio) {\n\n\t\t\t// New aspect ratio is too narrow / scaled row height is too tall.\n\t\t\t// Accept this item and leave row open for more items.\n\n\t\t\tthis.items.push(Object.assign({}, itemData));\n\t\t\treturn true;\n\n\t\t} else if (newAspectRatio > this.maxAspectRatio) {\n\n\t\t\t// New aspect ratio is too wide / scaled row height will be too short.\n\t\t\t// Accept item if the resulting aspect ratio is closer to target than it would be without the item.\n\t\t\t// NOTE: Any row that falls into this block will require cropping/padding on individual items.\n\n\t\t\tif (this.items.length === 0) {\n\n\t\t\t\t// When there are no existing items, force acceptance of the new item and complete the layout.\n\t\t\t\t// This is the pano special case.\n\t\t\t\tthis.items.push(Object.assign({}, itemData));\n\t\t\t\tthis.completeLayout(rowWidthWithoutSpacing / newAspectRatio, 'justify');\n\t\t\t\treturn true;\n\n\t\t\t}\n\n\t\t\t// Calculate width/aspect ratio for row before adding new item\n\t\t\tpreviousRowWidthWithoutSpacing = this.width - (this.items.length - 1) * this.spacing;\n\t\t\tpreviousAspectRatio = this.items.reduce(function (sum, item) {\n\t\t\t\treturn sum + item.aspectRatio;\n\t\t\t}, 0);\n\t\t\tpreviousTargetAspectRatio = previousRowWidthWithoutSpacing / this.targetRowHeight;\n\n\t\t\tif (Math.abs(newAspectRatio - targetAspectRatio) > Math.abs(previousAspectRatio - previousTargetAspectRatio)) {\n\n\t\t\t\t// Row with new item is us farther away from target than row without; complete layout and reject item.\n\t\t\t\tthis.completeLayout(previousRowWidthWithoutSpacing / previousAspectRatio, 'justify');\n\t\t\t\treturn false;\n\n\t\t\t} else {\n\n\t\t\t\t// Row with new item is us closer to target than row without;\n\t\t\t\t// accept the new item and complete the row layout.\n\t\t\t\tthis.items.push(Object.assign({}, itemData));\n\t\t\t\tthis.completeLayout(rowWidthWithoutSpacing / newAspectRatio, 'justify');\n\t\t\t\treturn true;\n\n\t\t\t}\n\n\t\t} else {\n\n\t\t\t// New aspect ratio / scaled row height is within tolerance;\n\t\t\t// accept the new item and complete the row layout.\n\t\t\tthis.items.push(Object.assign({}, itemData));\n\t\t\tthis.completeLayout(rowWidthWithoutSpacing / newAspectRatio, 'justify');\n\t\t\treturn true;\n\n\t\t}\n\n\t},\n\n\t/**\n\t * Check if a row has completed its layout.\n\t *\n\t * @method isLayoutComplete\n\t * @return {Boolean} True if complete; false if not.\n\t */\n\n\tisLayoutComplete: function () {\n\t\treturn this.height > 0;\n\t},\n\n\t/**\n\t * Set row height and compute item geometry from that height.\n\t * Will justify items within the row unless instructed not to.\n\t *\n\t * @method completeLayout\n\t * @param newHeight {Number} Set row height to this value.\n\t * @param widowLayoutStyle {String} How should widows display? Supported: left | justify | center\n\t */\n\n\tcompleteLayout: function (newHeight, widowLayoutStyle) {\n\n\t\tvar itemWidthSum = this.left,\n\t\t\trowWidthWithoutSpacing = this.width - (this.items.length - 1) * this.spacing,\n\t\t\tclampedToNativeRatio,\n\t\t\tclampedHeight,\n\t\t\terrorWidthPerItem,\n\t\t\troundedCumulativeErrors,\n\t\t\tsingleItemGeometry,\n\t\t\tcenterOffset;\n\n\t\t// Justify unless explicitly specified otherwise.\n\t\tif (typeof widowLayoutStyle === 'undefined' || ['justify', 'center', 'left'].indexOf(widowLayoutStyle) < 0) {\n\t\t\twidowLayoutStyle = 'left';\n\t\t}\n\n\t\t// Clamp row height to edge case minimum/maximum.\n\t\tclampedHeight = Math.max(this.edgeCaseMinRowHeight, Math.min(newHeight, this.edgeCaseMaxRowHeight));\n\n\t\tif (newHeight !== clampedHeight) {\n\n\t\t\t// If row height was clamped, the resulting row/item aspect ratio will be off,\n\t\t\t// so force it to fit the width (recalculate aspectRatio to match clamped height).\n\t\t\t// NOTE: this will result in cropping/padding commensurate to the amount of clamping.\n\t\t\tthis.height = clampedHeight;\n\t\t\tclampedToNativeRatio = (rowWidthWithoutSpacing / clampedHeight) / (rowWidthWithoutSpacing / newHeight);\n\n\t\t} else {\n\n\t\t\t// If not clamped, leave ratio at 1.0.\n\t\t\tthis.height = newHeight;\n\t\t\tclampedToNativeRatio = 1.0;\n\n\t\t}\n\n\t\t// Compute item geometry based on newHeight.\n\t\tthis.items.forEach(function (item) {\n\n\t\t\titem.top = this.top;\n\t\t\titem.width = item.aspectRatio * this.height * clampedToNativeRatio;\n\t\t\titem.height = this.height;\n\n\t\t\t// Left-to-right.\n\t\t\t// TODO right to left\n\t\t\t// item.left = this.width - itemWidthSum - item.width;\n\t\t\titem.left = itemWidthSum;\n\n\t\t\t// Increment width.\n\t\t\titemWidthSum += item.width + this.spacing;\n\n\t\t}, this);\n\n\t\t// If specified, ensure items fill row and distribute error\n\t\t// caused by rounding width and height across all items.\n\t\tif (widowLayoutStyle === 'justify') {\n\n\t\t\titemWidthSum -= (this.spacing + this.left);\n\n\t\t\terrorWidthPerItem = (itemWidthSum - this.width) / this.items.length;\n\t\t\troundedCumulativeErrors = this.items.map(function (item, i) {\n\t\t\t\treturn Math.round((i + 1) * errorWidthPerItem);\n\t\t\t});\n\n\n\t\t\tif (this.items.length === 1) {\n\n\t\t\t\t// For rows with only one item, adjust item width to fill row.\n\t\t\t\tsingleItemGeometry = this.items[0];\n\t\t\t\tsingleItemGeometry.width -= Math.round(errorWidthPerItem);\n\n\t\t\t} else {\n\n\t\t\t\t// For rows with multiple items, adjust item width and shift items to fill the row,\n\t\t\t\t// while maintaining equal spacing between items in the row.\n\t\t\t\tthis.items.forEach(function (item, i) {\n\t\t\t\t\tif (i > 0) {\n\t\t\t\t\t\titem.left -= roundedCumulativeErrors[i - 1];\n\t\t\t\t\t\titem.width -= (roundedCumulativeErrors[i] - roundedCumulativeErrors[i - 1]);\n\t\t\t\t\t} else {\n\t\t\t\t\t\titem.width -= roundedCumulativeErrors[i];\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t}\n\n\t\t} else if (widowLayoutStyle === 'center') {\n\n\t\t\t// Center widows\n\t\t\tcenterOffset = (this.width - itemWidthSum) / 2;\n\n\t\t\tthis.items.forEach(function (item) {\n\t\t\t\titem.left += centerOffset + this.spacing;\n\t\t\t}, this);\n\n\t\t}\n\n\t},\n\n\t/**\n\t * Force completion of row layout with current items.\n\t *\n\t * @method forceComplete\n\t * @param fitToWidth {Boolean} Stretch current items to fill the row width.\n\t * This will likely result in padding.\n\t * @param fitToWidth {Number}\n\t */\n\n\tforceComplete: function (fitToWidth, rowHeight) {\n\n\t\t// TODO Handle fitting to width\n\t\t// var rowWidthWithoutSpacing = this.width - (this.items.length - 1) * this.spacing,\n\t\t// \tcurrentAspectRatio = this.items.reduce(function (sum, item) {\n\t\t// \t\treturn sum + item.aspectRatio;\n\t\t// \t}, 0);\n\n\t\tif (typeof rowHeight === 'number') {\n\n\t\t\tthis.completeLayout(rowHeight, this.widowLayoutStyle);\n\n\t\t} else {\n\n\t\t\t// Complete using target row height.\n\t\t\tthis.completeLayout(this.targetRowHeight, this.widowLayoutStyle);\n\t\t}\n\n\t},\n\n\t/**\n\t * Return layout data for items within row.\n\t * Note: returns actual list, not a copy.\n\t *\n\t * @method getItems\n\t * @return Layout data for items within row.\n\t */\n\n\tgetItems: function () {\n\t\treturn this.items;\n\t}\n\n};\n","/*!\n * Copyright 2019 SmugMug, Inc.\n * Licensed under the terms of the MIT license. Please see LICENSE file in the project root for terms.\n * @license\n */\n\n'use strict';\n\nvar Row = require('./row');\n\n/**\n * Create a new, empty row.\n *\n * @method createNewRow\n * @param layoutConfig {Object} The layout configuration\n * @param layoutData {Object} The current state of the layout\n * @return A new, empty row of the type specified by this layout.\n */\n\nfunction createNewRow(layoutConfig, layoutData) {\n\n\tvar isBreakoutRow;\n\n\t// Work out if this is a full width breakout row\n\tif (layoutConfig.fullWidthBreakoutRowCadence !== false) {\n\t\tif (((layoutData._rows.length + 1) % layoutConfig.fullWidthBreakoutRowCadence) === 0) {\n\t\t\tisBreakoutRow = true;\n\t\t}\n\t}\n\n\treturn new Row({\n\t\ttop: layoutData._containerHeight,\n\t\tleft: layoutConfig.containerPadding.left,\n\t\twidth: layoutConfig.containerWidth - layoutConfig.containerPadding.left - layoutConfig.containerPadding.right,\n\t\tspacing: layoutConfig.boxSpacing.horizontal,\n\t\ttargetRowHeight: layoutConfig.targetRowHeight,\n\t\ttargetRowHeightTolerance: layoutConfig.targetRowHeightTolerance,\n\t\tedgeCaseMinRowHeight: 0.5 * layoutConfig.targetRowHeight,\n\t\tedgeCaseMaxRowHeight: 2 * layoutConfig.targetRowHeight,\n\t\trightToLeft: false,\n\t\tisBreakoutRow: isBreakoutRow,\n\t\twidowLayoutStyle: layoutConfig.widowLayoutStyle\n\t});\n}\n\n/**\n * Add a completed row to the layout.\n * Note: the row must have already been completed.\n *\n * @method addRow\n * @param layoutConfig {Object} The layout configuration\n * @param layoutData {Object} The current state of the layout\n * @param row {Row} The row to add.\n * @return {Array} Each item added to the row.\n */\n\nfunction addRow(layoutConfig, layoutData, row) {\n\n\tlayoutData._rows.push(row);\n\tlayoutData._layoutItems = layoutData._layoutItems.concat(row.getItems());\n\n\t// Increment the container height\n\tlayoutData._containerHeight += row.height + layoutConfig.boxSpacing.vertical;\n\n\treturn row.items;\n}\n\n/**\n * Calculate the current layout for all items in the list that require layout.\n * \"Layout\" means geometry: position within container and size\n *\n * @method computeLayout\n * @param layoutConfig {Object} The layout configuration\n * @param layoutData {Object} The current state of the layout\n * @param itemLayoutData {Array} Array of items to lay out, with data required to lay out each item\n * @return {Object} The newly-calculated layout, containing the new container height, and lists of layout items\n */\n\nfunction computeLayout(layoutConfig, layoutData, itemLayoutData) {\n\n\tvar laidOutItems = [],\n\t\titemAdded,\n\t\tcurrentRow,\n\t\tnextToLastRowHeight;\n\n\t// Apply forced aspect ratio if specified, and set a flag.\n\tif (layoutConfig.forceAspectRatio) {\n\t\titemLayoutData.forEach(function (itemData) {\n\t\t\titemData.forcedAspectRatio = true;\n\t\t\titemData.aspectRatio = layoutConfig.forceAspectRatio;\n\t\t});\n\t}\n\n\t// Loop through the items\n\titemLayoutData.some(function (itemData, i) {\n\n\t\tif (isNaN(itemData.aspectRatio)) {\n\t\t\tthrow new Error(\"Item \" + i + \" has an invalid aspect ratio\");\n\t\t}\n\n\t\t// If not currently building up a row, make a new one.\n\t\tif (!currentRow) {\n\t\t\tcurrentRow = createNewRow(layoutConfig, layoutData);\n\t\t}\n\n\t\t// Attempt to add item to the current row.\n\t\titemAdded = currentRow.addItem(itemData);\n\n\t\tif (currentRow.isLayoutComplete()) {\n\n\t\t\t// Row is filled; add it and start a new one\n\t\t\tlaidOutItems = laidOutItems.concat(addRow(layoutConfig, layoutData, currentRow));\n\n\t\t\tif (layoutData._rows.length >= layoutConfig.maxNumRows) {\n\t\t\t\tcurrentRow = null;\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tcurrentRow = createNewRow(layoutConfig, layoutData);\n\n\t\t\t// Item was rejected; add it to its own row\n\t\t\tif (!itemAdded) {\n\n\t\t\t\titemAdded = currentRow.addItem(itemData);\n\n\t\t\t\tif (currentRow.isLayoutComplete()) {\n\n\t\t\t\t\t// If the rejected item fills a row on its own, add the row and start another new one\n\t\t\t\t\tlaidOutItems = laidOutItems.concat(addRow(layoutConfig, layoutData, currentRow));\n\t\t\t\t\tif (layoutData._rows.length >= layoutConfig.maxNumRows) {\n\t\t\t\t\t\tcurrentRow = null;\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t\tcurrentRow = createNewRow(layoutConfig, layoutData);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t});\n\n\t// Handle any leftover content (orphans) depending on where they lie\n\t// in this layout update, and in the total content set.\n\tif (currentRow && currentRow.getItems().length && layoutConfig.showWidows) {\n\n\t\t// Last page of all content or orphan suppression is suppressed; lay out orphans.\n\t\tif (layoutData._rows.length) {\n\n\t\t\t// Only Match previous row's height if it exists and it isn't a breakout row\n\t\t\tif (layoutData._rows[layoutData._rows.length - 1].isBreakoutRow) {\n\t\t\t\tnextToLastRowHeight = layoutData._rows[layoutData._rows.length - 1].targetRowHeight;\n\t\t\t} else {\n\t\t\t\tnextToLastRowHeight = layoutData._rows[layoutData._rows.length - 1].height;\n\t\t\t}\n\n\t\t\tcurrentRow.forceComplete(false, nextToLastRowHeight);\n\n\t\t} else {\n\n\t\t\t// ...else use target height if there is no other row height to reference.\n\t\t\tcurrentRow.forceComplete(false);\n\n\t\t}\n\n\t\tlaidOutItems = laidOutItems.concat(addRow(layoutConfig, layoutData, currentRow));\n\t\tlayoutConfig._widowCount = currentRow.getItems().length;\n\n\t}\n\n\t// We need to clean up the bottom container padding\n\t// First remove the height added for box spacing\n\tlayoutData._containerHeight = layoutData._containerHeight - layoutConfig.boxSpacing.vertical;\n\t// Then add our bottom container padding\n\tlayoutData._containerHeight = layoutData._containerHeight + layoutConfig.containerPadding.bottom;\n\n\treturn {\n\t\tcontainerHeight: layoutData._containerHeight,\n\t\twidowCount: layoutConfig._widowCount,\n\t\tboxes: layoutData._layoutItems\n\t};\n\n}\n\n/**\n * Takes in a bunch of box data and config. Returns\n * geometry to lay them out in a justified view.\n *\n * @method covertSizesToAspectRatios\n * @param sizes {Array} Array of objects with widths and heights\n * @return {Array} A list of aspect ratios\n */\n\nmodule.exports = function (input, config) {\n\tvar layoutConfig = {};\n\tvar layoutData = {};\n\n\t// Defaults\n\tvar defaults = {\n\t\tcontainerWidth: 1060,\n\t\tcontainerPadding: 10,\n\t\tboxSpacing: 10,\n\t\ttargetRowHeight: 320,\n\t\ttargetRowHeightTolerance: 0.25,\n\t\tmaxNumRows: Number.POSITIVE_INFINITY,\n\t\tforceAspectRatio: false,\n\t\tshowWidows: true,\n\t\tfullWidthBreakoutRowCadence: false,\n\t\twidowLayoutStyle: 'left'\n\t};\n\n\tvar containerPadding = {};\n\tvar boxSpacing = {};\n\n\tconfig = config || {};\n\n\t// Merge defaults and config passed in\n\tlayoutConfig = Object.assign(defaults, config);\n\n\t// Sort out padding and spacing values\n\tcontainerPadding.top = (!isNaN(parseFloat(layoutConfig.containerPadding.top))) ? layoutConfig.containerPadding.top : layoutConfig.containerPadding;\n\tcontainerPadding.right = (!isNaN(parseFloat(layoutConfig.containerPadding.right))) ? layoutConfig.containerPadding.right : layoutConfig.containerPadding;\n\tcontainerPadding.bottom = (!isNaN(parseFloat(layoutConfig.containerPadding.bottom))) ? layoutConfig.containerPadding.bottom : layoutConfig.containerPadding;\n\tcontainerPadding.left = (!isNaN(parseFloat(layoutConfig.containerPadding.left))) ? layoutConfig.containerPadding.left : layoutConfig.containerPadding;\n\tboxSpacing.horizontal = (!isNaN(parseFloat(layoutConfig.boxSpacing.horizontal))) ? layoutConfig.boxSpacing.horizontal : layoutConfig.boxSpacing;\n\tboxSpacing.vertical = (!isNaN(parseFloat(layoutConfig.boxSpacing.vertical))) ? layoutConfig.boxSpacing.vertical : layoutConfig.boxSpacing;\n\n\tlayoutConfig.containerPadding = containerPadding;\n\tlayoutConfig.boxSpacing = boxSpacing;\n\n\t// Local\n\tlayoutData._layoutItems = [];\n\tlayoutData._awakeItems = [];\n\tlayoutData._inViewportItems = [];\n\tlayoutData._leadingOrphans = [];\n\tlayoutData._trailingOrphans = [];\n\tlayoutData._containerHeight = layoutConfig.containerPadding.top;\n\tlayoutData._rows = [];\n\tlayoutData._orphans = [];\n\tlayoutConfig._widowCount = 0;\n\n\t// Convert widths and heights to aspect ratios if we need to\n\treturn computeLayout(layoutConfig, layoutData, input.map(function (item) {\n\t\tif (item.width && item.height) {\n\t\t\treturn { aspectRatio: item.width / item.height };\n\t\t} else {\n\t\t\treturn { aspectRatio: item };\n\t\t}\n\t}));\n};\n","// get image dimensions\n// thanks https://gist.github.com/dimsemenov/5382856\nfunction getImgDimensions(img, cb) {\n let interval;\n let hasSize = false;\n let addedListeners = false;\n\n const onHasSize = () => {\n if (hasSize) {\n cb(hasSize);\n return;\n }\n\n // check for non-zero, non-undefined naturalWidth\n if (!img.naturalWidth) {\n return;\n }\n\n hasSize = {\n width: img.naturalWidth,\n height: img.naturalHeight,\n };\n cb(hasSize);\n\n clearInterval(interval);\n if (addedListeners) {\n // eslint-disable-next-line no-use-before-define\n removeListeners();\n }\n };\n const onLoaded = () => {\n onHasSize();\n };\n const onError = () => {\n onHasSize();\n };\n const checkSize = () => {\n if (0 < img.naturalWidth) {\n onHasSize();\n }\n };\n const addListeners = () => {\n addedListeners = true;\n img.addEventListener('load', onLoaded);\n img.addEventListener('error', onError);\n };\n const removeListeners = () => {\n addedListeners = false;\n img.removeEventListener('load', onLoaded);\n img.removeEventListener('error', onError);\n };\n\n checkSize();\n\n if (!hasSize) {\n addListeners();\n interval = setInterval(checkSize, 100);\n }\n}\n\nexport default getImgDimensions;\n","import { debounce } from 'throttle-debounce';\nimport rafSchd from 'raf-schd';\nimport justifiedLayout from 'justified-layout';\n\nimport domReady from './utils/ready';\nimport global from './utils/global';\nimport getImgDimensions from './utils/get-img-dimensions';\n\n// list with all fjGallery instances\n// need to render all in one scroll/resize event\nconst fjGalleryList = [];\n\nconst updateFjGallery = rafSchd(() => {\n fjGalleryList.forEach((item) => {\n item.resize();\n });\n});\n\nglobal.addEventListener('resize', updateFjGallery);\nglobal.addEventListener('orientationchange', updateFjGallery);\nglobal.addEventListener('load', updateFjGallery);\ndomReady(() => {\n updateFjGallery();\n});\n\nlet instanceID = 0;\n\n// fjGallery class\nclass FJGallery {\n constructor(container, userOptions) {\n const self = this;\n\n self.instanceID = instanceID;\n instanceID += 1;\n\n self.$container = container;\n\n self.images = [];\n\n self.defaults = {\n itemSelector: '.fj-gallery-item',\n imageSelector: 'img',\n gutter: 10, // supports object like `{ horizontal: 10, vertical: 10 }`.\n rowHeight: 320,\n rowHeightTolerance: 0.25, // [0, 1]\n maxRowsCount: Number.POSITIVE_INFINITY,\n lastRow: 'left', // left, center, right, hide\n transitionDuration: '0.3s',\n calculateItemsHeight: false,\n resizeDebounce: 100,\n isRtl: 'rtl' === self.css(self.$container, 'direction'),\n\n // events\n onInit: null, // function() {}\n onDestroy: null, // function() {}\n onAppendImages: null, // function() {}\n onBeforeJustify: null, // function() {}\n onJustify: null, // function() {}\n };\n\n // prepare data-options\n const dataOptions = self.$container.dataset || {};\n const pureDataOptions = {};\n Object.keys(dataOptions).forEach((key) => {\n const loweCaseOption = key.substr(0, 1).toLowerCase() + key.substr(1);\n if (loweCaseOption && 'undefined' !== typeof self.defaults[loweCaseOption]) {\n pureDataOptions[loweCaseOption] = dataOptions[key];\n }\n });\n\n self.options = {\n ...self.defaults,\n ...pureDataOptions,\n ...userOptions,\n };\n\n self.pureOptions = {\n ...self.options,\n };\n\n // debounce for resize\n self.resize = debounce(self.options.resizeDebounce, self.resize);\n self.justify = rafSchd(self.justify.bind(self));\n\n self.init();\n }\n\n // add styles to element\n // eslint-disable-next-line class-methods-use-this\n css(el, styles) {\n if ('string' === typeof styles) {\n return global.getComputedStyle(el).getPropertyValue(styles);\n }\n\n Object.keys(styles).forEach((key) => {\n el.style[key] = styles[key];\n });\n return el;\n }\n\n // set temporary transition with event listener\n applyTransition($item, properties) {\n const self = this;\n\n // Remove previous event listener\n self.onTransitionEnd($item)();\n\n // Add transitions\n self.css($item, {\n 'transition-property': properties.join(', '),\n 'transition-duration': self.options.transitionDuration,\n });\n\n // Add event listener\n $item.addEventListener('transitionend', self.onTransitionEnd($item, properties), false);\n }\n\n onTransitionEnd($item) {\n const self = this;\n\n return () => {\n self.css($item, {\n 'transition-property': '',\n 'transition-duration': '',\n });\n\n $item.removeEventListener('transitionend', self.onTransitionEnd($item));\n };\n }\n\n // add to fjGallery instances list\n addToFjGalleryList() {\n fjGalleryList.push(this);\n updateFjGallery();\n }\n\n // remove from fjGallery instances list\n removeFromFjGalleryList() {\n const self = this;\n\n fjGalleryList.forEach((item, key) => {\n if (item.instanceID === self.instanceID) {\n fjGalleryList.splice(key, 1);\n }\n });\n }\n\n init() {\n const self = this;\n\n self.appendImages(self.$container.querySelectorAll(self.options.itemSelector));\n\n self.addToFjGalleryList();\n\n // call onInit event\n if (self.options.onInit) {\n self.options.onInit.call(self);\n }\n }\n\n // append images\n appendImages($images) {\n const self = this;\n\n // check if jQuery\n if (global.jQuery && $images instanceof global.jQuery) {\n $images = $images.get();\n }\n\n if (!$images || !$images.length) {\n return;\n }\n\n $images.forEach(($item) => {\n // if $images is jQuery, for some reason in this array there is undefined item, that not a DOM,\n // so we need to check for $item.querySelector.\n if ($item && !$item.fjGalleryImage && $item.querySelector) {\n const $image = $item.querySelector(self.options.imageSelector);\n\n if ($image) {\n $item.fjGalleryImage = self;\n const data = {\n $item,\n $image,\n width: parseFloat($image.getAttribute('width')) || false,\n height: parseFloat($image.getAttribute('height')) || false,\n loadSizes() {\n const itemData = this;\n getImgDimensions($image, (dimensions) => {\n if (itemData.width !== dimensions.width || itemData.height !== dimensions.height) {\n itemData.width = dimensions.width;\n itemData.height = dimensions.height;\n self.resize();\n }\n });\n },\n };\n data.loadSizes();\n\n self.images.push(data);\n }\n }\n });\n\n // call onAppendImages event\n if (self.options.onAppendImages) {\n self.options.onAppendImages.call(self, [$images]);\n }\n\n self.justify();\n }\n\n // justify images\n justify() {\n const self = this;\n const justifyArray = [];\n\n self.justifyCount = (self.justifyCount || 0) + 1;\n\n // call onBeforeJustify event\n if (self.options.onBeforeJustify) {\n self.options.onBeforeJustify.call(self);\n }\n\n self.images.forEach((data) => {\n if (data.width && data.height) {\n justifyArray.push(data.width / data.height);\n }\n });\n\n const justifiedOptions = {\n containerWidth: self.$container.getBoundingClientRect().width,\n containerPadding: {\n top: parseFloat(self.css(self.$container, 'padding-top')) || 0,\n right: parseFloat(self.css(self.$container, 'padding-right')) || 0,\n bottom: parseFloat(self.css(self.$container, 'padding-bottom')) || 0,\n left: parseFloat(self.css(self.$container, 'padding-left')) || 0,\n },\n boxSpacing: self.options.gutter,\n targetRowHeight: self.options.rowHeight,\n targetRowHeightTolerance: self.options.rowHeightTolerance,\n maxNumRows: self.options.maxRowsCount,\n showWidows: 'hide' !== self.options.lastRow,\n };\n const justifiedData = justifiedLayout(justifyArray, justifiedOptions);\n\n // Align last row\n if (\n justifiedData.widowCount &&\n ('center' === self.options.lastRow || 'right' === self.options.lastRow)\n ) {\n const lastItemData = justifiedData.boxes[justifiedData.boxes.length - 1];\n let gapSize = justifiedOptions.containerWidth - lastItemData.width - lastItemData.left;\n\n if ('center' === self.options.lastRow) {\n gapSize /= 2;\n }\n if ('right' === self.options.lastRow) {\n gapSize -= justifiedOptions.containerPadding.right;\n }\n\n for (let i = 1; i <= justifiedData.widowCount; i += 1) {\n justifiedData.boxes[justifiedData.boxes.length - i].left =\n justifiedData.boxes[justifiedData.boxes.length - i].left + gapSize;\n }\n }\n\n // RTL compatibility\n if (self.options.isRtl) {\n justifiedData.boxes.forEach((boxData, i) => {\n justifiedData.boxes[i].left =\n justifiedOptions.containerWidth -\n justifiedData.boxes[i].left -\n justifiedData.boxes[i].width -\n justifiedOptions.containerPadding.right +\n justifiedOptions.containerPadding.left;\n });\n }\n\n let i = 0;\n let additionalTopOffset = 0;\n const rowsMaxHeight = {};\n\n // Set image sizes.\n self.images.forEach((data, imgI) => {\n if (justifiedData.boxes[i] && data.width && data.height) {\n // calculate additional offset based on actual items height.\n if (\n self.options.calculateItemsHeight &&\n 'undefined' === typeof rowsMaxHeight[justifiedData.boxes[i].top] &&\n Object.keys(rowsMaxHeight).length\n ) {\n additionalTopOffset +=\n rowsMaxHeight[Object.keys(rowsMaxHeight).pop()] - justifiedData.boxes[imgI - 1].height;\n }\n\n if (self.options.transitionDuration && 1 < self.justifyCount) {\n self.applyTransition(data.$item, ['transform']);\n }\n\n self.css(data.$item, {\n display: '',\n position: 'absolute',\n transform: `translateX(${justifiedData.boxes[i].left}px) translateY(${\n justifiedData.boxes[i].top + additionalTopOffset\n }px) translateZ(0)`,\n width: `${justifiedData.boxes[i].width}px`,\n });\n\n // calculate actual items height.\n if (self.options.calculateItemsHeight) {\n const rect = data.$item.getBoundingClientRect();\n\n if (\n 'undefined' === typeof rowsMaxHeight[justifiedData.boxes[i].top] ||\n rowsMaxHeight[justifiedData.boxes[i].top] < rect.height\n ) {\n rowsMaxHeight[justifiedData.boxes[i].top] = rect.height;\n }\n }\n\n i += 1;\n } else {\n self.css(data.$item, {\n display: 'none',\n });\n }\n });\n\n // increase additional offset based on the latest row items height.\n if (self.options.calculateItemsHeight && Object.keys(rowsMaxHeight).length) {\n additionalTopOffset +=\n rowsMaxHeight[Object.keys(rowsMaxHeight).pop()] -\n justifiedData.boxes[justifiedData.boxes.length - 1].height;\n }\n\n if (self.options.transitionDuration) {\n self.applyTransition(self.$container, ['height']);\n }\n\n // Set container height.\n self.css(self.$container, {\n height: `${justifiedData.containerHeight + additionalTopOffset}px`,\n });\n\n // call onJustify event\n if (self.options.onJustify) {\n self.options.onJustify.call(self);\n }\n }\n\n // update options and resize gallery items\n updateOptions(options) {\n const self = this;\n self.options = {\n ...self.options,\n ...options,\n };\n self.justify();\n }\n\n destroy() {\n const self = this;\n\n self.removeFromFjGalleryList();\n\n self.justifyCount = 0;\n\n // call onDestroy event\n if (self.options.onDestroy) {\n self.options.onDestroy.call(self);\n }\n\n // remove styles.\n self.css(self.$container, {\n height: '',\n transition: '',\n });\n self.images.forEach((data) => {\n self.css(data.$item, {\n position: '',\n transform: '',\n transition: '',\n width: '',\n height: '',\n });\n });\n\n // delete fjGalleryImage instance from images\n self.images.forEach((val) => {\n delete val.$item.fjGalleryImage;\n });\n\n // delete fjGallery instance from container\n delete self.$container.fjGallery;\n }\n\n resize() {\n const self = this;\n\n self.justify();\n }\n}\n\n// global definition\nconst fjGallery = function (items, options, ...args) {\n // check for dom element\n // thanks: http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object\n if (\n 'object' === typeof HTMLElement\n ? items instanceof HTMLElement\n : items &&\n 'object' === typeof items &&\n null !== items &&\n 1 === items.nodeType &&\n 'string' === typeof items.nodeName\n ) {\n items = [items];\n }\n\n const len = items.length;\n let k = 0;\n let ret;\n\n for (k; k < len; k += 1) {\n if ('object' === typeof options || 'undefined' === typeof options) {\n if (!items[k].fjGallery) {\n // eslint-disable-next-line new-cap\n items[k].fjGallery = new FJGallery(items[k], options);\n }\n } else if (items[k].fjGallery) {\n // eslint-disable-next-line prefer-spread\n ret = items[k].fjGallery[options].apply(items[k].fjGallery, args);\n }\n if ('undefined' !== typeof ret) {\n return ret;\n }\n }\n\n return items;\n};\nfjGallery.constructor = FJGallery;\n\nexport default fjGallery;\n","function ready(callback) {\n if ('complete' === document.readyState || 'interactive' === document.readyState) {\n // Already ready or interactive, execute callback\n callback();\n } else {\n document.addEventListener('DOMContentLoaded', callback, {\n capture: true,\n once: true,\n passive: true,\n });\n }\n}\n\nexport default ready;\n","import global from './utils/global';\nimport fjGallery from './fjGallery';\n\nconst $ = global.jQuery;\n\n// jQuery support\nif ('undefined' !== typeof $) {\n // add data to jQuery .data('fjGallery')\n const oldInit = fjGallery.constructor.prototype.init;\n fjGallery.constructor.prototype.init = function () {\n $(this.$container).data('fjGallery', this);\n\n if (oldInit) {\n oldInit.call(this);\n }\n };\n\n // remove data from jQuery .data('fjGallery')\n const oldDestroy = fjGallery.constructor.prototype.destroy;\n fjGallery.constructor.prototype.destroy = function () {\n if (this.$container) {\n $(this.$container).removeData('fjGallery');\n }\n if (oldDestroy) {\n oldDestroy.call(this);\n }\n };\n\n const $Plugin = function (...args) {\n Array.prototype.unshift.call(args, this);\n const res = fjGallery.apply(global, args);\n return 'object' !== typeof res ? res : this;\n };\n $Plugin.constructor = fjGallery.constructor;\n\n // no conflict\n const old$Plugin = $.fn.fjGallery;\n $.fn.fjGallery = $Plugin;\n $.fn.fjGallery.noConflict = function () {\n $.fn.fjGallery = old$Plugin;\n return this;\n };\n}\n\nexport default fjGallery;\n"],"names":["win","window","global","self","global$1","delay","callback","options","timeoutID","_ref$noTrailing","noTrailing","_ref$noLeading","noLeading","_ref$debounceMode","debounceMode","undefined","cancelled","lastExec","clearExistingTimeout","clearTimeout","wrapper","_len","arguments","length","arguments_","Array","_key","this","elapsed","Date","now","exec","apply","clear","setTimeout","cancel","_ref2$upcomingOnly","upcomingOnly","rafSchd","fn","lastArgs","frameId","wrapperFn","args","requestAnimationFrame","cancelAnimationFrame","rowModule","exports","params","top","left","width","spacing","targetRowHeight","targetRowHeightTolerance","minAspectRatio","maxAspectRatio","edgeCaseMinRowHeight","edgeCaseMaxRowHeight","widowLayoutStyle","isBreakoutRow","items","height","prototype","addItem","itemData","previousRowWidthWithoutSpacing","previousAspectRatio","previousTargetAspectRatio","newItems","concat","rowWidthWithoutSpacing","newAspectRatio","reduce","sum","item","aspectRatio","targetAspectRatio","push","completeLayout","Object","assign","Math","abs","isLayoutComplete","newHeight","clampedToNativeRatio","clampedHeight","errorWidthPerItem","roundedCumulativeErrors","centerOffset","itemWidthSum","indexOf","max","min","forEach","map","i","round","forceComplete","fitToWidth","rowHeight","getItems","Row","require$$0","createNewRow","layoutConfig","layoutData","fullWidthBreakoutRowCadence","_rows","_containerHeight","containerPadding","containerWidth","right","boxSpacing","horizontal","rightToLeft","addRow","row","_layoutItems","vertical","lib","input","config","defaults","maxNumRows","Number","POSITIVE_INFINITY","forceAspectRatio","showWidows","isNaN","parseFloat","bottom","_awakeItems","_inViewportItems","_leadingOrphans","_trailingOrphans","_orphans","_widowCount","itemLayoutData","itemAdded","currentRow","nextToLastRowHeight","laidOutItems","forcedAspectRatio","some","Error","containerHeight","widowCount","boxes","computeLayout","getImgDimensions","img","cb","interval","hasSize","addedListeners","onHasSize","naturalWidth","naturalHeight","clearInterval","removeListeners","onLoaded","onError","checkSize","removeEventListener","addEventListener","setInterval","fjGalleryList","updateFjGallery","resize","document","readyState","capture","once","passive","instanceID","FJGallery","constructor","container","userOptions","$container","images","itemSelector","imageSelector","gutter","rowHeightTolerance","maxRowsCount","lastRow","transitionDuration","calculateItemsHeight","resizeDebounce","isRtl","css","onInit","onDestroy","onAppendImages","onBeforeJustify","onJustify","dataOptions","dataset","pureDataOptions","keys","key","loweCaseOption","substr","toLowerCase","pureOptions","debounce","justify","bind","init","el","styles","getComputedStyle","getPropertyValue","style","applyTransition","$item","properties","onTransitionEnd","join","addToFjGalleryList","removeFromFjGalleryList","splice","appendImages","querySelectorAll","call","$images","jQuery","get","fjGalleryImage","querySelector","$image","data","getAttribute","loadSizes","dimensions","justifyArray","justifyCount","justifiedOptions","getBoundingClientRect","justifiedData","justifiedLayout","lastItemData","gapSize","boxData","additionalTopOffset","rowsMaxHeight","imgI","pop","display","position","transform","rect","updateOptions","destroy","transition","val","fjGallery","HTMLElement","nodeType","nodeName","len","ret","k","$","oldInit","oldDestroy","removeData","$Plugin","unshift","res","old$Plugin","noConflict"],"mappings":"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"} \ No newline at end of file diff --git a/assets/vendor/iframe-resizer/js/iframeResizer.contentWindow.map b/assets/vendor/iframe-resizer/js/iframeResizer.contentWindow.map new file mode 100644 index 00000000..1685c65f --- /dev/null +++ b/assets/vendor/iframe-resizer/js/iframeResizer.contentWindow.map @@ -0,0 +1 @@ +{"version":3,"file":"iframeResizer.contentWindow.min.js","sources":["iframeResizer.contentWindow.js"],"names":["undefined","window","autoResize","bodyBackground","bodyMargin","bodyMarginStr","bodyObserver","bodyPadding","calculateWidth","doubleEventList","resize","click","eventCancelTimer","firstRun","height","heightCalcModeDefault","heightCalcMode","initLock","initMsg","inPageLinks","interval","intervalTimer","logging","mouseEvents","msgID","msgIdLen","length","myID","resetRequiredMethods","max","min","bodyScroll","documentElementScroll","resizeFrom","sendPermit","target","parent","targetOriginDefault","tolerance","triggerLocked","triggerLockedTimer","throttledTimer","width","widthCalcModeDefault","widthCalcMode","win","onMessage","warn","onReady","onPageInfo","customCalcMethods","document","documentElement","offsetHeight","body","scrollWidth","eventHandlersByName","passiveSupported","options","Object","create","passive","get","addEventListener","noop","removeEventListener","error","func","context","args","result","timeout","previous","getHeight","bodyOffset","getComputedStyle","offset","scrollHeight","custom","documentElementOffset","Math","apply","getAllMeasurements","grow","lowestElement","getMaxElement","getAllElements","taggedElement","getTaggedElements","getWidth","offsetWidth","scroll","rightMostElement","sizeIFrameThrottled","sizeIFrame","now","Date","remaining","this","arguments","clearTimeout","setTimeout","later","event","processRequestFromParent","init","data","source","reset","log","triggerReset","sendSize","moveToAnchor","findTarget","getData","inPageLink","pageInfo","msgBody","JSON","parse","message","getMessageType","split","slice","indexOf","isInitMsg","true","false","callFromParent","messageType","module","exports","jQuery","prototype","chkLateLoaded","el","evt","capitalizeFirstLetter","string","charAt","toUpperCase","formatLogMsg","msg","console","strBool","str","setupCustomCalcMethods","calcMode","calcFunc","Number","enable","location","href","iFrameResizer","constructor","stringify","keys","forEach","depricate","targetOrigin","heightCalculationMethod","widthCalculationMethod","sendMouse","e","sendMsg","type","screenY","screenX","addMouseListener","name","setBodyStyle","attr","value","clearFix","createElement","style","clear","display","appendChild","checkHeightMode","checkWidthMode","parentIFrame","startEventListeners","manageEventListeners","disconnect","clearInterval","close","getId","getPageInfo","callback","hash","resetIFrame","scrollTo","x","y","scrollToOffset","sendMessage","setHeightCalculationMethod","setWidthCalculationMethod","setTargetOrigin","size","customHeight","customWidth","getElementPosition","elPosition","getBoundingClientRect","pagePosition","pageXOffset","scrollLeft","pageYOffset","scrollTop","parseInt","left","top","hashData","decodeURIComponent","getElementById","getElementsByName","jumpPosition","checkLocationHash","bindAnchors","Array","call","querySelectorAll","getAttribute","preventDefault","enableInPageLinks","key","splitName","manageTriggerEvent","listener","add","eventName","handleEvent","eventType","remove","eventNames","map","method","checkCalcMode","calcModeDefault","modes","forceIntervalTimer","MutationObserver","WebKitMutationObserver","initInterval","addImageLoadListners","mutation","addImageLoadListener","element","complete","src","imageLoaded","imageError","elements","push","attributeName","removeImageLoadListener","splice","imageEventTriggered","typeDesc","mutationObserved","mutations","observer","querySelector","observe","attributes","attributeOldValue","characterData","characterDataOldValue","childList","subtree","setInterval","abs","prop","retVal","defaultView","side","elVal","elementsLength","maxVal","Side","timer","i","dimensions","tag","triggerEvent","triggerEventDesc","checkDownSizing","checkTolarance","a","b","currentHeight","currentWidth","lockTrigger","resetPage","hcm","postMessage","readyState"],"mappings":"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"} \ No newline at end of file diff --git a/assets/vendor/iframe-resizer/js/iframeResizer.contentWindow.min.js b/assets/vendor/iframe-resizer/js/iframeResizer.contentWindow.min.js new file mode 100644 index 00000000..bb3b1c80 --- /dev/null +++ b/assets/vendor/iframe-resizer/js/iframeResizer.contentWindow.min.js @@ -0,0 +1,9 @@ +/*! iFrame Resizer (iframeSizer.contentWindow.min.js) - v4.3.7 - 2023-09-14 + * Desc: Include this file in any page being loaded into an iframe + * to force the iframe to resize to the content size. + * Requires: iframeResizer.min.js on host page. + * Copyright: (c) 2023 David J. Bradshaw - dave@bradshaw.net + * License: MIT + */ +!function(a){if("undefined"!=typeof window){var r=!0,P="",u=0,c="",s=null,D="",d=!1,j={resize:1,click:1},l=128,q=!0,f=1,n="bodyOffset",m=n,H=!0,W="",h={},g=32,B=null,p=!1,v=!1,y="[iFrameSizer]",J=y.length,w="",U={max:1,min:1,bodyScroll:1,documentElementScroll:1},b="child",V=!0,X=window.parent,T="*",E=0,i=!1,Y=null,O=16,S=1,K="scroll",M=K,Q=window,G=function(){x("onMessage function not defined")},Z=function(){},$=function(){},_={height:function(){return x("Custom height calculation function not defined"),document.documentElement.offsetHeight},width:function(){return x("Custom width calculation function not defined"),document.body.scrollWidth}},ee={},te=!1;try{var ne=Object.create({},{passive:{get:function(){te=!0}}});window.addEventListener("test",ae,ne),window.removeEventListener("test",ae,ne)}catch(e){}var oe,o,I,ie,N,A,C={bodyOffset:function(){return document.body.offsetHeight+ye("marginTop")+ye("marginBottom")},offset:function(){return C.bodyOffset()},bodyScroll:function(){return document.body.scrollHeight},custom:function(){return _.height()},documentElementOffset:function(){return document.documentElement.offsetHeight},documentElementScroll:function(){return document.documentElement.scrollHeight},max:function(){return Math.max.apply(null,e(C))},min:function(){return Math.min.apply(null,e(C))},grow:function(){return C.max()},lowestElement:function(){return Math.max(C.bodyOffset()||C.documentElementOffset(),we("bottom",Te()))},taggedElement:function(){return be("bottom","data-iframe-height")}},z={bodyScroll:function(){return document.body.scrollWidth},bodyOffset:function(){return document.body.offsetWidth},custom:function(){return _.width()},documentElementScroll:function(){return document.documentElement.scrollWidth},documentElementOffset:function(){return document.documentElement.offsetWidth},scroll:function(){return Math.max(z.bodyScroll(),z.documentElementScroll())},max:function(){return Math.max.apply(null,e(z))},min:function(){return Math.min.apply(null,e(z))},rightMostElement:function(){return we("right",Te())},taggedElement:function(){return be("right","data-iframe-width")}},re=(oe=Ee,N=null,A=0,function(){var e=Date.now(),t=O-(e-(A=A||e));return o=this,I=arguments,t<=0||Ok[r]["max"+e])throw new Error("Value for min"+e+" can not be greater than max"+e)}}function h(e,n){null===i&&(i=setTimeout(function(){i=null,e()},n))}function e(){"hidden"!==document.visibilityState&&(O("document","Trigger event: Visibility change"),h(function(){b("Tab Visible","resize")},16))}function b(i,t){Object.keys(k).forEach(function(e){var n;k[n=e]&&"parent"===k[n].resizeFrom&&k[n].autoResize&&!k[n].firstRun&&A(i,t,k[e].iframe,e)})}function y(){F(window,"message",w),F(window,"resize",function(){var e;O("window","Trigger event: "+(e="resize")),h(function(){b("Window "+e,"resize")},16)}),F(document,"visibilitychange",e),F(document,"-webkit-visibilitychange",e)}function n(){function t(e,n){if(n){if(!n.tagName)throw new TypeError("Object is not a valid DOM element");if("IFRAME"!==n.tagName.toUpperCase())throw new TypeError("Expected + + unset_option_prefix( $settings['saved_id'] ) ) . '"]' ); ?> + +
+ plugin_path . 'classes/3rd/plugins/class-elementor-widget.php'; + + \Elementor\Plugin::instance()->widgets_manager->register( new Visual_Portfolio_3rd_Elementor_Widget() ); + } + + /** + * Fix Elementor lightbox conflict. + * + * @see https://github.com/nk-crew/visual-portfolio/issues/103 + */ + public function maybe_fix_elementor_lightbox_conflict() { + if ( ! defined( 'ELEMENTOR_VERSION' ) ) { + return; + } + + // We should check it, as we are trying to inject this script twice. + if ( $this->lightbox_fix_added ) { + return; + } + + if ( ! wp_script_is( 'jquery', 'enqueued' ) ) { + return; + } + + $this->lightbox_fix_added = true; + + ?> + + = 3.11.0) added support for the latest version of Swiper library and changed their old fallback Swiper library. This is why we have to include Swiper's assets instead of ours. + */ + public function fix_elementor_swiper_assets() { + if ( ! class_exists( '\Elementor\Plugin' ) || ! isset( \Elementor\Plugin::$instance->experiments ) || ! defined( 'ELEMENTOR_URL' ) ) { + return; + } + + global $wp_scripts; + global $wp_styles; + + // Since the Elementor assets methods like `get_css_assets_url` are protected + // and we can't use it directly, we prepare assets URLs manually. + $e_swiper_latest = \Elementor\Plugin::$instance->experiments->is_feature_active( 'e_swiper_latest' ); + $e_swiper_asset_path = $e_swiper_latest ? 'assets/lib/swiper/v8/' : 'assets/lib/swiper/'; + $e_swiper_version = $e_swiper_latest ? '8.4.5' : '5.3.6'; + $e_file_name = 'swiper'; + $e_assets_base_url = ELEMENTOR_URL; + $e_assets_relative_url = 'assets/'; + $e_css_relative_url = $e_swiper_asset_path . 'css/'; + $e_js_relative_url = $e_swiper_asset_path; + + if ( ! $e_css_relative_url ) { + $e_css_relative_url = $e_assets_relative_url . 'css/'; + } + if ( ! $e_js_relative_url ) { + $e_js_relative_url = $e_assets_relative_url; + } + + $e_swiper_css_url = $e_assets_base_url . $e_css_relative_url . $e_file_name . '.css'; + $e_swiper_js_url = $e_assets_base_url . $e_js_relative_url . $e_file_name . '.js'; + + // Elementor < 3.11.0 does not have a Swiper CSS file. + if ( version_compare( ELEMENTOR_VERSION, '3.11.0', '<' ) ) { + $e_swiper_css_url = visual_portfolio()->plugin_url . 'assets/vendor/swiper-5-3-6/swiper.min.css'; + } + + // Since we include Swiper library ourselves, here we have to override assets URLs. + if ( isset( $wp_scripts->registered['swiper']->src ) ) { + $wp_scripts->registered['swiper']->src = $e_swiper_js_url; + $wp_scripts->registered['swiper']->ver = $e_swiper_version; + } + if ( isset( $wp_styles->registered['swiper']->src ) ) { + $wp_styles->registered['swiper']->src = $e_swiper_css_url; + $wp_styles->registered['swiper']->ver = $e_swiper_version; + } + } +} + +new Visual_Portfolio_3rd_Elementor(); diff --git a/classes/3rd/plugins/class-ewww-image-optimizer.php b/classes/3rd/plugins/class-ewww-image-optimizer.php new file mode 100644 index 00000000..1eeeb092 --- /dev/null +++ b/classes/3rd/plugins/class-ewww-image-optimizer.php @@ -0,0 +1,30 @@ +registered[ $jetpack_ll_handler ] ) ) { + return; + } + + Visual_Portfolio_Assets::register_script( 'visual-portfolio-3rd-jetpack', 'assets/js/3rd/plugin-jetpack.min', array( 'jquery' ) ); + + $wp_scripts->registered[ $jetpack_ll_handler ]->deps[] = 'visual-portfolio-3rd-jetpack'; + } + + /** + * Skip Jetpack lazy loading when data-src attribute added to image. + * + * @param boolean $return skip lazy Jetpack. + * @param array $attributes image attributes. + * + * @return boolean + */ + public function jetpack_lazy_images_skip_image_with_attributes( $return, $attributes ) { + return isset( $attributes['data-src'] ); + } +} + +new Visual_Portfolio_3rd_Jetpack(); diff --git a/classes/3rd/plugins/class-lazy-loading-responsive-images.php b/classes/3rd/plugins/class-lazy-loading-responsive-images.php new file mode 100644 index 00000000..9e6c78ae --- /dev/null +++ b/classes/3rd/plugins/class-lazy-loading-responsive-images.php @@ -0,0 +1,30 @@ + $value ) { + if ( 'vp_page' === $key || 'vp_filter' === $key || 'vp_sort' === $key || 'vp_search' === $key ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $canonical = add_query_arg( array_map( 'sanitize_text_field', wp_unslash( array( $key => $value ) ) ), $canonical ); + } + } + return $canonical; + } +} +new Visual_Portfolio_3rd_Rank_Math(); diff --git a/src/classes/3rd/plugins/class-sg-cachepress.php b/classes/3rd/plugins/class-sg-cachepress.php similarity index 60% rename from src/classes/3rd/plugins/class-sg-cachepress.php rename to classes/3rd/plugins/class-sg-cachepress.php index 762eda82..68f1071d 100644 --- a/src/classes/3rd/plugins/class-sg-cachepress.php +++ b/classes/3rd/plugins/class-sg-cachepress.php @@ -2,47 +2,47 @@ /** * SiteGround Optimizer Plugin. * - * @package @@plugin_name + * @package visual-portfolio */ if ( ! defined( 'ABSPATH' ) ) { - exit; + exit; } /** * Class Visual_Portfolio_3rd_SG_Cachepress */ class Visual_Portfolio_3rd_SG_Cachepress { - /** - * Visual_Portfolio_3rd_SG_Cachepress constructor. - */ - public function __construct() { - if ( ! class_exists( '\SiteGround_Optimizer\Options\Options' ) || ! \SiteGround_Optimizer\Options\Options::is_enabled( 'siteground_optimizer_lazyload_images' ) ) { - return; - } + /** + * Visual_Portfolio_3rd_SG_Cachepress constructor. + */ + public function __construct() { + if ( ! class_exists( '\SiteGround_Optimizer\Options\Options' ) || ! \SiteGround_Optimizer\Options\Options::is_enabled( 'siteground_optimizer_lazyload_images' ) ) { + return; + } - // Disable our lazyload if SiteGround Optimizer lazyload used. - add_filter( 'vpf_images_lazyload', '__return_false' ); - add_action( 'wp_enqueue_scripts', array( $this, 'wp_enqueue_scripts' ), 20 ); - } + // Disable our lazyload if SiteGround Optimizer lazyload used. + add_filter( 'vpf_images_lazyload', '__return_false' ); + add_action( 'wp_enqueue_scripts', array( $this, 'wp_enqueue_scripts' ), 20 ); + } - /** - * SG lazy loading breaks our scripts which calculate size of the images, - * we need to resolve it in a hacky way by changing src placeholder. - */ - public function wp_enqueue_scripts() { - $wp_scripts = wp_scripts(); - $sg_ll_handler = 'siteground-optimizer-lazy-sizes-js'; + /** + * SG lazy loading breaks our scripts which calculate size of the images, + * we need to resolve it in a hacky way by changing src placeholder. + */ + public function wp_enqueue_scripts() { + $wp_scripts = wp_scripts(); + $sg_ll_handler = 'siteground-optimizer-lazy-sizes-js'; - if ( ! isset( $wp_scripts->registered[ $sg_ll_handler ] ) ) { - return; - } + if ( ! isset( $wp_scripts->registered[ $sg_ll_handler ] ) ) { + return; + } - $wp_scripts->registered[ $sg_ll_handler ]->deps[] = 'jquery'; + $wp_scripts->registered[ $sg_ll_handler ]->deps[] = 'jquery'; - wp_add_inline_script( - $sg_ll_handler, - '(function($){ + wp_add_inline_script( + $sg_ll_handler, + '(function($){ if ( !$ ) { return; } @@ -79,9 +79,9 @@ function maybeFixSGSrc( $images ) { maybeFixSGSrc( $items ); } ); }(window.jQuery));', - 'before' - ); - } + 'before' + ); + } } new Visual_Portfolio_3rd_SG_Cachepress(); diff --git a/classes/3rd/plugins/class-tinymce.php b/classes/3rd/plugins/class-tinymce.php new file mode 100644 index 00000000..ba949247 --- /dev/null +++ b/classes/3rd/plugins/class-tinymce.php @@ -0,0 +1,107 @@ +init_hooks(); + } + + /** + * Hooks. + */ + public function init_hooks() { + add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) ); + add_action( 'admin_head', array( $this, 'admin_head' ) ); + } + + /** + * Admin Head Action. + */ + public function admin_head() { + if ( current_user_can( 'edit_posts' ) && current_user_can( 'edit_pages' ) ) { + add_filter( 'mce_external_plugins', array( $this, 'mce_external_plugins' ) ); + add_filter( 'mce_buttons', array( $this, 'mce_buttons' ) ); + } + } + + /** + * Enqueue admin scripts + * + * @param string $page - page name. + */ + public function admin_enqueue_scripts( $page ) { + if ( 'post.php' === $page || 'post-new.php' === $page ) { + // add tiny mce data. + $data_tiny_mce = array( + 'plugin_name' => visual_portfolio()->plugin_name, + 'layouts' => array(), + ); + + // get all visual-portfolio post types. + // Don't use WP_Query on the admin side https://core.trac.wordpress.org/ticket/18408 . + $vp_query = get_posts( + array( + 'post_type' => 'vp_lists', + 'posts_per_page' => -1, + 'paged' => -1, + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + ) + ); + foreach ( $vp_query as $post ) { + $data_tiny_mce['layouts'][] = array( + 'id' => $post->ID, + 'title' => '#' . $post->ID . ' - ' . $post->post_title, + ); + } + + // return if no data. + if ( empty( $data_tiny_mce['layouts'] ) ) { + return; + } + + Visual_Portfolio_Assets::enqueue_script( 'visual-portfolio-tinymce-localize', 'build/assets/admin/js/mce-localize' ); + wp_localize_script( 'visual-portfolio-tinymce-localize', 'VPTinyMCEData', $data_tiny_mce ); + } + } + + /** + * Add script for button + * + * @param array $plugins - available plugins. + * + * @return mixed + */ + public function mce_external_plugins( $plugins ) { + $plugins['visual_portfolio'] = visual_portfolio()->plugin_url . 'build/assets/admin/js/mce-dropdown.js'; + return $plugins; + } + + /** + * Add dropdown button to tinymce + * + * @param array $buttons - available buttons. + * + * @return mixed + */ + public function mce_buttons( $buttons ) { + array_push( $buttons, 'visual_portfolio' ); + return $buttons; + } +} + +new Visual_Portfolio_3rd_TinyMCE(); diff --git a/classes/3rd/plugins/class-vc.php b/classes/3rd/plugins/class-vc.php new file mode 100644 index 00000000..da246222 --- /dev/null +++ b/classes/3rd/plugins/class-vc.php @@ -0,0 +1,99 @@ +init_hooks(); + } + + /** + * Hooks. + */ + public function init_hooks() { + add_action( 'init', array( $this, 'add_shortcode' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) ); + } + + /** + * Enqueue script for frontend VC + * + * @param object $page - page object. + */ + public function admin_enqueue_scripts( $page ) { + if ( 'post.php' === $page || 'post-new.php' === $page ) { + Visual_Portfolio_Assets::enqueue_script( 'visual-portfolio-vc-frontend', 'build/assets/admin/js/vc-frontend', array( 'jquery' ) ); + } + } + + /** + * Add shortcode to the visual composer + */ + public function add_shortcode() { + if ( function_exists( 'vc_map' ) ) { + // get all visual-portfolio post types. + // Don't use WP_Query on the admin side https://core.trac.wordpress.org/ticket/18408 . + $vp_query = get_posts( + array( + 'post_type' => 'vp_lists', + 'posts_per_page' => -1, + 'paged' => -1, + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + ) + ); + + $data_vc = array(); + foreach ( $vp_query as $post ) { + $data_vc[] = array( $post->ID, '#' . $post->ID . ' - ' . $post->post_title ); + } + + vc_map( + array( + 'name' => visual_portfolio()->plugin_name, + 'base' => 'visual_portfolio', + 'controls' => 'full', + 'icon' => 'icon-visual-portfolio', + 'params' => array( + array( + 'type' => 'dropdown', + 'heading' => esc_html__( 'Select Layout', 'visual-portfolio' ), + 'param_name' => 'id', + 'value' => $data_vc, + 'description' => '', + 'admin_label' => true, + ), + array( + 'type' => 'textfield', + 'heading' => esc_html__( 'Custom Classes', 'visual-portfolio' ), + 'param_name' => 'class', + 'value' => '', + 'description' => '', + ), + array( + 'type' => 'css_editor', + 'heading' => esc_html__( 'CSS', 'visual-portfolio' ), + 'param_name' => 'vc_css', + 'group' => esc_html__( 'Design Options', 'visual-portfolio' ), + ), + ), + ) + ); + } + } +} + +new Visual_Portfolio_3rd_VC(); diff --git a/classes/3rd/plugins/class-wp-rocket.php b/classes/3rd/plugins/class-wp-rocket.php new file mode 100644 index 00000000..3d462671 --- /dev/null +++ b/classes/3rd/plugins/class-wp-rocket.php @@ -0,0 +1,40 @@ +archive_page = Visual_Portfolio_Settings::get_option( 'portfolio_archive_page', 'vp_general' ); + if ( isset( $this->archive_page ) && ! empty( $this->archive_page ) ) { + add_action( 'wp_insert_post', array( $this, 'set_archive_meta' ), 10, 1 ); + } + } + + /** + * Set Archive Meta. + * + * @param int $post_id - Post ID. + * @return void + */ + public function set_archive_meta( $post_id ) { + $translate_page_id = self::get_object_id( $this->archive_page ); + if ( $translate_page_id === $post_id ) { + visual_portfolio()->defer_flush_rewrite_rules(); + + update_post_meta( (int) $post_id, '_vp_post_type_mapped', 'portfolio' ); + } + } + + /** + * Get wpml object ID. + * + * @param int $post_id - Post ID. + * @return int + */ + public static function get_object_id( $post_id ) { + if ( class_exists( 'SitePress' ) ) { + return apply_filters( 'wpml_object_id', $post_id, 'page', true ); + } + return $post_id; + } + + /** + * Make Control Translatable. + * https://wpml.org/forums/topic/unable-to-save-custom-field-translation-settings-when-acf-ml-is-installed/ + * + * @param array $controls - controls array. + * + * @return array + */ + public function make_control_translatable( $controls ) { + global $iclTranslationManagement; + + $allow_save = false; + + // Prepare Saved Layouts meta fields. + foreach ( $controls as $control ) { + $name = 'vp_' . $control['name']; + + // Create initial arrays. + if ( ! isset( $iclTranslationManagement->settings['custom_fields_translation'] ) ) { + $iclTranslationManagement->settings['custom_fields_translation'] = array(); + } + if ( ! isset( $iclTranslationManagement->settings['custom_fields_readonly_config'] ) ) { + $iclTranslationManagement->settings['custom_fields_readonly_config'] = array(); + } + + // Add fields translation. + if ( ! isset( $iclTranslationManagement->settings['custom_fields_translation'][ $name ] ) ) { + $iclTranslationManagement->settings['custom_fields_translation'][ $name ] = $control['wpml'] ? WPML_TRANSLATE_CUSTOM_FIELD : WPML_COPY_CUSTOM_FIELD; + + $allow_save = true; + } + + // Add fields read only. + if ( ! in_array( $name, $iclTranslationManagement->settings['custom_fields_readonly_config'], true ) ) { + $iclTranslationManagement->settings['custom_fields_readonly_config'][] = $name; + + $allow_save = true; + } + } + + // Images meta array. + if ( ! isset( $iclTranslationManagement->settings['custom_fields_attributes_whitelist']['vp_images'] ) ) { + $iclTranslationManagement->settings['custom_fields_attributes_whitelist']['vp_images'] = array( + '*' => array( + 'title' => array(), + 'description' => array(), + 'author' => array(), + 'categories' => array(), + ), + ); + + $allow_save = true; + } + + if ( $allow_save ) { + $iclTranslationManagement->save_settings(); + } + + return $controls; + } + + /** + * Convert custom taxonomies to the current language. + * + * @param array $options - query options. + * + * @return array + */ + public function prepare_custom_query_taxonomies( $options ) { + if ( isset( $options['posts_taxonomies'] ) && ! empty( $options['posts_taxonomies'] ) ) { + foreach ( $options['posts_taxonomies'] as $k => $taxonomy ) { + $taxonomy_data = get_term( $taxonomy ); + + if ( isset( $taxonomy_data->taxonomy ) ) { + $options['posts_taxonomies'][ $k ] = apply_filters( 'wpml_object_id', $taxonomy, $taxonomy_data->taxonomy, true ); + } + } + } + + return $options; + } +} + +new Visual_Portfolio_3rd_WPML(); diff --git a/classes/3rd/plugins/class-yoast.php b/classes/3rd/plugins/class-yoast.php new file mode 100644 index 00000000..7cb91f9d --- /dev/null +++ b/classes/3rd/plugins/class-yoast.php @@ -0,0 +1,41 @@ + $value ) { + if ( 'vp_page' === $key || 'vp_filter' === $key || 'vp_sort' === $key || 'vp_search' === $key ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $canonical = add_query_arg( array_map( 'sanitize_text_field', wp_unslash( array( $key => $value ) ) ), $canonical ); + } + } + return $canonical; + } +} +new Visual_Portfolio_3rd_Yoast(); diff --git a/classes/3rd/themes/class-avada.php b/classes/3rd/themes/class-avada.php new file mode 100644 index 00000000..07db037c --- /dev/null +++ b/classes/3rd/themes/class-avada.php @@ -0,0 +1,37 @@ +lazy_loading_option_cache ) { + $enfold_option = $this->lazy_loading_option_cache; + } elseif ( function_exists( 'avia_get_option' ) ) { + $enfold_option = avia_get_option( 'lazy_loading', '' ); + } + + if ( null === $this->lazy_loading_option_cache ) { + $this->lazy_loading_option_cache = $enfold_option; + } + + if ( '' === $enfold_option ) { + return false; + } + + return $return; + } +} + +new Visual_Portfolio_3rd_Enfold(); diff --git a/classes/3rd/themes/class-thrive-architect.php b/classes/3rd/themes/class-thrive-architect.php new file mode 100644 index 00000000..dcd40d11 --- /dev/null +++ b/classes/3rd/themes/class-thrive-architect.php @@ -0,0 +1,36 @@ +plugin_basename, array( $this, 'add_go_pro_link_plugins_page' ) ); + add_action( 'admin_menu', array( $this, 'pro_admin_menu' ), 12 ); + add_action( 'admin_menu', array( $this, 'add_menu_if_portfolio_post_type_unregistered' ), 7 ); + + // Add Pro links to menu. + add_action( 'admin_menu', array( $this, 'add_go_pro_url' ), 100 ); + + // register controls. + add_action( 'init', array( $this, 'register_controls' ), 9 ); + add_filter( 'vpf_extend_layouts', array( $this, 'add_default_layouts' ), 9 ); + add_filter( 'vpf_extend_items_styles', array( $this, 'add_default_items_styles' ), 9 ); + + // ajax actions. + add_action( 'wp_ajax_vp_find_oembed', array( $this, 'ajax_find_oembed' ) ); + } + + /** + * Add hight level Portfolio admin menu if portfolio post type unregistered. + * + * @return void + */ + public function add_menu_if_portfolio_post_type_unregistered() { + if ( ! Visual_Portfolio_Custom_Post_Type::portfolio_post_type_is_registered() ) { + add_menu_page( + visual_portfolio()->plugin_name, + visual_portfolio()->plugin_name, + 'manage_options', + 'visual-portfolio-settings', + array( 'Visual_Portfolio_Settings', 'print_settings_page' ), + 'dashicons-visual-portfolio', + 25 + ); + } + } + + /** + * Enqueue styles and scripts + */ + public function admin_enqueue_scripts() { + $data_init = array( + 'nonce' => wp_create_nonce( 'vp-ajax-nonce' ), + ); + + Visual_Portfolio_Assets::enqueue_script( 'visual-portfolio-admin', 'build/assets/admin/js/script', array( 'jquery', 'wp-data', 'wp-compose' ) ); + wp_localize_script( 'visual-portfolio-admin', 'VPAdminVariables', $data_init ); + Visual_Portfolio_Assets::enqueue_style( 'visual-portfolio-admin', 'build/assets/admin/css/style' ); + wp_style_add_data( 'visual-portfolio-admin', 'rtl', 'replace' ); + wp_style_add_data( 'visual-portfolio-admin', 'suffix', '.min' ); + } + + /** + * Enqueue styles and scripts on saved layouts editor. + */ + public function saved_layouts_editor_enqueue_scripts() { + $data_init = array( + 'nonce' => wp_create_nonce( 'vp-ajax-nonce' ), + ); + + if ( 'vp_lists' === get_post_type() ) { + Visual_Portfolio_Assets::enqueue_script( 'visual-portfolio-saved-layouts', 'build/gutenberg/layouts-editor-script', array( 'jquery' ) ); + Visual_Portfolio_Assets::enqueue_style( 'visual-portfolio-saved-layouts', 'build/gutenberg/layouts-editor-style' ); + wp_style_add_data( 'visual-portfolio-saved-layouts', 'rtl', 'replace' ); + wp_style_add_data( 'visual-portfolio-saved-layouts', 'suffix', '.min' ); + + $block_data = Visual_Portfolio_Get::get_options( array( 'id' => get_the_ID() ) ); + + wp_localize_script( + 'visual-portfolio-saved-layouts', + 'VPSavedLayoutVariables', + array( + 'nonce' => $data_init['nonce'], + 'data' => $block_data, + ) + ); + } + } + + /** + * Admin footer text. + * + * @param string $text The admin footer text. + * + * @return string + */ + public function admin_footer_text( $text ) { + if ( ! function_exists( 'get_current_screen' ) ) { + return $text; + } + + $screen = get_current_screen(); + + // Determine if the current page being viewed is "Visual Portfolio" related. + if ( isset( $screen->post_type ) && ( 'portfolio' === $screen->post_type || 'vp_lists' === $screen->post_type || 'vp_proofing' === $screen->post_type ) ) { + $footer_text = esc_attr__( 'and', 'visual-portfolio' ) . ' ' . visual_portfolio()->plugin_name . ''; + + // Use RegExp to append "Visual Portfolio" after the element allowing translations to read correctly. + return preg_replace( '/()/', '$1 ' . $footer_text, $text, 1 ); + } + + return $text; + } + + /** + * Admin navigation. + */ + public function in_admin_header() { + if ( ! function_exists( 'get_current_screen' ) ) { + return; + } + + $screen = get_current_screen(); + + $is_settings = ( isset( $screen->post_type ) && isset( $screen->id ) && 'toplevel_page_visual-portfolio-settings' === $screen->id ) ? true : false; + + // Determine if the current page being viewed is "Lazy Blocks" related. + if ( + ! isset( $screen->post_type ) || + ( + 'portfolio' !== $screen->post_type && + 'vp_lists' !== $screen->post_type && + 'vp_proofing' !== $screen->post_type && + ! $is_settings + ) || + ( isset( $screen->is_block_editor ) && $screen->is_block_editor() ) + ) { + return; + } + + global $submenu, $submenu_file, $plugin_page; + + $parent_slug = Visual_Portfolio_Custom_Post_Type::get_menu_slug(); + $tabs = array(); + + // Generate array of navigation items. + if ( isset( $submenu[ $parent_slug ] ) ) { + foreach ( $submenu[ $parent_slug ] as $sub_item ) { + + // Check user can access page. + if ( ! current_user_can( $sub_item[1] ) ) { + continue; + } + + // Ignore "Add New". + if ( 'post-new.php?post_type=portfolio' === $sub_item[2] ) { + continue; + } + + // Define tab. + $tab = array( + 'text' => $sub_item[0], + 'url' => $sub_item[2], + ); + + // Convert submenu slug "test" to "$parent_slug&page=test". + if ( ! strpos( $sub_item[2], '.php' ) && 0 !== strpos( $sub_item[2], 'https://' ) ) { + $tab['url'] = add_query_arg( array( 'page' => $sub_item[2] ), $parent_slug ); + } + + // Fixed Settings tab url if Portfolio Post Type disabled. + if ( 'visual-portfolio-settings' === $parent_slug && 'visual-portfolio-settings' === $sub_item[2] ) { + $tab['url'] = 'admin.php?page=' . $parent_slug; + } + + // Detect active state. + if ( $submenu_file === $sub_item[2] || $plugin_page === $sub_item[2] ) { + $tab['is_active'] = true; + } + + $tabs[] = $tab; + } + } + + // Bail early if set to false. + if ( false === $tabs ) { + return; + } + + ?> +
+

+ + plugin_name ); ?> +

+ %s
', + ! empty( $tab['is_active'] ) ? ' is-active' : '', + esc_url( $tab['url'] ), + wp_kses_post( $tab['text'] ) + ); + } + ?> +
+ 'plugins_list' ) ) . '">' . esc_html__( 'Go Pro', 'visual-portfolio' ) . '', + ) + ); + } + + /** + * Get URL to main site with UTM tags. + * + * @param array $args - Arguments of link. + * @return string + */ + public static function get_plugin_site_url( $args = array() ) { + $args = array_merge( + array( + 'sub_path' => 'pricing', + 'utm_source' => 'plugin', + 'utm_medium' => 'admin_menu', + 'utm_campaign' => 'go_pro', + 'utm_content' => VISUAL_PORTFOLIO_VERSION, + ), + $args + ); + $url = 'https://visualportfolio.co/'; + $first_flag = true; + + if ( isset( $args['sub_path'] ) && ! empty( $args['sub_path'] ) ) { + $url .= $args['sub_path'] . '/'; + } + + foreach ( $args as $key => $value ) { + if ( 'sub_path' !== $key && ! empty( $value ) ) { + $url .= ( $first_flag ? '?' : '&' ); + $url .= $key . '=' . $value; + $first_flag = false; + } + } + + return $url; + } + + /** + * Register the admin settings menu Pro link. + * + * @return void + */ + public function pro_admin_menu() { + add_submenu_page( + Visual_Portfolio_Custom_Post_Type::get_menu_slug(), + '', + ' ' . esc_html__( 'Go Pro', 'visual-portfolio' ), + 'manage_options', + 'visual_portfolio_go_pro' + ); + } + + /** + * Add go pro links to admin menu. + * + * @return void + */ + public function add_go_pro_url() { + global $submenu; + $menu_slug = Visual_Portfolio_Custom_Post_Type::get_menu_slug(); + + if ( ! isset( $submenu[ $menu_slug ] ) ) { + return; + } + + $plugin_submenu = &$submenu[ $menu_slug ]; + + if ( is_array( $plugin_submenu ) && ! empty( $plugin_submenu ) ) { + foreach ( $plugin_submenu as $key => $submenu_item ) { + if ( 'visual_portfolio_go_pro' === $submenu_item[2] ) { + $plugin_submenu[ $key ][2] = self::get_plugin_site_url( array( 'utm_medium' => 'admin_menu' ) ); + } + } + } + } + + /** + * Add default layouts. + * + * @param array $layouts - layouts array. + * + * @return array + */ + public function add_default_layouts( $layouts ) { + return array_merge( + array( + // Tiles. + 'tiles' => array( + 'title' => esc_html__( 'Tiles', 'visual-portfolio' ), + 'icon' => '', + 'controls' => array( + /** + * Tile type: + * first parameter - is columns number + * the next is item sizes + * + * Example: + * 3|1,0.5|2,0.25| + * 3 columns in row + * First item 100% width and 50% height + * Second item 200% width and 25% height + */ + array( + 'type' => 'tiles_selector', + 'label' => esc_html__( 'Tiles Preview', 'visual-portfolio' ), + 'name' => 'type', + 'default' => '3|1,1|', + 'reload_iframe' => false, + 'options' => array_merge( + array( + array( + 'value' => '1|1,0.5|', + ), + array( + 'value' => '2|1,1|', + ), + array( + 'value' => '2|1,0.8|', + ), + array( + 'value' => '2|1,1.34|', + ), + array( + 'value' => '2|1,1.2|1,1.2|1,0.67|1,0.67|', + ), + array( + 'value' => '2|1,1.2|1,0.67|1,1.2|1,0.67|', + ), + array( + 'value' => '2|1,0.67|1,1|1,1|1,1|1,1|1,0.67|', + ), + array( + 'value' => '3|1,1|', + ), + array( + 'value' => '3|1,0.8|', + ), + array( + 'value' => '3|1,1.3|', + ), + array( + 'value' => '3|1,1|1,1|1,1|1,1.3|1,1.3|1,1.3|', + ), + array( + 'value' => '3|1,1|1,1|1,2|1,1|1,1|1,1|1,1|1,1|', + ), + array( + 'value' => '3|1,2|1,1|1,1|1,1|1,1|1,1|1,1|1,1|', + ), + array( + 'value' => '3|1,1|1,2|1,1|1,1|1,1|1,1|1,1|1,1|', + ), + array( + 'value' => '3|1,1|1,2|1,1|1,1|1,1|1,1|2,0.5|', + ), + array( + 'value' => '3|1,0.8|1,1.6|1,0.8|1,0.8|1,1.6|1,0.8|1,0.8|1,0.8|1,0.8|1,0.8|', + ), + array( + 'value' => '3|1,0.8|1,1.6|1,0.8|1,0.8|1,1.6|1,1.6|1,0.8|1,0.8|1,0.8|', + ), + array( + 'value' => '3|1,0.8|1,0.8|1,1.6|1,0.8|1,0.8|1,1.6|1,1.6|1,0.8|1,0.8|', + ), + array( + 'value' => '3|1,0.8|1,0.8|1,1.6|1,0.8|1,0.8|1,0.8|1,1.6|1,1.6|1,0.8|', + ), + array( + 'value' => '3|1,1|2,1|1,1|2,0.5|1,1|', + ), + array( + 'value' => '3|1,1|2,1|1,1|1,1|1,1|1,1|2,0.5|1,1|', + ), + array( + 'value' => '3|1,2|2,0.5|1,1|1,2|2,0.5|', + ), + array( + 'value' => '4|1,1|', + ), + array( + 'value' => '4|1,1|1,1.34|1,1|1,1.34|1,1.34|1,1.34|1,1|1,1|', + ), + array( + 'value' => '4|1,0.8|1,1|1,0.8|1,1|1,1|1,1|1,0.8|1,0.8|', + ), + array( + 'value' => '4|1,1|1,1|2,1|1,1|1,1|2,1|1,1|1,1|1,1|1,1|', + ), + array( + 'value' => '4|2,1|2,0.5|2,0.5|2,0.5|2,1|2,0.5|', + ), + ), + // phpcs:ignore Squiz.PHP.CommentedOutCode.Found, Squiz.Commenting.BlockComment.NoEmptyLineBefore + /* + * Example: + array( + array( + 'value' => '1|1,0.5|', + ), + array( + 'value' => '2|1,1|', + ), + ) + */ + apply_filters( 'vpf_extend_tiles', array() ) + ), + ), + ), + ), + + // Masonry. + 'masonry' => array( + 'title' => esc_html__( 'Masonry', 'visual-portfolio' ), + 'icon' => '', + 'controls' => array( + array( + 'type' => 'number', + 'label' => esc_html__( 'Columns', 'visual-portfolio' ), + 'name' => 'columns', + 'min' => 1, + 'max' => 5, + 'default' => 3, + 'reload_iframe' => false, + ), + array( + 'type' => 'aspect_ratio', + 'label' => esc_html__( 'Images Aspect Ratio', 'visual-portfolio' ), + 'name' => 'images_aspect_ratio', + 'default' => '', + 'reload_iframe' => false, + 'style' => array( + array( + 'element' => '.vp-portfolio__item-wrap .vp-portfolio__item-img-wrap::before', + 'property' => 'padding-top', + ), + ), + ), + ), + ), + + // Grid. + 'grid' => array( + 'title' => esc_html__( 'Grid', 'visual-portfolio' ), + 'icon' => '', + 'controls' => array( + array( + 'type' => 'number', + 'label' => esc_html__( 'Columns', 'visual-portfolio' ), + 'name' => 'columns', + 'min' => 1, + 'max' => 5, + 'default' => 3, + 'reload_iframe' => false, + ), + array( + 'type' => 'aspect_ratio', + 'label' => esc_html__( 'Images Aspect Ratio', 'visual-portfolio' ), + 'name' => 'images_aspect_ratio', + 'default' => '', + 'reload_iframe' => false, + 'style' => array( + array( + 'element' => '.vp-portfolio__item-wrap .vp-portfolio__item-img-wrap::before', + 'property' => 'padding-top', + ), + ), + ), + ), + ), + + // Justified. + 'justified' => array( + 'title' => esc_html__( 'Justified', 'visual-portfolio' ), + 'icon' => '', + 'controls' => array( + array( + 'type' => 'range', + 'label' => esc_html__( 'Row Height', 'visual-portfolio' ), + 'name' => 'row_height', + 'group' => 'justified_row_height', + 'min' => 100, + 'max' => 1000, + 'default' => 200, + 'reload_iframe' => false, + ), + array( + 'type' => 'range', + 'label' => esc_html__( 'Row Height Tolerance', 'visual-portfolio' ), + 'name' => 'row_height_tolerance', + 'group' => 'justified_row_height', + 'min' => 0, + 'max' => 1, + 'step' => 0.05, + 'default' => 0.25, + 'reload_iframe' => false, + ), + array( + 'type' => 'range', + 'label' => esc_html__( 'Max Rows Count', 'visual-portfolio' ), + 'description' => esc_html__( 'Limit the number of rows to display. 0 means - unlimited.', 'visual-portfolio' ), + 'name' => 'max_rows_count', + 'min' => 0, + 'max' => 50, + 'step' => 1, + 'default' => 0, + 'reload_iframe' => false, + ), + array( + 'type' => 'select', + 'label' => esc_html__( 'Last Row Align', 'visual-portfolio' ), + 'name' => 'last_row', + 'default' => 'left', + 'reload_iframe' => false, + 'options' => array( + 'left' => esc_html__( 'Left', 'visual-portfolio' ), + 'center' => esc_html__( 'Center', 'visual-portfolio' ), + 'right' => esc_html__( 'Right', 'visual-portfolio' ), + 'hide' => esc_html__( 'Hide', 'visual-portfolio' ), + ), + ), + ), + ), + + // Slider. + 'slider' => array( + 'title' => esc_html__( 'Slider', 'visual-portfolio' ), + 'icon' => '', + 'controls' => array( + array( + 'type' => 'icons_selector', + 'label' => esc_html__( 'Effect', 'visual-portfolio' ), + 'name' => 'effect', + 'default' => 'slide', + 'reload_iframe' => false, + 'options' => array( + 'slide' => array( + 'value' => 'slide', + 'title' => esc_html__( 'Slide', 'visual-portfolio' ), + 'icon' => '', + ), + 'coverflow' => array( + 'value' => 'coverflow', + 'title' => esc_html__( 'Coverflow', 'visual-portfolio' ), + 'icon' => '', + ), + 'fade' => array( + 'value' => 'fade', + 'title' => esc_html__( 'Fade', 'visual-portfolio' ), + 'icon' => '', + ), + ), + ), + array( + 'type' => 'range', + 'label' => esc_html__( 'Speed (in Seconds)', 'visual-portfolio' ), + 'name' => 'speed', + 'min' => 0, + 'max' => 5, + 'step' => 0.1, + 'default' => 0.3, + 'reload_iframe' => false, + ), + array( + 'type' => 'range', + 'label' => esc_html__( 'Autoplay (in Seconds)', 'visual-portfolio' ), + 'name' => 'autoplay', + 'min' => 0, + 'max' => 60, + 'step' => 0.2, + 'default' => 6, + 'reload_iframe' => false, + ), + array( + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Pause on Mouse Over', 'visual-portfolio' ), + 'name' => 'autoplay_hover_pause', + 'default' => false, + 'reload_iframe' => false, + 'condition' => array( + array( + 'control' => 'autoplay', + 'operator' => '>', + 'value' => 0, + ), + ), + ), + array( + 'type' => 'radio', + 'label' => esc_html__( 'Items Height', 'visual-portfolio' ), + 'name' => 'items_height_type', + 'group' => 'slider_items_height', + 'default' => 'dynamic', + 'options' => array( + 'auto' => esc_html__( 'Auto', 'visual-portfolio' ), + 'static' => esc_html__( 'Static (px)', 'visual-portfolio' ), + 'dynamic' => esc_html__( 'Dynamic (%)', 'visual-portfolio' ), + ), + ), + array( + 'type' => 'number', + 'name' => 'items_height_static', + 'group' => 'slider_items_height', + 'min' => 30, + 'max' => 800, + 'default' => 300, + 'condition' => array( + array( + 'control' => 'items_height_type', + 'operator' => '==', + 'value' => 'static', + ), + ), + ), + array( + 'type' => 'number', + 'name' => 'items_height_dynamic', + 'group' => 'slider_items_height', + 'min' => 10, + 'max' => 300, + 'default' => 80, + 'condition' => array( + array( + 'control' => 'items_height_type', + 'operator' => '==', + 'value' => 'dynamic', + ), + ), + ), + array( + 'type' => 'text', + 'label' => esc_html__( 'Items Minimal Height', 'visual-portfolio' ), + 'placeholder' => esc_attr__( '300px, 80vh', 'visual-portfolio' ), + 'description' => esc_html__( 'Values with `vh` units will not be visible in preview.', 'visual-portfolio' ), + 'name' => 'items_min_height', + 'group' => 'slider_items_height', + 'default' => '', + 'condition' => array( + array( + 'control' => 'items_height_type', + 'operator' => '!==', + 'value' => 'auto', + ), + ), + ), + array( + 'type' => 'radio', + 'label' => esc_html__( 'Slides Per View', 'visual-portfolio' ), + 'name' => 'slides_per_view_type', + 'group' => 'slider_slides_per_view', + 'default' => 'custom', + 'options' => array( + 'auto' => esc_html__( 'Auto', 'visual-portfolio' ), + 'custom' => esc_html__( 'Custom', 'visual-portfolio' ), + ), + 'condition' => array( + array( + 'control' => 'effect', + 'operator' => '!=', + 'value' => 'fade', + ), + ), + ), + array( + 'type' => 'number', + 'name' => 'slides_per_view_custom', + 'group' => 'slider_slides_per_view', + 'min' => 1, + 'max' => 6, + 'default' => 3, + 'condition' => array( + array( + 'control' => 'effect', + 'operator' => '!=', + 'value' => 'fade', + ), + array( + 'control' => 'slides_per_view_type', + 'operator' => '==', + 'value' => 'custom', + ), + ), + ), + array( + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Centered Slides', 'visual-portfolio' ), + 'name' => 'centered_slides', + 'default' => true, + 'reload_iframe' => false, + 'condition' => array( + array( + 'control' => 'effect', + 'operator' => '!=', + 'value' => 'fade', + ), + ), + ), + array( + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Loop', 'visual-portfolio' ), + 'name' => 'loop', + 'default' => false, + 'reload_iframe' => false, + ), + array( + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Free Scroll', 'visual-portfolio' ), + 'name' => 'free_mode', + 'group' => 'slider_free_mode', + 'default' => false, + 'reload_iframe' => false, + ), + array( + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Free Scroll Sticky', 'visual-portfolio' ), + 'name' => 'free_mode_sticky', + 'group' => 'slider_free_mode', + 'default' => false, + 'reload_iframe' => false, + 'condition' => array( + array( + 'control' => 'free_mode', + ), + ), + ), + array( + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Display Arrows', 'visual-portfolio' ), + 'name' => 'arrows', + 'default' => true, + ), + array( + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Display Bullets', 'visual-portfolio' ), + 'name' => 'bullets', + 'group' => 'slider_bullets', + 'default' => false, + ), + array( + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Dynamic Bullets', 'visual-portfolio' ), + 'name' => 'bullets_dynamic', + 'group' => 'slider_bullets', + 'default' => false, + 'reload_iframe' => false, + 'condition' => array( + array( + 'control' => 'bullets', + ), + ), + ), + array( + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Mousewheel Control', 'visual-portfolio' ), + 'name' => 'mousewheel', + 'default' => false, + ), + array( + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Display Thumbnails', 'visual-portfolio' ), + 'name' => 'thumbnails', + 'group' => 'slider_thumbnails', + 'default' => false, + ), + array( + 'type' => 'range', + 'label' => esc_html__( 'Thumbnails Gap', 'visual-portfolio' ), + 'name' => 'thumbnails_gap', + 'group' => 'slider_thumbnails', + 'default' => 15, + 'min' => 0, + 'max' => 150, + 'condition' => array( + array( + 'control' => 'thumbnails', + ), + ), + ), + array( + 'type' => 'radio', + 'label' => esc_html__( 'Thumbnails Height', 'visual-portfolio' ), + 'name' => 'thumbnails_height_type', + 'group' => 'slider_thumbnails', + 'default' => 'static', + 'options' => array( + 'auto' => esc_html__( 'Auto', 'visual-portfolio' ), + 'static' => esc_html__( 'Static (px)', 'visual-portfolio' ), + 'dynamic' => esc_html__( 'Dynamic (%)', 'visual-portfolio' ), + ), + 'condition' => array( + array( + 'control' => 'thumbnails', + ), + ), + ), + array( + 'type' => 'number', + 'name' => 'thumbnails_height_static', + 'group' => 'slider_thumbnails', + 'min' => 10, + 'max' => 400, + 'default' => 100, + 'condition' => array( + array( + 'control' => 'thumbnails', + ), + array( + 'control' => 'thumbnails_height_type', + 'operator' => '==', + 'value' => 'static', + ), + ), + ), + array( + 'type' => 'number', + 'name' => 'thumbnails_height_dynamic', + 'group' => 'slider_thumbnails', + 'min' => 10, + 'max' => 200, + 'default' => 30, + 'condition' => array( + array( + 'control' => 'thumbnails', + ), + array( + 'control' => 'thumbnails_height_type', + 'operator' => '==', + 'value' => 'dynamic', + ), + ), + ), + array( + 'type' => 'radio', + 'label' => esc_html__( 'Thumbnails Per View', 'visual-portfolio' ), + 'name' => 'thumbnails_per_view_type', + 'group' => 'slider_thumbnails', + 'default' => 'custom', + 'options' => array( + 'auto' => esc_html__( 'Auto', 'visual-portfolio' ), + 'custom' => esc_html__( 'Custom', 'visual-portfolio' ), + ), + 'condition' => array( + array( + 'control' => 'thumbnails', + ), + ), + ), + array( + 'type' => 'number', + 'name' => 'thumbnails_per_view_custom', + 'group' => 'slider_thumbnails', + 'min' => 1, + 'max' => 14, + 'default' => 8, + 'condition' => array( + array( + 'control' => 'thumbnails', + ), + array( + 'control' => 'thumbnails_per_view_type', + 'operator' => '==', + 'value' => 'custom', + ), + ), + ), + ), + ), + ), + $layouts + ); + } + + /** + * Add default items styles. + * + * @param array $items_styles - items styles array. + * + * @return array + */ + public function add_default_items_styles( $items_styles ) { + return array_merge( + array( + // Classic. + 'default' => array( + 'title' => esc_html__( 'Classic', 'visual-portfolio' ), + 'icon' => '', + 'image_preview_wizard' => visual_portfolio()->plugin_url . '/assets/admin/images/items-style-preview-classic.png', + 'builtin_controls' => array( + 'image' => array( + 'border_radius' => true, + 'transform' => true, // Pro. + 'css_filter' => true, // Pro. + ), + 'overlay' => array( + 'states' => true, + 'text_align' => false, + + // Elements. + 'elements' => array( + 'icons' => true, + ), + + // Colors. + 'colors' => array( + 'background' => true, + 'text' => true, + 'links' => false, + 'mix_blend_mode' => true, // Pro. + ), + + // Dimensions Pro. + 'dimensions' => array( + 'border_radius' => true, + 'margin' => true, + ), + ), + 'caption' => array( + 'states' => false, + 'text_align' => 'horizontal', + + // Elements. + 'elements' => array( + 'title' => true, + 'categories' => true, + 'date' => true, + 'author' => true, + 'comments_count' => true, + 'views_count' => true, + 'reading_time' => true, + 'excerpt' => true, + 'read_more' => true, + ), + + // Colors. + 'colors' => array( + 'background' => false, + 'text' => true, + 'links' => true, + ), + + // Typography Pro. + 'typography' => array( + 'title' => true, + 'category' => true, + 'meta' => true, + 'description' => true, + 'button' => true, + ), + + // Dimensions Pro. + 'dimensions' => array( + 'padding' => true, + 'items_gap' => true, + ), + ), + ), + 'controls' => array( + // Nothing here yet, all options are in builtin controls. + ), + ), + + // Fade. + 'fade' => array( + 'title' => esc_html__( 'Fade', 'visual-portfolio' ), + 'icon' => '', + 'image_preview_wizard' => visual_portfolio()->plugin_url . '/assets/admin/images/items-style-preview-fade.png', + 'builtin_controls' => array( + 'image' => array( + 'border_radius' => true, + 'transform' => true, // Pro. + 'css_filter' => true, // Pro. + ), + 'overlay' => array( + 'states' => true, + + // All available align values: 'horizontal'|'vertical'|'box'. + 'text_align' => 'box', + + 'under_image' => true, // Pro. + + // Elements. + 'elements' => array( + 'title' => true, + 'categories' => true, + 'date' => true, + 'author' => true, + 'comments_count' => true, + 'views_count' => true, + 'reading_time' => true, + 'excerpt' => true, + 'read_more' => false, + 'icons' => true, + ), + + // Colors. + 'colors' => array( + 'background' => true, + 'text' => true, + 'links' => false, + 'mix_blend_mode' => true, // Pro. + ), + + // Typography Pro. + 'typography' => array( + 'title' => true, + 'category' => true, + 'meta' => true, + 'description' => true, + ), + + // Dimensions Pro. + 'dimensions' => array( + 'border_radius' => true, + 'padding' => true, + 'margin' => true, + 'items_gap' => true, + ), + ), + 'caption' => false, + ), + 'controls' => array( + // Nothing here yet, all options are in builtin controls. + ), + ), + + // Fly. + 'fly' => array( + 'title' => esc_html__( 'Fly', 'visual-portfolio' ), + 'icon' => '', + 'image_preview_wizard' => visual_portfolio()->plugin_url . '/assets/admin/images/items-style-preview-fly.png', + 'builtin_controls' => array( + 'image' => array( + 'border_radius' => true, + 'transform' => true, // Pro. + 'css_filter' => true, // Pro. + ), + 'overlay' => array( + 'states' => true, + + // All available align values: 'horizontal'|'vertical'|'box'. + 'text_align' => 'box', + + 'under_image' => true, // Pro. + + // Elements. + 'elements' => array( + 'title' => true, + 'categories' => true, + 'date' => true, + 'author' => true, + 'comments_count' => true, + 'views_count' => true, + 'reading_time' => true, + 'excerpt' => true, + 'read_more' => false, + 'icons' => true, + ), + + // Colors. + 'colors' => array( + 'background' => true, + 'text' => true, + 'links' => false, + 'mix_blend_mode' => true, // Pro. + ), + + // Typography Pro. + 'typography' => array( + 'title' => true, + 'category' => true, + 'meta' => true, + 'description' => true, + ), + + // Dimensions Pro. + 'dimensions' => array( + 'border_radius' => true, + 'padding' => true, + 'margin' => true, + 'items_gap' => true, + ), + ), + 'caption' => false, + ), + 'controls' => array( + // Nothing here yet, all options are in builtin controls. + ), + ), + + // Emerge. + 'emerge' => array( + 'title' => esc_html__( 'Emerge', 'visual-portfolio' ), + 'icon' => '', + 'image_preview_wizard' => visual_portfolio()->plugin_url . '/assets/admin/images/items-style-preview-emerge.png', + 'builtin_controls' => array( + 'image' => array( + 'border_radius' => true, + 'transform' => true, // Pro. + 'css_filter' => true, // Pro. + ), + 'overlay' => array( + 'states' => true, + + // Colors. + 'colors' => array( + 'background' => true, + 'text' => false, + 'links' => false, + 'mix_blend_mode' => true, // Pro. + ), + + // Dimensions Pro. + 'dimensions' => array( + 'border_radius' => true, + 'margin' => true, + ), + ), + 'caption' => array( + 'states' => true, + + // All available align values: 'horizontal'|'vertical'|'box'. + 'text_align' => 'horizontal', + + 'under_image' => true, // Pro. + + // Elements. + 'elements' => array( + 'title' => true, + 'categories' => true, + 'date' => true, + 'author' => true, + 'comments_count' => true, + 'views_count' => true, + 'reading_time' => true, + 'excerpt' => true, + 'read_more' => false, + 'icons' => false, + ), + + // Colors. + 'colors' => array( + 'background' => true, + 'text' => true, + 'links' => true, + 'mix_blend_mode' => true, // Pro. + ), + + // Typography Pro. + 'typography' => array( + 'title' => true, + 'category' => true, + 'meta' => true, + 'description' => true, + ), + + // Dimensions Pro. + 'dimensions' => array( + 'padding' => true, + 'items_gap' => true, + ), + ), + ), + 'controls' => array( + // Nothing here yet, all options are in builtin controls. + ), + ), + ), + $items_styles + ); + } + + /** + * Register control fields for the metaboxes. + */ + public function register_controls() { + do_action( 'vpf_before_register_controls' ); + + /** + * Categories. + */ + Visual_Portfolio_Controls::register_categories( + array( + 'content-source' => array( + 'title' => esc_html__( 'Content Source', 'visual-portfolio' ), + 'is_opened' => true, + ), + 'content-source-post-based' => array( + 'title' => esc_html__( 'Posts Settings', 'visual-portfolio' ), + 'is_opened' => true, + 'icon' => '', + ), + 'content-source-images' => array( + 'title' => esc_html__( 'Images Settings', 'visual-portfolio' ), + 'is_opened' => true, + 'icon' => '', + ), + 'content-source-social-stream' => array( + 'title' => esc_html__( 'Social Stream Settings', 'visual-portfolio' ), + 'is_opened' => true, + 'icon' => '', + ), + 'content-source-general' => array( + 'title' => esc_html__( 'General Settings', 'visual-portfolio' ), + 'is_opened' => true, + 'icon' => '', + ), + 'layout-elements' => array( + 'title' => esc_html__( 'Layout', 'visual-portfolio' ), + 'is_opened' => false, + 'icon' => '', + ), + 'items-style' => array( + 'title' => esc_html__( 'Skin', 'visual-portfolio' ), + 'is_opened' => false, + 'icon' => '', + ), + 'items-click-action' => array( + 'title' => esc_html__( 'Click Action', 'visual-portfolio' ), + 'is_opened' => false, + 'icon' => '', + ), + 'content-protection' => array( + 'title' => esc_html__( 'Protection', 'visual-portfolio' ), + 'is_opened' => false, + 'icon' => '', + ), + 'custom_css' => array( + 'title' => esc_html__( 'Custom CSS', 'visual-portfolio' ), + 'is_opened' => false, + 'icon' => '', + ), + ) + ); + + /** + * Enabled preview for gutenberg block. + */ + Visual_Portfolio_Controls::register( + array( + 'type' => 'hidden', + 'name' => 'preview_image_example', + 'default' => '', + ) + ); + + /** + * Enabled setup wizard. + */ + Visual_Portfolio_Controls::register( + array( + 'type' => 'hidden', + 'name' => 'setup_wizard', + 'default' => '', + ) + ); + + /** + * Content Source + */ + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source', + 'type' => 'icons_selector', + 'name' => 'content_source', + 'setup_wizard' => true, + 'default' => '', + 'options' => array( + 'post-based' => array( + 'value' => 'post-based', + 'title' => esc_html__( 'Posts', 'visual-portfolio' ), + 'icon' => '', + 'icon_wizard' => '', + ), + 'images' => array( + 'value' => 'images', + 'title' => esc_html__( 'Images', 'visual-portfolio' ), + 'icon' => '', + 'icon_wizard' => '', + ), + 'social-stream' => array( + 'value' => 'social-stream', + 'title' => esc_html__( 'Social', 'visual-portfolio' ), + 'icon' => '', + 'icon_wizard' => '', + ), + ), + ) + ); + + /** + * Content Source Posts + */ + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-post-based', + 'type' => 'icons_selector', + 'name' => 'posts_source', + 'default' => 'portfolio', + 'collapse_rows' => 2, + 'value_callback' => array( $this, 'find_post_types_options' ), + ) + ); + $allowed_protocols = array( + 'a' => array( + 'href' => array(), + 'target' => array(), + ), + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-post-based', + 'type' => 'textarea', + 'label' => esc_html__( 'Custom Query', 'visual-portfolio' ), + // translators: %1$s - escaped url. + 'description' => sprintf( wp_kses( __( 'Build custom query according to WordPress Codex. See example here %1$s.', 'visual-portfolio' ), $allowed_protocols ), esc_url( 'https://visualportfolio.co/docs/portfolio-layouts/content-source/post-based/#custom-query' ) ), + 'name' => 'posts_custom_query', + 'default' => '', + 'cols' => 30, + 'rows' => 3, + 'condition' => array( + array( + 'control' => 'posts_source', + 'value' => 'custom_query', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-post-based', + 'type' => 'select', + 'label' => esc_html__( 'Post Types', 'visual-portfolio' ), + 'name' => 'post_types_set', + 'default' => array( 'post' ), + 'value_callback' => array( $this, 'find_posts_types_select_control' ), + 'multiple' => true, + 'condition' => array( + array( + 'control' => 'posts_source', + 'value' => 'post_types_set', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-post-based', + 'type' => 'select', + 'label' => esc_html__( 'Specific Posts', 'visual-portfolio' ), + 'name' => 'posts_ids', + 'default' => array(), + 'value_callback' => array( $this, 'find_posts_select_control' ), + 'searchable' => true, + 'multiple' => true, + 'condition' => array( + array( + 'control' => 'posts_source', + 'value' => 'ids', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-post-based', + 'type' => 'select', + 'label' => esc_html__( 'Excluded Posts', 'visual-portfolio' ), + 'name' => 'posts_excluded_ids', + 'default' => array(), + 'value_callback' => array( $this, 'find_posts_select_control' ), + 'searchable' => true, + 'multiple' => true, + 'condition' => array( + array( + 'control' => 'posts_source', + 'operator' => '!=', + 'value' => 'ids', + ), + array( + 'control' => 'posts_source', + 'operator' => '!=', + 'value' => 'custom_query', + ), + array( + 'control' => 'posts_source', + 'operator' => '!=', + 'value' => 'current_query', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-post-based', + 'type' => 'select', + 'label' => esc_html__( 'Taxonomies', 'visual-portfolio' ), + 'name' => 'posts_taxonomies', + 'group' => 'posts_taxonomies', + 'default' => array(), + 'value_callback' => array( $this, 'find_taxonomies_select_control' ), + 'searchable' => true, + 'multiple' => true, + 'condition' => array( + array( + 'control' => 'posts_source', + 'operator' => '!=', + 'value' => 'ids', + ), + array( + 'control' => 'posts_source', + 'operator' => '!=', + 'value' => 'custom_query', + ), + array( + 'control' => 'posts_source', + 'operator' => '!=', + 'value' => 'current_query', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-post-based', + 'type' => 'radio', + 'label' => esc_html__( 'Taxonomies Relation', 'visual-portfolio' ), + 'name' => 'posts_taxonomies_relation', + 'group' => 'posts_taxonomies', + 'default' => 'or', + 'options' => array( + 'or' => esc_html__( 'OR', 'visual-portfolio' ), + 'and' => esc_html__( 'AND', 'visual-portfolio' ), + ), + 'condition' => array( + array( + 'control' => 'posts_source', + 'operator' => '!=', + 'value' => 'ids', + ), + array( + 'control' => 'posts_source', + 'operator' => '!=', + 'value' => 'custom_query', + ), + array( + 'control' => 'posts_source', + 'operator' => '!=', + 'value' => 'current_query', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-post-based', + 'type' => 'select', + 'label' => esc_html__( 'Order by', 'visual-portfolio' ), + 'name' => 'posts_order_by', + 'group' => 'posts_order', + 'default' => 'post_date', + 'options' => array( + 'post_date' => esc_html__( 'Date', 'visual-portfolio' ), + 'title' => esc_html__( 'Title', 'visual-portfolio' ), + 'id' => esc_html__( 'ID', 'visual-portfolio' ), + 'comment_count' => esc_html__( 'Comments Count', 'visual-portfolio' ), + 'modified' => esc_html__( 'Modified', 'visual-portfolio' ), + 'menu_order' => esc_html__( 'Menu Order', 'visual-portfolio' ), + 'post__in' => esc_html__( 'Manual Selection', 'visual-portfolio' ), + 'rand' => esc_html__( 'Random', 'visual-portfolio' ), + ), + 'condition' => array( + array( + 'control' => 'posts_source', + 'operator' => '!=', + 'value' => 'custom_query', + ), + array( + 'control' => 'posts_source', + 'operator' => '!=', + 'value' => 'current_query', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-post-based', + 'type' => 'notice', + // translators: %1$s - url. + // translators: %2$s - link text. + 'description' => wp_kses_post( sprintf( __( 'Menu Order is typically used in combination with one of these plugins: %2$s', 'visual-portfolio' ), 'https://wordpress.org/plugins/search/post+order/', 'Post Order Plugins' ) ), + 'name' => 'posts_order_direction_notice', + 'group' => 'posts_order', + 'condition' => array( + array( + 'control' => 'posts_order_by', + 'operator' => '===', + 'value' => 'menu_order', + ), + array( + 'control' => 'posts_source', + 'operator' => '!=', + 'value' => 'custom_query', + ), + array( + 'control' => 'posts_source', + 'operator' => '!=', + 'value' => 'current_query', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-post-based', + 'type' => 'radio', + 'label' => esc_html__( 'Order Direction', 'visual-portfolio' ), + 'name' => 'posts_order_direction', + 'group' => 'posts_order', + 'default' => 'desc', + 'options' => array( + 'asc' => esc_html__( 'ASC', 'visual-portfolio' ), + 'desc' => esc_html__( 'DESC', 'visual-portfolio' ), + ), + 'condition' => array( + array( + 'control' => 'posts_source', + 'operator' => '!=', + 'value' => 'custom_query', + ), + array( + 'control' => 'posts_source', + 'operator' => '!=', + 'value' => 'current_query', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-post-based', + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Avoid Duplicates', 'visual-portfolio' ), + 'description' => esc_html__( 'Enable to avoid duplicate posts from showing up. This only affects the frontend', 'visual-portfolio' ), + 'name' => 'posts_avoid_duplicate_posts', + 'default' => false, + 'reload_iframe' => false, + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-post-based', + 'type' => 'range', + 'label' => esc_html__( 'Offset', 'visual-portfolio' ), + 'description' => esc_html__( 'Use this setting to skip over posts (e.g. `2` to skip over 2 posts)', 'visual-portfolio' ), + 'name' => 'posts_offset', + 'min' => 0, + 'max' => 100, + 'condition' => array( + array( + 'control' => 'posts_source', + 'operator' => '!=', + 'value' => 'ids', + ), + array( + 'control' => 'posts_source', + 'operator' => '!=', + 'value' => 'custom_query', + ), + array( + 'control' => 'posts_source', + 'operator' => '!=', + 'value' => 'current_query', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-post-based', + 'type' => 'pro_note', + 'name' => 'additional_query_settings_pro', + 'label' => esc_html__( 'Premium Only', 'visual-portfolio' ), + 'description' => esc_html__( 'Additional query settings, such as:', 'visual-portfolio' ) . ' +
    +
  • ' . esc_html__( 'Filter by specific author', 'visual-portfolio' ) . '
  • +
  • ' . esc_html__( 'Filter by publish date range', 'visual-portfolio' ) . '
  • +
  • ' . esc_html__( 'Exclude posts without thumbnail', 'visual-portfolio' ) . '
  • +
  • ' . esc_html__( 'Exclude sticky posts', 'visual-portfolio' ) . '
  • +
  • ' . esc_html__( 'etc...', 'visual-portfolio' ) . '
  • +
', + ) + ); + + /** + * Content Source Images + */ + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-images', + 'type' => 'gallery', + 'name' => 'images', + 'wpml' => true, + 'setup_wizard' => true, + 'focal_point' => true, + 'image_controls' => array( + 'title' => array( + 'type' => 'text', + 'label' => esc_html__( 'Title', 'visual-portfolio' ), + ), + 'description' => array( + 'type' => 'textarea', + 'label' => esc_html__( 'Description', 'visual-portfolio' ), + ), + 'categories' => array( + 'type' => 'select', + 'label' => esc_html__( 'Categories', 'visual-portfolio' ), + 'multiple' => true, + 'creatable' => true, + ), + 'format' => array( + 'type' => 'select', + 'label' => esc_html__( 'Format', 'visual-portfolio' ), + 'default' => 'standard', + 'options' => array( + 'standard' => esc_html__( 'Standard', 'visual-portfolio' ), + 'video' => esc_html__( 'Video', 'visual-portfolio' ), + ), + ), + 'video_url' => array( + 'type' => 'text', + 'label' => esc_html__( 'Video URL', 'visual-portfolio' ), + 'placeholder' => esc_html__( 'https://...', 'visual-portfolio' ), + 'description' => esc_html__( 'Full list of supported links', 'visual-portfolio' ) . ' ' . esc_html__( 'see here', 'visual-portfolio' ) . '', + 'condition' => array( + array( + 'control' => 'SELF.format', + 'value' => 'video', + ), + ), + ), + 'url' => array( + 'type' => 'text', + 'label' => esc_html__( 'URL', 'visual-portfolio' ), + 'description' => esc_html__( 'By default used full image url, you can use custom one', 'visual-portfolio' ), + 'placeholder' => esc_html__( 'https://...', 'visual-portfolio' ), + ), + 'author' => array( + 'type' => 'text', + 'label' => esc_html__( 'Author Name', 'visual-portfolio' ), + 'default' => '', + ), + 'author_url' => array( + 'type' => 'text', + 'label' => esc_html__( 'Author URL', 'visual-portfolio' ), + 'default' => '', + ), + 'image_settings_pro_note' => array( + 'type' => 'pro_note', + 'label' => esc_html__( 'Premium Only', 'visual-portfolio' ), + 'description' => '
    +
  • ' . esc_html__( 'Support for Audio format', 'visual-portfolio' ) . '
  • +
  • ' . esc_html__( 'Custom image for Popup', 'visual-portfolio' ) . '
  • +
  • ' . esc_html__( 'Custom image for hover state', 'visual-portfolio' ) . '
  • +
  • ' . esc_html__( 'etc...', 'visual-portfolio' ) . '
  • +
', + ), + ), + 'default' => array( + /** + * Array items: + * id - image id. + * title - image title. + * description - image description. + * categories - categories array. + * format - image format [standard,video]. + * video_url - video url. + */ + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-images', + 'type' => 'select', + 'label' => esc_html__( 'Items Title Source', 'visual-portfolio' ), + 'name' => 'images_titles_source', + 'group' => 'images_titles_source', + 'default' => 'custom', + 'options' => array( + 'none' => esc_html__( 'None', 'visual-portfolio' ), + 'custom' => esc_html__( 'Custom', 'visual-portfolio' ), + 'title' => esc_html__( 'Image Title', 'visual-portfolio' ), + 'caption' => esc_html__( 'Image Caption', 'visual-portfolio' ), + 'alt' => esc_html__( 'Image Alt', 'visual-portfolio' ), + 'description' => esc_html__( 'Image Description', 'visual-portfolio' ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-images', + 'type' => 'select', + 'label' => esc_html__( 'Items Description Source', 'visual-portfolio' ), + 'name' => 'images_descriptions_source', + 'group' => 'images_titles_source', + 'default' => 'custom', + 'options' => array( + 'none' => esc_html__( 'None', 'visual-portfolio' ), + 'custom' => esc_html__( 'Custom', 'visual-portfolio' ), + 'title' => esc_html__( 'Image Title', 'visual-portfolio' ), + 'caption' => esc_html__( 'Image Caption', 'visual-portfolio' ), + 'alt' => esc_html__( 'Image Alt', 'visual-portfolio' ), + 'description' => esc_html__( 'Image Description', 'visual-portfolio' ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-images', + 'type' => 'select', + 'label' => esc_html__( 'Order by', 'visual-portfolio' ), + 'name' => 'images_order_by', + 'group' => 'images_order', + 'default' => 'default', + 'options' => array( + 'default' => esc_html__( 'Default', 'visual-portfolio' ), + 'date' => esc_html__( 'Uploaded', 'visual-portfolio' ), + 'title' => esc_html__( 'Title', 'visual-portfolio' ), + 'rand' => esc_html__( 'Random', 'visual-portfolio' ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-images', + 'type' => 'radio', + 'label' => esc_html__( 'Order Direction', 'visual-portfolio' ), + 'name' => 'images_order_direction', + 'group' => 'images_order', + 'default' => 'asc', + 'options' => array( + 'asc' => esc_html__( 'ASC', 'visual-portfolio' ), + 'desc' => esc_html__( 'DESC', 'visual-portfolio' ), + ), + ) + ); + + /** + * Content Source Protection. + */ + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-protection', + 'type' => 'pro_note', + 'name' => 'protection_pro_note', + 'label' => esc_html__( 'Premium Only', 'visual-portfolio' ), + 'description' => '

' . esc_html__( 'Protect your works using watermarks, password, and age gate', 'visual-portfolio' ) . '

', + 'condition' => array( + array( + 'control' => 'content_source', + 'operator' => '!==', + 'value' => 'social-stream', + ), + ), + ) + ); + + /** + * Content Source Social Stream. + */ + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-social-stream', + 'type' => 'pro_note', + 'name' => 'social_pro_note', + 'label' => esc_html__( 'Premium Only', 'visual-portfolio' ), + 'description' => '

' . esc_html__( 'Display social feeds such as Instagram, Youtube, Flickr, Twitter, etc...', 'visual-portfolio' ) . '

', + ) + ); + + /** + * Content Source General Settings. + */ + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-general', + 'type' => 'number', + 'label' => esc_html__( 'Items Per Page', 'visual-portfolio' ), + 'name' => 'items_count', + 'default' => 6, + 'min' => 1, + 'condition' => array( + array( + array( + 'control' => 'content_source', + 'operator' => '!==', + 'value' => 'post-based', + ), + // AND. + array( + 'control' => 'posts_source', + 'operator' => '!==', + 'value' => 'current_query', + ), + ), + ), + ) + ); + + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-general', + 'type' => 'buttons', + 'label' => esc_html__( 'No Items Action', 'visual-portfolio' ), + 'name' => 'no_items_action', + 'group' => 'no_items_action', + 'default' => 'notice', + 'options' => array( + 'notice' => esc_html__( 'Notice', 'visual-portfolio' ), + 'hide' => esc_html__( 'Hide', 'visual-portfolio' ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-general', + 'type' => 'textarea', + 'placeholder' => esc_html__( 'Notice', 'visual-portfolio' ), + 'name' => 'no_items_notice', + 'group' => 'no_items_action', + 'default' => esc_html__( 'No items were found matching your selection.', 'visual-portfolio' ), + 'wpml' => true, + 'condition' => array( + array( + 'control' => 'no_items_action', + 'operator' => '===', + 'value' => 'notice', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-general', + 'type' => 'html', + 'description' => esc_html__( 'Note: you will see the notice in the preview. Block will be hidden in the site frontend.', 'visual-portfolio' ), + 'name' => 'no_items_action_hide_info', + 'group' => 'no_items_action', + 'condition' => array( + array( + 'control' => 'no_items_action', + 'operator' => '===', + 'value' => 'hide', + ), + ), + ) + ); + + Visual_Portfolio_Controls::register( + array( + 'category' => 'content-source-general', + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Stretch', 'visual-portfolio' ), + 'name' => 'stretch', + 'default' => false, + 'reload_iframe' => false, + 'description' => esc_attr__( 'Break container and display it wide. This option helpful for 3rd-party page builders, in the Gutenberg themes you can use the built in Wide and Fullwidth features.', 'visual-portfolio' ), + ) + ); + + /** + * Layouts. + */ + $layouts = Visual_Portfolio_Get::get_all_layouts(); + + // Layouts selector. + $layouts_selector = array(); + foreach ( $layouts as $name => $layout ) { + $layouts_selector[ $name ] = array( + 'value' => $name, + 'title' => $layout['title'], + 'icon' => isset( $layout['icon'] ) ? $layout['icon'] : '', + ); + } + + Visual_Portfolio_Controls::register( + array( + 'category' => 'layouts', + 'type' => 'icons_selector', + 'name' => 'layout', + 'default' => 'tiles', + 'options' => $layouts_selector, + ) + ); + + // layouts options. + foreach ( $layouts as $name => $layout ) { + if ( ! isset( $layout['controls'] ) ) { + continue; + } + foreach ( $layout['controls'] as $field ) { + $field['category'] = 'layouts'; + $field['name'] = $name . '_' . $field['name']; + + // condition names prefix fix. + if ( isset( $field['condition'] ) ) { + foreach ( $field['condition'] as $k => $cond ) { + if ( isset( $cond['control'] ) ) { + if ( strpos( $cond['control'], 'GLOBAL_' ) === 0 ) { + $field['condition'][ $k ]['control'] = str_replace( 'GLOBAL_', '', $cond['control'] ); + } else { + $field['condition'][ $k ]['control'] = $name . '_' . $cond['control']; + } + } + } + } + + $field['condition'] = array_merge( + isset( $field['condition'] ) ? $field['condition'] : array(), + array( + array( + 'control' => 'layout', + 'value' => $name, + ), + ) + ); + Visual_Portfolio_Controls::register( $field ); + } + } + + Visual_Portfolio_Controls::register( + array( + 'category' => 'layouts', + 'type' => 'range', + 'label' => esc_html__( 'Gap', 'visual-portfolio' ), + 'name' => 'items_gap', + 'group' => 'layout_items_gap', + 'default' => 15, + 'min' => 0, + 'max' => 200, + 'reload_iframe' => false, + 'style' => array( + array( + 'element' => '.vp-portfolio__items', + 'property' => '--vp-items__gap', + 'mask' => '$px', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'layouts', + 'type' => 'range', + 'label' => esc_html__( 'Vertical Gap', 'visual-portfolio' ), + 'description' => esc_html__( 'When empty, used Gap option', 'visual-portfolio' ), + 'name' => 'items_gap_vertical', + 'group' => 'layout_items_gap', + 'default' => '', + 'min' => 0, + 'max' => 200, + 'reload_iframe' => false, + 'style' => array( + array( + 'element' => '.vp-portfolio__items', + 'property' => '--vp-items__gap-vertical', + 'mask' => '$px', + ), + ), + 'condition' => array( + array( + 'control' => 'layout', + 'operator' => '!==', + 'value' => 'slider', + ), + ), + ) + ); + + /** + * Items Style + */ + $items_styles = Visual_Portfolio_Get::get_all_items_styles(); + + // Styles selector. + $items_styles_selector = array(); + foreach ( $items_styles as $style_name => $style ) { + $items_styles_selector[ $style_name ] = array( + 'value' => $style_name, + 'title' => $style['title'], + 'icon' => isset( $style['icon'] ) ? $style['icon'] : '', + 'image_preview_wizard' => isset( $style['image_preview_wizard'] ) ? $style['image_preview_wizard'] : '', + ); + } + + Visual_Portfolio_Controls::register( + array( + 'category' => 'items-style', + 'type' => 'icons_selector', + 'name' => 'items_style', + 'default' => 'fade', + 'collapse_rows' => 2, + 'options' => $items_styles_selector, + 'setup_wizard' => true, + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'items-style', + 'type' => 'category_navigator', + 'name' => 'items_style_subcategory', + 'initialOpen' => 'items-style-general', + 'options' => array( + array( + 'title' => esc_html__( 'General', 'visual-portfolio' ), + 'category' => 'items-style-general', + ), + array( + 'title' => esc_html__( 'Image', 'visual-portfolio' ), + 'category' => 'items-style-image', + ), + array( + 'title' => esc_html__( 'Overlay', 'visual-portfolio' ), + 'category' => 'items-style-overlay', + ), + array( + 'title' => esc_html__( 'Caption', 'visual-portfolio' ), + 'category' => 'items-style-caption', + ), + ), + ) + ); + + $builtin_default_options = array( + 'image' => array( + 'border_radius' => true, + 'transform' => false, // Pro. + 'css_filter' => false, // Pro. + ), + 'overlay' => array( + 'states' => true, + + // All available align values: 'horizontal'|'vertical'|'box'. + 'text_align' => 'box', + + 'under_image' => false, // Pro. + + // Elements. + 'elements' => array( + 'title' => false, + 'categories' => false, + 'date' => false, + 'author' => false, + 'comments_count' => false, + 'views_count' => false, + 'reading_time' => false, + 'excerpt' => false, + 'read_more' => false, + 'icons' => false, + ), + + // Colors. + 'colors' => array( + 'background' => true, + 'text' => true, + 'links' => false, + 'mix_blend_mode' => false, // Pro. + ), + + // Typography Pro. + 'typography' => array( + 'title' => false, + 'category' => false, + 'meta' => false, + 'description' => false, + 'button' => false, + ), + + // Dimensions Pro. + 'dimensions' => array( + 'border_radius' => false, + 'padding' => false, + 'margin' => false, + 'items_gap' => false, + ), + ), + 'caption' => array( + 'states' => true, + + // All available align values: 'horizontal'|'vertical'|'box'. + 'text_align' => 'horizontal', + + 'under_image' => false, // Pro. + + // Elements. + 'elements' => array( + 'title' => false, + 'categories' => false, + 'date' => false, + 'author' => false, + 'comments_count' => false, + 'views_count' => false, + 'reading_time' => false, + 'excerpt' => false, + 'read_more' => false, + 'icons' => false, + ), + + // Colors. + 'colors' => array( + 'background' => false, + 'text' => true, + 'links' => true, + 'mix_blend_mode' => false, // Pro. + ), + + // Typography Pro. + 'typography' => array( + 'title' => false, + 'category' => false, + 'meta' => false, + 'description' => false, + 'button' => false, + ), + + // Dimensions Pro. + 'dimensions' => array( + 'border_radius' => false, + 'padding' => false, + 'margin' => false, + 'items_gap' => false, + ), + ), + ); + $builtin_default_options = apply_filters( 'vpf_items_style_builtin_controls_options', $builtin_default_options ); + + // styles builtin options. + foreach ( $items_styles as $style_name => $style ) { + $builtin_fields = array(); + + if ( ! empty( $style['builtin_controls'] ) ) { + foreach ( $builtin_default_options as $category_name => $default_options ) { + if ( empty( $style['builtin_controls'][ $category_name ] ) || ! $style['builtin_controls'][ $category_name ] ) { + continue; + } + + $options = $style['builtin_controls'][ $category_name ]; + + $new_fields = array(); + + switch ( $category_name ) { + // Image. + case 'image': + $new_fields[] = array( + 'type' => 'category_toggle_group', + 'category' => 'items-style-image', + 'name' => 'items_style_image_states', + 'options' => array( + array( + 'title' => esc_html__( 'Normal', 'visual-portfolio' ), + 'category' => 'items-style-image-normal', + ), + array( + 'title' => esc_html__( 'Hover', 'visual-portfolio' ), + 'category' => 'items-style-image-hover', + ), + ), + ); + + if ( isset( $options['border_radius'] ) && $options['border_radius'] ) { + $new_fields[] = array( + 'type' => 'unit', + 'category' => 'items-style-image-normal', + 'label' => esc_html__( 'Border Radius', 'visual-portfolio' ), + 'name' => 'images_rounded_corners', + 'default' => '', + 'style' => array( + array( + 'element' => '.vp-portfolio__items-style-' . $style_name, + 'property' => '--vp-items-style-' . $style_name . '--image__border-radius', + ), + ), + ); + } + + if ( isset( $options['transform'] ) && $options['transform'] && isset( $options['css_filter'] ) && $options['css_filter'] ) { + $new_fields[] = array( + 'type' => 'pro_note', + 'category' => 'items-style-image-normal', + 'name' => 'additional_image_skin_settings_pro', + 'label' => esc_html__( 'Premium Only', 'visual-portfolio' ), + 'description' => 'Apply Instagram-like filters, change image transform', + ); + $new_fields[] = array( + 'type' => 'pro_note', + 'category' => 'items-style-image-hover', + 'name' => 'additional_image_hover_skin_settings_pro', + 'label' => esc_html__( 'Premium Only', 'visual-portfolio' ), + 'description' => 'Apply Instagram-like filters, change image transform and border radius for both default and hover states', + ); + } + break; + + // Overlay / Caption. + case 'overlay': + case 'caption': + if ( isset( $options['states'] ) && $options['states'] ) { + $new_fields[] = array( + 'type' => 'select', + 'category' => 'items-style-' . $category_name, + 'label' => esc_html__( 'Display', 'visual-portfolio' ), + 'name' => 'show_' . $category_name, + 'default' => 'hover', + 'options' => array( + 'hover' => esc_html__( 'Hover State Only', 'visual-portfolio' ), + 'default' => esc_html__( 'Default State Only', 'visual-portfolio' ), + 'always' => esc_html__( 'Always', 'visual-portfolio' ), + ), + ); + } + + if ( isset( $options['text_align'] ) && $options['text_align'] ) { + $new_fields[] = array( + 'type' => 'align', + 'category' => 'items-style-' . $category_name, + 'label' => esc_html__( 'Text Align', 'visual-portfolio' ), + 'name' => $category_name . '_text_align', + 'default' => 'center', + 'options' => $options['text_align'], + ); + } + + $new_fields[] = array( + 'type' => 'category_collapse', + 'category' => 'items-style-' . $category_name, + 'name' => 'items_style_' . $category_name . '_subcategory', + 'options' => array( + array( + 'title' => esc_html__( 'Elements', 'visual-portfolio' ), + 'category' => 'items-style-' . $category_name . '-elements', + ), + array( + 'title' => esc_html__( 'Colors', 'visual-portfolio' ), + 'category' => 'items-style-' . $category_name . '-colors', + ), + array( + 'title' => esc_html__( 'Typography', 'visual-portfolio' ), + 'category' => 'items-style-' . $category_name . '-typography', + ), + array( + 'title' => esc_html__( 'Dimensions', 'visual-portfolio' ), + 'category' => 'items-style-' . $category_name . '-dimensions', + ), + ), + ); + + // Elements. + if ( ! empty( $options['elements'] ) ) { + $elements = $options['elements']; + + if ( isset( $elements['title'] ) && $elements['title'] ) { + $new_fields[] = array( + 'type' => 'checkbox', + 'category' => 'items-style-' . $category_name . '-elements', + 'alongside' => esc_html__( 'Display Title', 'visual-portfolio' ), + 'name' => 'show_title', + 'default' => true, + ); + } + + if ( isset( $elements['categories'] ) && $elements['categories'] ) { + $new_fields[] = array( + 'type' => 'checkbox', + 'category' => 'items-style-' . $category_name . '-elements', + 'alongside' => esc_html__( 'Display Categories', 'visual-portfolio' ), + 'name' => 'show_categories', + 'group' => 'items_style_categories', + 'default' => true, + ); + $new_fields[] = array( + 'type' => 'range', + 'category' => 'items-style-' . $category_name . '-elements', + 'label' => esc_html__( 'Categories Count', 'visual-portfolio' ), + 'name' => 'categories_count', + 'group' => 'items_style_categories', + 'min' => 1, + 'max' => 20, + 'default' => 1, + 'condition' => array( + array( + 'control' => 'show_categories', + ), + ), + ); + } + + if ( isset( $elements['date'] ) && $elements['date'] ) { + $new_fields[] = array( + 'type' => 'select', + 'category' => 'items-style-' . $category_name . '-elements', + 'label' => esc_html__( 'Display Date', 'visual-portfolio' ), + 'name' => 'show_date', + 'group' => 'items_style_date', + 'default' => 'false', + 'options' => array( + 'false' => esc_html__( 'Hide', 'visual-portfolio' ), + 'true' => esc_html__( 'Default', 'visual-portfolio' ), + 'human' => esc_html__( 'Human Format', 'visual-portfolio' ), + ), + ); + $new_fields[] = array( + 'type' => 'text', + 'category' => 'items-style-' . $category_name . '-elements', + 'name' => 'date_format', + 'group' => 'items_style_date', + 'default' => 'F j, Y', + 'description' => esc_attr__( 'Date format example: F j, Y', 'visual-portfolio' ), + 'wpml' => true, + 'condition' => array( + array( + 'control' => 'show_date', + ), + ), + ); + } + + if ( isset( $elements['author'] ) && $elements['author'] ) { + $new_fields[] = array( + 'type' => 'checkbox', + 'category' => 'items-style-' . $category_name . '-elements', + 'alongside' => esc_html__( 'Display Author', 'visual-portfolio' ), + 'name' => 'show_author', + 'default' => false, + ); + } + + if ( isset( $elements['comments_count'] ) && $elements['comments_count'] ) { + $new_fields[] = array( + 'type' => 'checkbox', + 'category' => 'items-style-' . $category_name . '-elements', + 'alongside' => esc_html__( 'Display Comments Count', 'visual-portfolio' ), + 'name' => 'show_comments_count', + 'default' => false, + 'condition' => array( + array( + 'control' => 'GLOBAL_content_source', + 'value' => 'post-based', + ), + ), + ); + } + + if ( isset( $elements['views_count'] ) && $elements['views_count'] ) { + $new_fields[] = array( + 'type' => 'checkbox', + 'category' => 'items-style-' . $category_name . '-elements', + 'alongside' => esc_html__( 'Display Views Count', 'visual-portfolio' ), + 'name' => 'show_views_count', + 'default' => false, + 'condition' => array( + array( + 'control' => 'GLOBAL_content_source', + 'value' => 'post-based', + ), + ), + ); + } + + if ( isset( $elements['reading_time'] ) && $elements['reading_time'] ) { + $new_fields[] = array( + 'type' => 'checkbox', + 'category' => 'items-style-' . $category_name . '-elements', + 'alongside' => esc_html__( 'Display Reading Time', 'visual-portfolio' ), + 'name' => 'show_reading_time', + 'default' => false, + 'condition' => array( + array( + 'control' => 'GLOBAL_content_source', + 'value' => 'post-based', + ), + ), + ); + } + + if ( isset( $elements['excerpt'] ) && $elements['excerpt'] ) { + $new_fields[] = array( + 'type' => 'checkbox', + 'category' => 'items-style-' . $category_name . '-elements', + 'alongside' => esc_html__( 'Display Excerpt', 'visual-portfolio' ), + 'name' => 'show_excerpt', + 'group' => 'items_style_excerpt', + 'default' => false, + ); + $new_fields[] = array( + 'type' => 'number', + 'category' => 'items-style-' . $category_name . '-elements', + 'label' => esc_html__( 'Excerpt Words Count', 'visual-portfolio' ), + 'name' => 'excerpt_words_count', + 'group' => 'items_style_excerpt', + 'default' => 15, + 'min' => 1, + 'max' => 200, + 'condition' => array( + array( + 'control' => 'show_excerpt', + ), + ), + ); + } + + if ( isset( $elements['read_more'] ) && $elements['read_more'] ) { + $new_fields[] = array( + 'type' => 'select', + 'category' => 'items-style-' . $category_name . '-elements', + 'label' => esc_html__( 'Display Read More Button', 'visual-portfolio' ), + 'name' => 'show_read_more', + 'group' => 'items_style_read_more', + 'default' => 'false', + 'options' => array( + 'false' => esc_html__( 'Hide', 'visual-portfolio' ), + 'true' => esc_html__( 'Always Display', 'visual-portfolio' ), + 'more_tag' => esc_html__( 'Display when used `More tag` in the post', 'visual-portfolio' ), + ), + ); + $new_fields[] = array( + 'type' => 'text', + 'category' => 'items-style-' . $category_name . '-elements', + 'name' => 'read_more_label', + 'group' => 'items_style_read_more', + 'default' => 'Read More', + 'description' => esc_attr__( 'Read More button label', 'visual-portfolio' ), + 'wpml' => true, + 'condition' => array( + array( + 'control' => 'show_read_more', + 'operator' => '!=', + 'value' => 'false', + ), + ), + ); + } + + if ( isset( $elements['icons'] ) && $elements['icons'] ) { + $new_fields[] = array( + 'type' => 'checkbox', + 'category' => 'items-style-' . $category_name . '-elements', + 'alongside' => esc_html__( 'Display Icon', 'visual-portfolio' ), + 'name' => 'show_icon', + 'default' => false, + ); + } + } + + // Colors. + if ( ! empty( $options['colors'] ) ) { + $has_background = isset( $options['colors']['background'] ) && $options['colors']['background']; + $has_text = isset( $options['colors']['text'] ) && $options['colors']['text']; + $has_links = isset( $options['colors']['links'] ) && $options['colors']['links']; + $has_blend_mode = isset( $options['colors']['mix_blend_mode'] ) && $options['colors']['mix_blend_mode']; + + if ( $has_background ) { + $new_fields[] = array( + 'type' => 'color', + 'category' => 'items-style-' . $category_name . '-colors', + 'label' => esc_html__( 'Background', 'visual-portfolio' ), + 'name' => $category_name . '_bg_color', + 'alpha' => true, + 'gradient' => true, + 'style' => array( + array( + 'element' => '.vp-portfolio__items-style-' . $style_name, + 'property' => '--vp-items-style-' . $style_name . '--' . $category_name . '__background', + ), + ), + ); + } + + if ( $has_text ) { + $new_fields[] = array( + 'type' => 'color', + 'category' => 'items-style-' . $category_name . '-colors', + 'label' => esc_html__( 'Text', 'visual-portfolio' ), + 'name' => $category_name . '_text_color', + 'alpha' => true, + 'style' => array( + array( + 'element' => '.vp-portfolio__items-style-' . $style_name, + 'property' => '--vp-items-style-' . $style_name . '--' . $category_name . '__color', + ), + ), + ); + } + + if ( $has_links ) { + $new_fields[] = array( + 'type' => 'color', + 'category' => 'items-style-' . $category_name . '-colors', + 'label' => esc_html__( 'Links', 'visual-portfolio' ), + 'name' => $category_name . '_links_color', + 'alpha' => true, + 'style' => array( + array( + 'element' => '.vp-portfolio__items-style-' . $style_name, + 'property' => '--vp-items-style-' . $style_name . '--' . $category_name . '-links__color', + ), + ), + ); + $new_fields[] = array( + 'type' => 'color', + 'category' => 'items-style-' . $category_name . '-colors', + 'label' => esc_html__( 'Links Hover', 'visual-portfolio' ), + 'name' => $category_name . '_links_hover_color', + 'alpha' => true, + 'style' => array( + array( + 'element' => '.vp-portfolio__items-style-' . $style_name, + 'property' => '--vp-items-style-' . $style_name . '--' . $category_name . '-links-hover__color', + ), + ), + ); + } + + // Mix Blend Mode Pro. + if ( $has_blend_mode ) { + $new_fields[] = array( + 'type' => 'pro_note', + 'category' => 'items-style-' . $category_name . '-colors', + 'name' => 'additional_' . $category_name . '_mix_blend_mode_skin_settings_pro', + 'label' => esc_html__( 'Premium Only', 'visual-portfolio' ), + 'description' => 'Select blend mode layer effects such as Normal, Multiply, Screen, Overlay, and more', + ); + } + } + + // Typography. + if ( ! empty( $options['typography'] ) ) { + $new_fields[] = array( + 'type' => 'pro_note', + 'category' => 'items-style-' . $category_name . '-typography', + 'name' => 'additional_' . $category_name . '_typography_skin_settings_pro', + 'label' => esc_html__( 'Premium Only', 'visual-portfolio' ), + 'description' => 'Manage fonts, sizing, and appearance of distinct text components within the Skin', + ); + } + + // Dimensions. + if ( ! empty( $options['dimensions'] ) ) { + $has_border_radius = isset( $options['dimensions']['border_radius'] ) && $options['dimensions']['border_radius']; + $has_padding = isset( $options['dimensions']['padding'] ) && $options['dimensions']['padding']; + $has_margin = isset( $options['dimensions']['margin'] ) && $options['dimensions']['margin']; + $has_items_gap = isset( $options['dimensions']['items_gap'] ) && $options['dimensions']['items_gap']; + + $dimensions_list = array(); + + if ( $has_border_radius ) { + $dimensions_list[] = 'border radius'; + } + if ( $has_padding ) { + $dimensions_list[] = 'padding'; + } + if ( $has_margin ) { + $dimensions_list[] = 'margin'; + } + if ( $has_items_gap ) { + $dimensions_list[] = 'gap between elements'; + } + + $new_fields[] = array( + 'type' => 'pro_note', + 'category' => 'items-style-' . $category_name . '-dimensions', + 'name' => 'additional_' . $category_name . '_dimensions_skin_settings_pro', + 'label' => esc_html__( 'Premium Only', 'visual-portfolio' ), + 'description' => 'Adjust element spacing and dimensions such as ' . implode( ', ', $dimensions_list ), + ); + } + break; + // no default. + } + + $builtin_fields = array_merge( $builtin_fields, apply_filters( 'vpf_items_style_builtin_controls', $new_fields, $category_name, $options, $style_name, $style ) ); + } + } + + $items_styles[ $style_name ]['controls'] = array_merge( $builtin_fields, isset( $style['controls'] ) ? $style['controls'] : array() ); + } + + // styles options. + foreach ( $items_styles as $style_name => $style ) { + if ( ! isset( $style['controls'] ) ) { + continue; + } + foreach ( $style['controls'] as $field ) { + $field['category'] = $field['category'] ?? 'items-style'; + $field['name'] = 'items_style_' . $style_name . '__' . $field['name']; + + // condition names prefix fix. + if ( isset( $field['condition'] ) ) { + $loop_over_conditions = function( $field_cond ) use ( &$loop_over_conditions, $style_name ) { + if ( is_array( $field_cond ) && ! isset( $field_cond['control'] ) ) { + foreach ( $field_cond as $k => $inner_cond ) { + $field_cond[ $k ] = $loop_over_conditions( $inner_cond ); + } + } elseif ( isset( $field_cond['control'] ) ) { + if ( strpos( $field_cond['control'], 'GLOBAL_' ) === 0 ) { + $field_cond['control'] = str_replace( 'GLOBAL_', '', $field_cond['control'] ); + } else { + $field_cond['control'] = 'items_style_' . $style_name . '__' . $field_cond['control']; + } + } + + return $field_cond; + }; + + $field['condition'] = $loop_over_conditions( $field['condition'] ); + } + + $field['condition'] = array_merge( + isset( $field['condition'] ) ? $field['condition'] : array(), + array( + array( + 'control' => 'items_style', + 'value' => $style_name, + ), + ) + ); + Visual_Portfolio_Controls::register( $field ); + } + } + + /** + * Items Click Action + */ + Visual_Portfolio_Controls::register( + array( + 'category' => 'items-click-action', + 'type' => 'icons_selector', + 'name' => 'items_click_action', + 'default' => 'url', + 'options' => array( + array( + 'value' => 'false', + 'title' => esc_html__( 'Disabled', 'visual-portfolio' ), + 'icon' => '', + ), + array( + 'value' => 'url', + 'title' => esc_html__( 'URL', 'visual-portfolio' ), + 'icon' => '', + ), + array( + 'value' => 'popup_gallery', + 'title' => esc_html__( 'Popup', 'visual-portfolio' ), + 'icon' => '', + ), + array( + 'value' => 'advanced', + 'title' => esc_html__( 'Advanced', 'visual-portfolio' ), + 'icon' => '', + ), + ), + ) + ); + + // url. + Visual_Portfolio_Controls::register( + array( + 'category' => 'items-click-action', + 'type' => 'radio', + 'label' => esc_html__( 'Target', 'visual-portfolio' ), + 'name' => 'items_click_action_url_target', + 'group' => 'items_click_action_target', + 'default' => '', + 'reload_iframe' => false, + 'options' => array( + '' => esc_html__( 'Default', 'visual-portfolio' ), + '_blank' => esc_html__( 'New Tab (_blank)', 'visual-portfolio' ), + '_top' => esc_html__( 'Top Frame (_top)', 'visual-portfolio' ), + ), + 'condition' => array( + array( + 'control' => 'items_click_action', + 'value' => 'url', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'items-click-action', + 'type' => 'text', + 'label' => esc_html__( 'Rel', 'visual-portfolio' ), + 'name' => 'items_click_action_url_rel', + 'group' => 'items_click_action_target', + 'default' => '', + 'reload_iframe' => false, + 'condition' => array( + array( + 'control' => 'items_click_action', + 'value' => 'url', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'items-click-action', + 'type' => 'pro_note', + 'name' => 'items_click_action_url_pro_note', + 'label' => esc_html__( 'Premium Only', 'visual-portfolio' ), + 'description' => '

' . esc_html__( 'Link URL click priority', 'visual-portfolio' ) . '

', + 'condition' => array( + array( + 'control' => 'items_click_action', + 'value' => 'url', + ), + ), + ) + ); + + // popup. + Visual_Portfolio_Controls::register( + array( + 'category' => 'items-click-action', + 'type' => 'select', + 'label' => esc_html__( 'Title Source', 'visual-portfolio' ), + 'name' => 'items_click_action_popup_title_source', + 'group' => 'popup_title_source', + 'default' => 'title', + 'reload_iframe' => false, + 'options' => array( + 'none' => esc_html__( 'None', 'visual-portfolio' ), + 'title' => esc_html__( 'Image Title', 'visual-portfolio' ), + 'caption' => esc_html__( 'Image Caption', 'visual-portfolio' ), + 'alt' => esc_html__( 'Image Alt', 'visual-portfolio' ), + 'description' => esc_html__( 'Image Description', 'visual-portfolio' ), + 'item_title' => esc_html__( 'Item Title', 'visual-portfolio' ), + 'item_description' => esc_html__( 'Item Description', 'visual-portfolio' ), + 'item_author' => esc_html__( 'Item Author', 'visual-portfolio' ), + ), + 'condition' => array( + array( + 'control' => 'items_click_action', + 'value' => 'popup_gallery', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'items-click-action', + 'type' => 'select', + 'label' => esc_html__( 'Description Source', 'visual-portfolio' ), + 'name' => 'items_click_action_popup_description_source', + 'group' => 'popup_title_source', + 'default' => 'description', + 'reload_iframe' => false, + 'options' => array( + 'none' => esc_html__( 'None', 'visual-portfolio' ), + 'title' => esc_html__( 'Image Title', 'visual-portfolio' ), + 'caption' => esc_html__( 'Image Caption', 'visual-portfolio' ), + 'alt' => esc_html__( 'Image Alt', 'visual-portfolio' ), + 'description' => esc_html__( 'Image Description', 'visual-portfolio' ), + 'item_title' => esc_html__( 'Item Title', 'visual-portfolio' ), + 'item_description' => esc_html__( 'Item Description', 'visual-portfolio' ), + 'item_author' => esc_html__( 'Item Author', 'visual-portfolio' ), + ), + 'condition' => array( + array( + 'control' => 'items_click_action', + 'value' => 'popup_gallery', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'items-click-action', + 'type' => 'pro_note', + 'name' => 'items_click_action_popup_pro_note', + 'label' => esc_html__( 'Premium Only', 'visual-portfolio' ), + 'description' => '
    +
  • ' . esc_html__( 'Manage media object priority', 'visual-portfolio' ) . '
  • +
  • ' . esc_html__( 'Quick View for posts and pages', 'visual-portfolio' ) . '
  • +
  • ' . esc_html__( 'Popup media item deep linking', 'visual-portfolio' ) . '
  • +
  • ' . esc_html__( 'etc...', 'visual-portfolio' ) . '
  • +
', + 'condition' => array( + array( + 'control' => 'items_click_action', + 'value' => 'popup_gallery', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'items-click-action', + 'type' => 'pro_note', + 'name' => 'items_click_action_advanced_pro_note', + 'label' => esc_html__( 'Premium Only', 'visual-portfolio' ), + 'description' => '

' . esc_html__( 'Deeply customize actions of clicks on different types of items and links.', 'visual-portfolio' ) . '

', + 'condition' => array( + array( + 'control' => 'items_click_action', + 'value' => 'advanced', + ), + ), + ) + ); + + /** + * Layout Elements. + */ + Visual_Portfolio_Controls::register( + array( + 'category' => 'layout-elements', + 'type' => 'elements_selector', + 'name' => 'layout_elements', + 'locations' => array( + 'top' => array( + 'title' => esc_html__( 'Start', 'visual-portfolio' ), + 'align' => array( + 'left', + 'center', + 'right', + 'between', + ), + ), + 'items' => array( + 'title' => esc_html__( 'Middle', 'visual-portfolio' ), + ), + 'bottom' => array( + 'title' => esc_html__( 'End', 'visual-portfolio' ), + 'align' => array( + 'left', + 'center', + 'right', + 'between', + ), + ), + ), + 'default' => array( + 'top' => array( + 'elements' => array(), + 'align' => 'center', + ), + 'items' => array( + 'elements' => array( 'items' ), + ), + 'bottom' => array( + 'elements' => array(), + 'align' => 'center', + ), + ), + 'options' => array( + 'filter' => array( + 'title' => esc_html__( 'Filter', 'visual-portfolio' ), + 'allowed_locations' => array( 'top' ), + 'category' => 'filter', + 'render_callback' => 'Visual_Portfolio_Get::filter', + ), + 'sort' => array( + 'title' => esc_html__( 'Sort', 'visual-portfolio' ), + 'allowed_locations' => array( 'top' ), + 'category' => 'sort', + 'render_callback' => 'Visual_Portfolio_Get::sort', + ), + 'search' => array( + 'title' => esc_html__( 'Search', 'visual-portfolio' ), + 'allowed_locations' => array( 'top' ), + 'category' => 'search', + 'is_pro' => true, + ), + 'items' => array( + 'title' => esc_html__( 'Layout Items', 'visual-portfolio' ), + 'allowed_locations' => array( 'items' ), + 'category' => 'layouts', + ), + 'pagination' => array( + 'title' => esc_html__( 'Pagination', 'visual-portfolio' ), + 'allowed_locations' => array( 'bottom' ), + 'category' => 'pagination', + 'render_callback' => 'Visual_Portfolio_Get::pagination', + ), + ), + ) + ); + + /** + * Filter. + */ + $filters = array_merge( + array( + // Minimal. + 'minimal' => array( + 'title' => esc_html__( 'Minimal', 'visual-portfolio' ), + 'icon' => '', + 'controls' => array(), + ), + + // Classic. + 'default' => array( + 'title' => esc_html__( 'Classic', 'visual-portfolio' ), + 'icon' => '', + 'controls' => array(), + ), + + // Dropdown. + 'dropdown' => array( + 'title' => esc_html__( 'Dropdown', 'visual-portfolio' ), + 'icon' => '', + 'controls' => array(), + ), + ), + // phpcs:ignore Squiz.PHP.CommentedOutCode.Found, Squiz.Commenting.BlockComment.NoEmptyLineBefore + /* + * Example: + array( + 'new_filter' => array( + 'title' => esc_html__( 'New Filter', 'visual-portfolio' ), + 'controls' => array( + ... controls ... + ), + ), + ) + */ + apply_filters( 'vpf_extend_filters', array() ) + ); + + // Extend specific filter controls. + foreach ( $filters as $name => $filter ) { + if ( isset( $filter['controls'] ) ) { + // phpcs:ignore Squiz.PHP.CommentedOutCode.Found, Squiz.Commenting.BlockComment.NoEmptyLineBefore + /* + * Example: + array( + ... controls ... + ) + */ + $filters[ $name ]['controls'] = apply_filters( 'vpf_extend_filter_' . $name . '_controls', $filter['controls'] ); + } + } + + // Filters selector. + $filters_selector = array(); + foreach ( $filters as $name => $filter ) { + $filters_selector[] = array( + 'value' => $name, + 'title' => $filter['title'], + 'icon' => isset( $filter['icon'] ) ? $filter['icon'] : '', + ); + } + Visual_Portfolio_Controls::register( + array( + 'category' => 'filter', + 'type' => 'icons_selector', + 'name' => 'filter', + 'default' => 'minimal', + 'options' => $filters_selector, + 'setup_wizard' => true, + ) + ); + + // filters options. + foreach ( $filters as $name => $filter ) { + if ( ! isset( $filter['controls'] ) ) { + continue; + } + foreach ( $filter['controls'] as $field ) { + $field['category'] = 'filter'; + $field['name'] = 'filter_' . $name . '__' . $field['name']; + + // condition names prefix fix. + if ( isset( $field['condition'] ) ) { + foreach ( $field['condition'] as $k => $cond ) { + if ( isset( $cond['control'] ) ) { + if ( strpos( $cond['control'], 'GLOBAL_' ) === 0 ) { + $field['condition'][ $k ]['control'] = str_replace( 'GLOBAL_', '', $cond['control'] ); + } else { + $field['condition'][ $k ]['control'] = $name . '_' . $cond['control']; + } + } + } + } + + $field['condition'] = array_merge( + isset( $field['condition'] ) ? $field['condition'] : array(), + array( + array( + 'control' => 'filter', + 'value' => $name, + ), + ) + ); + Visual_Portfolio_Controls::register( $field ); + } + } + + Visual_Portfolio_Controls::register( + array( + 'category' => 'filter', + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Display Count', 'visual-portfolio' ), + 'name' => 'filter_show_count', + 'default' => false, + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'filter', + 'type' => 'text', + 'label' => esc_html__( 'All Button Text', 'visual-portfolio' ), + 'name' => 'filter_text_all', + 'default' => esc_attr__( 'All', 'visual-portfolio' ), + 'wpml' => true, + ) + ); + + /** + * Sort. + */ + $sorts = array_merge( + array( + // Minimal. + 'minimal' => array( + 'title' => esc_html__( 'Minimal', 'visual-portfolio' ), + 'icon' => '', + 'controls' => array(), + ), + + // Classic. + 'default' => array( + 'title' => esc_html__( 'Classic', 'visual-portfolio' ), + 'icon' => '', + 'controls' => array(), + ), + + // Dropdown. + 'dropdown' => array( + 'title' => esc_html__( 'Dropdown', 'visual-portfolio' ), + 'icon' => '', + 'controls' => array(), + ), + ), + // phpcs:ignore Squiz.PHP.CommentedOutCode.Found, Squiz.Commenting.BlockComment.NoEmptyLineBefore + /* + * Example: + array( + 'new_sort' => array( + 'title' => esc_html__( 'New Sort', 'visual-portfolio' ), + 'controls' => array( + ... controls ... + ), + ), + ) + */ + apply_filters( 'vpf_extend_sort', array() ) + ); + + // Extend specific sort controls. + foreach ( $sorts as $name => $sort ) { + if ( isset( $sort['controls'] ) ) { + // phpcs:ignore Squiz.PHP.CommentedOutCode.Found, Squiz.Commenting.BlockComment.NoEmptyLineBefore + /* + * Example: + array( + ... controls ... + ) + */ + $sorts[ $name ]['controls'] = apply_filters( 'vpf_extend_sort_' . $name . '_controls', $sort['controls'] ); + } + } + + // Sort selector. + $sorts_selector = array(); + foreach ( $sorts as $name => $sort ) { + $sorts_selector[ $name ] = array( + 'value' => $name, + 'title' => $sort['title'], + 'icon' => isset( $sort['icon'] ) ? $sort['icon'] : '', + ); + } + Visual_Portfolio_Controls::register( + array( + 'category' => 'sort', + 'type' => 'icons_selector', + 'name' => 'sort', + 'default' => 'dropdown', + 'options' => $sorts_selector, + ) + ); + + // sorts options. + foreach ( $sorts as $name => $sort ) { + if ( ! isset( $sort['controls'] ) ) { + continue; + } + foreach ( $sort['controls'] as $field ) { + $field['category'] = 'sort'; + $field['name'] = 'sort_' . $name . '__' . $field['name']; + + // condition names prefix fix. + if ( isset( $field['condition'] ) ) { + foreach ( $field['condition'] as $k => $cond ) { + if ( isset( $cond['control'] ) ) { + if ( strpos( $cond['control'], 'GLOBAL_' ) === 0 ) { + $field['condition'][ $k ]['control'] = str_replace( 'GLOBAL_', '', $cond['control'] ); + } else { + $field['condition'][ $k ]['control'] = $name . '_' . $cond['control']; + } + } + } + } + + $field['condition'] = array_merge( + isset( $field['condition'] ) ? $field['condition'] : array(), + array( + array( + 'control' => 'sort', + 'value' => $name, + ), + ) + ); + Visual_Portfolio_Controls::register( $field ); + } + } + + /** + * Search + */ + Visual_Portfolio_Controls::register( + array( + 'category' => 'search', + 'type' => 'pro_note', + 'name' => 'search_pro_note', + 'label' => esc_html__( 'Premium Only', 'visual-portfolio' ), + 'description' => '

' . esc_html__( 'The search module is only available for Pro users.', 'visual-portfolio' ) . '

', + ) + ); + + /** + * Pagination + */ + $pagination = array_merge( + array( + // Minimal. + 'minimal' => array( + 'title' => esc_html__( 'Minimal', 'visual-portfolio' ), + 'icon' => '', + 'controls' => array(), + ), + + // Classic. + 'default' => array( + 'title' => esc_html__( 'Classic', 'visual-portfolio' ), + 'icon' => '', + 'controls' => array(), + ), + ), + // phpcs:ignore Squiz.PHP.CommentedOutCode.Found, Squiz.Commenting.BlockComment.NoEmptyLineBefore + /* + * Example: + array( + 'new_pagination' => array( + 'title' => esc_html__( 'New Pagination', 'visual-portfolio' ), + 'controls' => array( + ... controls ... + ), + ), + ) + */ + apply_filters( 'vpf_extend_pagination', array() ) + ); + + // Extend specific pagination controls. + foreach ( $pagination as $name => $pagin ) { + if ( isset( $pagin['controls'] ) ) { + // phpcs:ignore Squiz.PHP.CommentedOutCode.Found, Squiz.Commenting.BlockComment.NoEmptyLineBefore + /* + * Example: + array( + ... controls ... + ) + */ + $pagination[ $name ]['controls'] = apply_filters( 'vpf_extend_pagination_' . $name . '_controls', $pagin['controls'] ); + } + } + + // Pagination selector. + $pagination_selector = array(); + foreach ( $pagination as $name => $pagin ) { + $pagination_selector[ $name ] = array( + 'value' => $name, + 'title' => $pagin['title'], + 'icon' => isset( $pagin['icon'] ) ? $pagin['icon'] : '', + ); + } + Visual_Portfolio_Controls::register( + array( + 'category' => 'pagination', + 'type' => 'icons_selector', + 'name' => 'pagination_style', + 'default' => 'minimal', + 'options' => $pagination_selector, + ) + ); + + // pagination options. + foreach ( $pagination as $name => $pagin ) { + if ( ! isset( $pagin['controls'] ) ) { + continue; + } + foreach ( $pagin['controls'] as $field ) { + $field['category'] = 'pagination'; + $field['name'] = 'pagination_' . $name . '__' . $field['name']; + + // condition names prefix fix. + if ( isset( $field['condition'] ) ) { + foreach ( $field['condition'] as $k => $cond ) { + if ( isset( $cond['control'] ) ) { + $field['condition'][ $k ]['control'] = $name . '_' . $cond['control']; + } + } + } + + $field['condition'] = array_merge( + isset( $field['condition'] ) ? $field['condition'] : array(), + array( + array( + 'control' => 'pagination_style', + 'value' => $name, + ), + ) + ); + Visual_Portfolio_Controls::register( $field ); + } + } + + Visual_Portfolio_Controls::register( + array( + 'category' => 'pagination', + 'label' => esc_html__( 'Type', 'visual-portfolio' ), + 'type' => 'icons_selector', + 'name' => 'pagination', + 'default' => 'load-more', + 'options' => array( + array( + 'value' => 'paged', + 'title' => esc_html__( 'Paged', 'visual-portfolio' ), + 'icon' => '', + ), + array( + 'value' => 'load-more', + 'title' => esc_html__( 'Load More', 'visual-portfolio' ), + 'icon' => '', + ), + array( + 'value' => 'infinite', + 'title' => esc_html__( 'Infinite', 'visual-portfolio' ), + 'icon' => '', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'pagination', + 'type' => 'html', + 'description' => esc_html__( 'Note: you will see the "Load More" pagination in the preview. "Infinite" pagination will be visible on the site.', 'visual-portfolio' ), + 'name' => 'pagination_infinite_notice', + 'condition' => array( + array( + 'control' => 'pagination', + 'operator' => '==', + 'value' => 'infinite', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'pagination', + 'type' => 'text', + 'label' => esc_html__( 'Texts', 'visual-portfolio' ), + 'name' => 'pagination_infinite_text_load', + 'group' => 'pagination_texts', + 'default' => esc_attr__( 'Load More', 'visual-portfolio' ), + 'description' => esc_attr__( 'Load more button label', 'visual-portfolio' ), + 'wpml' => true, + 'condition' => array( + array( + 'control' => 'pagination', + 'operator' => '==', + 'value' => 'infinite', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'pagination', + 'type' => 'text', + 'name' => 'pagination_infinite_text_loading', + 'group' => 'pagination_texts', + 'default' => esc_attr__( 'Loading More...', 'visual-portfolio' ), + 'description' => esc_attr__( 'Loading more button label', 'visual-portfolio' ), + 'wpml' => true, + 'condition' => array( + array( + 'control' => 'pagination', + 'operator' => '==', + 'value' => 'infinite', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'pagination', + 'type' => 'textarea', + 'name' => 'pagination_infinite_text_end_list', + 'group' => 'pagination_texts', + 'default' => esc_attr__( 'You’ve reached the end of the list', 'visual-portfolio' ), + 'description' => esc_attr__( 'End of the list text', 'visual-portfolio' ), + 'wpml' => true, + 'condition' => array( + array( + 'control' => 'pagination', + 'operator' => '==', + 'value' => 'infinite', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'pagination', + 'type' => 'text', + 'label' => esc_html__( 'Texts', 'visual-portfolio' ), + 'name' => 'pagination_load_more_text_load', + 'group' => 'pagination_texts', + 'default' => esc_attr__( 'Load More', 'visual-portfolio' ), + 'description' => esc_attr__( 'Load more button label', 'visual-portfolio' ), + 'wpml' => true, + 'condition' => array( + array( + 'control' => 'pagination', + 'operator' => '==', + 'value' => 'load-more', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'pagination', + 'type' => 'text', + 'name' => 'pagination_load_more_text_loading', + 'group' => 'pagination_texts', + 'default' => esc_attr__( 'Loading More...', 'visual-portfolio' ), + 'description' => esc_attr__( 'Loading more button label', 'visual-portfolio' ), + 'wpml' => true, + 'condition' => array( + array( + 'control' => 'pagination', + 'operator' => '==', + 'value' => 'load-more', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'pagination', + 'type' => 'textarea', + 'name' => 'pagination_load_more_text_end_list', + 'group' => 'pagination_texts', + 'default' => esc_attr__( 'You’ve reached the end of the list', 'visual-portfolio' ), + 'description' => esc_attr__( 'End of the list text', 'visual-portfolio' ), + 'wpml' => true, + 'condition' => array( + array( + 'control' => 'pagination', + 'operator' => '==', + 'value' => 'load-more', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'pagination', + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Display Arrows', 'visual-portfolio' ), + 'name' => 'pagination_paged__show_arrows', + 'default' => true, + 'condition' => array( + array( + 'control' => 'pagination', + 'value' => 'paged', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'pagination', + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Display Numbers', 'visual-portfolio' ), + 'name' => 'pagination_paged__show_numbers', + 'default' => true, + 'condition' => array( + array( + 'control' => 'pagination', + 'value' => 'paged', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'pagination', + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Scroll to Top', 'visual-portfolio' ), + 'name' => 'pagination_paged__scroll_top', + 'group' => 'pagination_scroll_top', + 'default' => true, + 'reload_iframe' => false, + 'condition' => array( + array( + 'control' => 'pagination', + 'value' => 'paged', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'pagination', + 'type' => 'number', + 'label' => esc_html__( 'Scroll to Top Offset', 'visual-portfolio' ), + 'name' => 'pagination_paged__scroll_top_offset', + 'group' => 'pagination_scroll_top', + 'default' => 30, + 'reload_iframe' => false, + 'condition' => array( + array( + 'control' => 'pagination', + 'value' => 'paged', + ), + array( + 'control' => 'pagination_paged__scroll_top', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'pagination', + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Hide on Reached End', 'visual-portfolio' ), + 'name' => 'pagination_hide_on_end', + 'default' => false, + 'condition' => array( + array( + 'control' => 'pagination', + 'operator' => '!=', + 'value' => 'paged', + ), + ), + ) + ); + Visual_Portfolio_Controls::register( + array( + 'category' => 'pagination', + 'type' => 'pro_note', + 'name' => 'pagination_infinite_additional_options_pro_note', + 'label' => esc_html__( 'Premium Only', 'visual-portfolio' ), + 'description' => '

' . esc_html__( 'Adjust the loading threshold, limit the number of automatic loads and run the infinite scroll only after the Load button click.', 'visual-portfolio' ) . '

', + 'condition' => array( + array( + 'control' => 'pagination', + 'operator' => '==', + 'value' => 'infinite', + ), + ), + ) + ); + + /** + * Code Editor + */ + Visual_Portfolio_Controls::register( + array( + 'category' => 'custom_css', + 'type' => 'code_editor', + 'name' => 'custom_css', + 'max_lines' => 20, + 'min_lines' => 5, + 'mode' => 'css', + 'allow_modal' => true, + 'classes_tree' => true, + 'encode' => true, + 'reload_iframe' => false, + 'code_placeholder' => "selector {\n\n}", + 'default' => '', + 'description' => '

+

' . wp_kses_post( __( 'Use selector rule to change block styles.', 'visual-portfolio' ) ) . '

+

' . esc_html__( 'Example:', 'visual-portfolio' ) . '

+
+selector {
+    background-color: #5C39A7;
+}
+
+selector p {
+    color: #5C39A7;
+}
+
', + ) + ); + + do_action( 'vpf_after_register_controls' ); + } + + /** + * Find post types options for control. + * + * @return array + */ + public function find_post_types_options() { + check_ajax_referer( 'vp-ajax-nonce', 'nonce' ); + + // post types list. + $post_types = get_post_types( + array( + 'public' => false, + 'name' => 'attachment', + ), + 'names', + 'NOT' + ); + + $post_types_selector = array(); + if ( is_array( $post_types ) && ! empty( $post_types ) ) { + foreach ( $post_types as $post_type ) { + $post_types_selector[ $post_type ] = array( + 'value' => $post_type, + 'title' => ucfirst( $post_type ), + 'icon' => '', + ); + } + } + $post_types_selector['post_types_set'] = array( + 'value' => 'post_types_set', + 'title' => esc_html__( 'Post Types Set', 'visual-portfolio' ), + 'icon' => '', + ); + $post_types_selector['ids'] = array( + 'value' => 'ids', + 'title' => esc_html__( 'Manual Selection', 'visual-portfolio' ), + 'icon' => '', + ); + $post_types_selector['custom_query'] = array( + 'value' => 'custom_query', + 'title' => esc_html__( 'Custom Query', 'visual-portfolio' ), + 'icon' => '', + ); + $post_types_selector['current_query'] = array( + 'value' => 'current_query', + 'title' => esc_html__( 'Current Query', 'visual-portfolio' ), + 'icon' => '', + ); + + return array( + 'options' => $post_types_selector, + ); + } + + /** + * Find post types for select control. + * + * @return array + */ + public function find_posts_types_select_control() { + check_ajax_referer( 'vp-ajax-nonce', 'nonce' ); + + $result = array(); + + // post types list. + $post_types = get_post_types( + array( + 'public' => false, + 'name' => 'attachment', + ), + 'names', + 'NOT' + ); + + if ( is_array( $post_types ) && ! empty( $post_types ) ) { + $result['options'] = array(); + + foreach ( $post_types as $post_type ) { + $result['options'][ $post_type ] = array( + 'value' => $post_type, + 'label' => ucfirst( $post_type ), + ); + } + } + + return $result; + } + + /** + * Find posts for select control. + * + * @param array $attributes - current block attributes. + * @param array $control - current control. + * + * @return array + */ + public function find_posts_select_control( $attributes, $control ) { + check_ajax_referer( 'vp-ajax-nonce', 'nonce' ); + + $result = array(); + + // get selected options. + $selected_ids = isset( $attributes[ $control['name'] ] ) ? $attributes[ $control['name'] ] : array(); + + if ( ! isset( $_POST['q'] ) && empty( $selected_ids ) ) { + return $result; + } + + $post_type = isset( $attributes['posts_source'] ) ? sanitize_text_field( wp_unslash( $attributes['posts_source'] ) ) : 'any'; + + if ( ! $post_type || 'post_types_set' === $post_type || 'custom_query' === $post_type || 'ids' === $post_type ) { + $post_type = 'any'; + } + + if ( isset( $_POST['q'] ) ) { + $the_query = new WP_Query( + array( + 's' => sanitize_text_field( wp_unslash( $_POST['q'] ) ), + 'posts_per_page' => 50, + 'post_type' => $post_type, + ) + ); + } else { + $the_query = new WP_Query( + array( + 'post__in' => $selected_ids, + 'posts_per_page' => 50, + 'post_type' => $post_type, + ) + ); + } + + if ( $the_query->have_posts() ) { + $result['options'] = array(); + + while ( $the_query->have_posts() ) { + $the_query->the_post(); + $result['options'][ (string) get_the_ID() ] = array( + 'value' => (string) get_the_ID(), + 'label' => get_the_title(), + 'img' => get_the_post_thumbnail_url( null, 'thumbnail' ), + 'category' => get_post_type( get_the_ID() ), + ); + } + $the_query->reset_postdata(); + } + + return $result; + } + + /** + * Find taxonomies for select control. + * + * @param array $attributes - current block attributes. + * @param array $control - current control. + * + * @return array + */ + public function find_taxonomies_select_control( $attributes, $control ) { + check_ajax_referer( 'vp-ajax-nonce', 'nonce' ); + + $result = array(); + + // get selected options. + $selected_ids = isset( $attributes[ $control['name'] ] ) ? $attributes[ $control['name'] ] : array(); + + if ( ! isset( $_POST['q'] ) && empty( $selected_ids ) ) { + return $result; + } + + if ( isset( $_POST['q'] ) ) { + $post_type = isset( $_POST['post_type'] ) ? sanitize_text_field( wp_unslash( $_POST['post_type'] ) ) : 'any'; + + if ( ! $post_type || 'post_types_set' === $post_type || 'custom_query' === $post_type || 'ids' === $post_type ) { + $post_type = 'any'; + } + + // get taxonomies for selected post type or all available. + if ( 'any' === $post_type ) { + $post_type = get_post_types( + array( + 'public' => false, + 'name' => 'attachment', + ), + 'names', + 'NOT' + ); + } + + $taxonomies_names = get_object_taxonomies( $post_type ); + + $the_query = new WP_Term_Query( + array( + 'taxonomy' => $taxonomies_names, + 'hide_empty' => false, + 'search' => sanitize_text_field( wp_unslash( $_POST['q'] ) ), + ) + ); + } else { + $the_query = new WP_Term_Query( + array( + 'include' => $selected_ids, + 'hide_empty' => false, + ) + ); + } + + if ( ! empty( $the_query->terms ) ) { + $result['options'] = array(); + + foreach ( $the_query->terms as $term ) { + $result['options'][ (string) $term->term_id ] = array( + 'value' => (string) $term->term_id, + 'label' => $term->name, + 'category' => $term->taxonomy, + ); + } + } + + return $result; + } + + /** + * Find taxonomies ajax + */ + public function ajax_find_oembed() { + check_ajax_referer( 'vp-ajax-nonce', 'nonce' ); + if ( ! isset( $_POST['q'] ) ) { + wp_die(); + } + + $oembed = visual_portfolio()->get_oembed_data( sanitize_text_field( wp_unslash( $_POST['q'] ) ) ); + + if ( ! isset( $oembed ) || ! $oembed || ! isset( $oembed['html'] ) ) { + wp_die(); + } + + echo wp_json_encode( $oembed ); + + wp_die(); + } +} + +new Visual_Portfolio_Admin(); diff --git a/classes/class-archive-mapping.php b/classes/class-archive-mapping.php new file mode 100644 index 00000000..e72a44ba --- /dev/null +++ b/classes/class-archive-mapping.php @@ -0,0 +1,1251 @@ +archive_page = Settings::get_option( 'portfolio_archive_page', 'vp_general' ); + $this->posts_per_page = Settings::get_option( 'archive_page_items_per_page', 'vp_general' ); + $this->permalinks = self::get_permalink_structure(); + + if ( isset( $this->archive_page ) && ! empty( $this->archive_page ) && Visual_Portfolio_Custom_Post_Type::portfolio_post_type_is_registered() ) { + + $this->init_rewrite_rules(); + + if ( -1 === (int) $this->posts_per_page ) { + $this->posts_per_page = 9999; + } + + add_action( 'pre_get_posts', array( $this, 'maybe_override_archive' ) ); + add_action( 'pre_post_update', array( $this, 'pre_page_update' ), 10, 2 ); + add_action( 'deleted_post', array( $this, 'delete_archive_page' ), 10, 1 ); + add_action( 'trashed_post', array( $this, 'delete_archive_page' ), 10, 1 ); + add_action( 'update_option_vp_general', array( $this, 'flush_rewrite_rules_after_update' ), 10, 3 ); + add_action( 'update_option_page_on_front', array( $this, 'flush_rewrite_rules_after_update_front_page' ), 10, 3 ); + add_action( 'vpf_extend_query_args', array( $this, 'extend_query_args' ), 10, 2 ); + add_filter( 'vpf_layout_element_options', array( $this, 'unset_pagination_archive_page' ), 10, 1 ); + + // Add a post display state for special Portfolio Archive page. + add_filter( 'display_post_states', array( $this, 'add_display_post_states' ), 10, 2 ); + + // Add Permalinks. + add_action( 'admin_init', array( $this, 'permalink_settings_init' ) ); + add_action( 'admin_init', array( $this, 'permalink_settings_save' ), 12 ); + add_filter( 'post_type_link', array( $this, 'portfolio_permalink_replacements' ), 1, 2 ); + add_filter( 'vpf_extend_filter_items', array( $this, 'add_filter_items' ), 10, 2 ); + add_filter( 'the_title', array( $this, 'set_archive_title' ), 10, 2 ); + add_filter( 'body_class', array( $this, 'add_body_archive_classes' ), 10, 1 ); + add_filter( 'redirect_canonical', array( $this, 'maybe_redirect_canonical_links' ), 10, 2 ); + add_filter( 'pre_get_shortlink', array( $this, 'remove_taxanomy_shortlinks' ), 10, 3 ); + add_filter( 'vpf_extend_portfolio_data_attributes', array( $this, 'converting_data_next_page_to_friendly_url' ), 10, 2 ); + add_filter( 'vpf_pagination_item_data', array( $this, 'converting_paginate_links_to_friendly_url' ), 10, 3 ); + add_filter( 'vpf_pagination_args', array( $this, 'converting_load_more_and_infinite_paginate_next_page_to_friendly_url' ), 10, 2 ); + add_filter( 'vpf_extend_sort_item_url', array( $this, 'remove_page_url_from_sort_item_url' ), 10, 3 ); + } + + self::create_archive_page(); + } + + /** + * Remove page url from sort item url. + * + * @param string $url - Sort url. + * @param string $slug - Sort slug. + * @param array $vp_options - Block Options. + * @return string + */ + public function remove_page_url_from_sort_item_url( $url, $slug, $vp_options ) { + if ( + isset( $_REQUEST['vp_preview_post_id'] ) && + ! empty( $_REQUEST['vp_preview_post_id'] ) && + isset( $_REQUEST['vp_preview_nonce'] ) && + ! empty( $_REQUEST['vp_preview_nonce'] ) && + wp_verify_nonce( sanitize_key( $_REQUEST['vp_preview_nonce'] ), 'vp-ajax-nonce' ) + ) { + $post_id = intval( $_REQUEST['vp_preview_post_id'] ); + } + + if ( self::is_archive( $vp_options, $post_id ?? null ) ) { + $url = $this->remove_page_url( $url ); + } + + return $url; + } + + /** + * Remove /page/ from url. + * + * @param string $url - uncleared url. + * @return string + */ + private function remove_page_url( $url ) { + // Clear pagination parameters from filter link. + preg_match( '/page\/(\d+)\//', $url, $match_page ); + + if ( is_array( $match_page ) && ! empty( $match_page ) ) { + $url = str_replace( $match_page[0], '', $url ); + } + return $url; + } + + /** + * Converting next page attribute to friendly URL. + * + * @param array $args - Block Arguments. + * @param array $options - Block Options. + * @param string $next_page_attribute - Name of converting Attribute. + * @return array + */ + private function converting_next_page_to_friendly_url( $args, $options, $next_page_attribute = 'next_page_url' ) { + // Determine if a page is an archive. + if ( + self::is_archive( $options ) && + isset( $args[ $next_page_attribute ] ) + ) { + global $wp_query; + $current_page = $wp_query->query['paged'] ?? $wp_query->query['vp_page_query'] ?? 1; + $next_page_url = $args[ $next_page_attribute ]; + $next_page = (int) $current_page === $options['max_pages'] ? false : ( $current_page ? $current_page + 1 : false ); + + $next_page_url = $this->converting_paginate_link_to_friendly_url( $next_page_url, $next_page ); + + $args[ $next_page_attribute ] = $next_page ? $next_page_url : false; + } + + return $args; + } + + /** + * Converting next page for Load More and Infinite on archive page to friendly URL. + * + * @param array $args - Block Arguments. + * @param array $vp_options - Block Options. + * @return array + */ + public function converting_load_more_and_infinite_paginate_next_page_to_friendly_url( $args, $vp_options ) { + if ( 'infinite' === $vp_options['pagination'] || 'load-more' === $vp_options['pagination'] ) { + $args = $this->converting_next_page_to_friendly_url( $args, $vp_options ); + } + return $args; + } + + /** + * Converting pagination archive links to friendly URL. + * + * @param array $arr - Array with paginate arguments. + * @param array $args - Block Arguments. + * @param array $vp_options - Block Options. + * @return array + */ + public function converting_paginate_links_to_friendly_url( $arr, $args, $vp_options ) { + // Determine if a page is an archive. + if ( + self::is_archive( $vp_options ) && + isset( $arr ) && + ! empty( $arr ) + ) { + if ( $arr['url'] ) { + // Parsing the content of links. + preg_match( '/vp_filter=([^&]*)/', $arr['url'], $match_filter ); + preg_match( '/' . $this->permalinks['category_base'] . '\/[^\/]+\//', $arr['url'], $match_category ); + /** + * Change category links to friendly. + * For example, a link like: example.com/?vp_filter=portfolio_category:test + * Has been converted to: example.com/portfolio-category/test/ + * In this case, the path to the category is determined by the permalink settings. + */ + if ( is_array( $match_filter ) && ! empty( $match_filter ) ) { + $taxonomies = explode( ':', rawurldecode( $match_filter[1] ) ); + if ( is_array( $taxonomies ) && 'portfolio_category' === $taxonomies[0] ) { + $category_slug = $taxonomies[1]; + $base_page = $this->get_relative_archive_link(); + $base_page = ( '' === $base_page || '/' ) ? '/?' : '/' . $base_page . '?'; + $arr['url'] = str_replace( $base_page, '/' . $this->permalinks['category_base'] . '/' . $category_slug . '/?', $arr['url'] ); + } + } + + $arr['url'] = $this->converting_paginate_link_to_friendly_url( $arr['url'] ); + + // Clear vp_filter GET variable in the link. + if ( strpos( $arr['url'], 'vp_filter' ) !== false ) { + $arr['url'] = remove_query_arg( 'vp_filter', $arr['url'] ); + } + } + } + return $arr; + } + + /** + * Converting data next page on archive page to friendly URL. + * + * @param array $data_attrs - Data Block Attributes. + * @param array $options - Block Options. + * @return array + */ + public function converting_data_next_page_to_friendly_url( $data_attrs, $options ) { + return $this->converting_next_page_to_friendly_url( $data_attrs, $options, 'data-vp-next-page-url' ); + } + + /** + * Remove Taxonomy Shortlinks. + * + * @param bool|string $shortlink - Short-circuit return value. Either false or a URL string. + * @param int $id - Post ID, or 0 for the current post. + * @param string $context - The context for the link. One of 'post' or 'query'. + * @return bool|string + */ + public function remove_taxanomy_shortlinks( $shortlink, $id, $context ) { + if ( 0 === $id && 'query' === $context && ! $shortlink ) { + $shortlink = $this->remove_taxanomy_shortlink_by_slug( get_query_var( 'vp_category' ) ) ?? + $this->remove_taxanomy_shortlink_by_slug( get_query_var( 'portfolio_tag' ), 'portfolio_tag' ) ?? + false; + } + + return $shortlink; + } + + /** + * Remove Taxonomy Shortlink by Taxonomy slug. + * + * @param string $slug - Taxonomy slug. + * @param string $taxonomy - Name of Taxonomy. + * @param bool|string $shortlink - Short-circuit return value. Either false or a URL string. + * @return bool|string + */ + private function remove_taxanomy_shortlink_by_slug( $slug, $taxonomy = 'portfolio_category', $shortlink = false ) { + if ( $slug && ! empty( $slug ) ) { + $terms = get_terms( + array( + 'slug' => $slug, + ) + ); + if ( ! empty( $terms ) && is_array( $terms ) ) { + foreach ( $terms as $term ) { + if ( $taxonomy === $term->taxonomy && $slug === $term->slug ) { + $shortlink = ''; + break; + } + } + } + } + + return $shortlink; + } + + /** + * Maybe redirect canonical Portfolio Archive Page. + * When registering a post, standard rules for overwriting archives are created, + * Which do not suit us for a number of reasons. To catch redirects according to these standard rules, we need the following function. + * This function controls requests to standard portfolio pages of archives, taxonomies and pagination, and, depending on the settings of permalinks, + * Allows or disables the standard redirect. + * + * @param string $redirect_url - Redirect URL. + * @param string $requested_url - Requested URL. + * @return string|bool + */ + public function maybe_redirect_canonical_links( $redirect_url, $requested_url ) { + $queried_object = get_queried_object(); + if ( + untrailingslashit( $redirect_url ) === untrailingslashit( get_home_url() ) && + (int) get_option( 'page_on_front' ) === (int) $this->archive_page + ) { + $is_category_redirect = strpos( $requested_url, $this->permalinks['category_base'] ) !== false; + $is_tag_redirect = strpos( $requested_url, $this->permalinks['tag_base'] ) !== false; + $is_portfolio_archive = ! $is_category_redirect && + ! $is_tag_redirect && + strpos( $requested_url, $this->permalinks['portfolio_base'] ) !== false && + isset( $queried_object->ID ) && + (int) $queried_object->ID === (int) $this->archive_page; + $parse_page_from_link = intval( untrailingslashit( str_replace( trailingslashit( $redirect_url ) . 'page/', '', $requested_url ) ) ); + + if ( $is_portfolio_archive ) { + $redirect_url = get_home_url(); + } + + if ( $is_category_redirect || $is_tag_redirect || $parse_page_from_link > 0 ) { + $redirect_url = false; + } + } elseif ( isset( $queried_object->ID ) && (int) $queried_object->ID === (int) $this->archive_page ) { + + $parse_page_from_link = intval( untrailingslashit( str_replace( trailingslashit( $redirect_url ) . 'page/', '', $requested_url ) ) ); + + if ( $parse_page_from_link > 0 ) { + $redirect_url = false; + } + } + return $redirect_url; + } + + /** + * Filters the list of CSS body class names for the current archive. + * + * @param array $classes - An array of body class names. + * @return string + */ + public function add_body_archive_classes( $classes ) { + if ( + isset( $_REQUEST['vp_preview_post_id'] ) && + ! empty( $_REQUEST['vp_preview_post_id'] ) && + isset( $_REQUEST['vp_preview_nonce'] ) && + ! empty( $_REQUEST['vp_preview_nonce'] ) && + wp_verify_nonce( sanitize_key( $_REQUEST['vp_preview_nonce'] ), 'vp-ajax-nonce' ) + ) { + $post_id = intval( $_REQUEST['vp_preview_post_id'] ); + } + + $post_id = $post_id ?? get_the_ID() ?? null; + + if ( $post_id && get_post_meta( $post_id, '_vp_post_type_mapped', true ) ) { + $classes[] = 'visual-portfolio-archive'; + $classes[] = 'archive'; + $classes[] = 'post-type-archive'; + + $unused_classes = array( 'single', 'single-page', 'page', 'postid-' . $post_id, 'page-id-' . $post_id ); + foreach ( $unused_classes as $unused_class ) { + $founding_key = array_search( $unused_class, $classes, true ); + if ( false !== $founding_key ) { + unset( $classes[ $founding_key ] ); + } + } + } + return $classes; + } + + /** + * Change Title for Archive Taxonomy pages. + * + * @param string $title - Post title. + * @param int $id - Post ID. + * @return string + */ + public function set_archive_title( $title, $id = 0 ) { + if ( $id && get_post_meta( $id, '_vp_post_type_mapped', true ) ) { + global $wp_query; + + if ( isset( $wp_query->query['vp_category'] ) ) { + $category = get_term_by( 'slug', $wp_query->query['vp_category'], 'portfolio_category' ); + // translators: %s - taxonomy name. + $title = sprintf( esc_html__( 'Portfolio Category: %s', 'visual-portfolio' ), esc_html( ucfirst( $category->name ) ) ); + } + + if ( isset( $wp_query->query['portfolio_tag'] ) ) { + $tag = get_term_by( 'slug', $wp_query->query['portfolio_tag'], 'portfolio_tag' ); + // translators: %s - taxonomy name. + $title = sprintf( esc_html__( 'Portfolio Tag: %s', 'visual-portfolio' ), esc_html( ucfirst( $tag->name ) ) ); + } + } + return $title; + } + + /** + * Add filter items. + * + * @param array $terms - Current terms. + * @param array $vp_options - Current vp_list options. + * @return array + */ + public function add_filter_items( $terms, $vp_options ) { + if ( + isset( $_REQUEST['vp_preview_post_id'] ) && + ! empty( $_REQUEST['vp_preview_post_id'] ) && + isset( $_REQUEST['vp_preview_nonce'] ) && + ! empty( $_REQUEST['vp_preview_nonce'] ) && + wp_verify_nonce( sanitize_key( $_REQUEST['vp_preview_nonce'] ), 'vp-ajax-nonce' ) + ) { + $post_id = intval( $_REQUEST['vp_preview_post_id'] ); + } + + if ( self::is_archive( $vp_options, $post_id ?? null ) ) { + + $query_opts = Visual_Portfolio_Get::get_query_params( $vp_options, true ); + // Get active item. + $active_item = Visual_Portfolio_Get::get_filter_active_item( $query_opts ); + $portfolio_query = new WP_Query( + array( + 'post_type' => 'portfolio', + 'posts_per_page' => -1, + ) + ); + $term_items = Visual_Portfolio_Get::get_posts_terms( $portfolio_query, $active_item ); + + // Add 'All' active item. + if ( ! empty( $term_items['terms'] ) && $vp_options['filter_text_all'] ) { + array_unshift( + $term_items['terms'], + array( + 'filter' => '*', + 'label' => $vp_options['filter_text_all'], + 'description' => false, + 'count' => false, + 'id' => 0, + 'parent' => 0, + 'active' => ! $term_items['there_is_active'], + 'url' => Visual_Portfolio_Get::get_pagenum_link( + array( + 'vp_filter' => '', + 'vp_page' => 1, + ) + ), + 'class' => 'vp-filter__item' . ( ! $term_items['there_is_active'] ? ' vp-filter__item-active' : '' ), + ) + ); + } + if ( ! empty( $term_items['terms'] ) ) { + $terms = $term_items['terms']; + foreach ( $terms as $key => $term ) { + // Parsing the content of links. + preg_match( '/vp_filter=([^&]*)/', $term['url'], $match_filter ); + preg_match( '/' . $this->permalinks['category_base'] . '\/[^\/]+\//', $term['url'], $match_category ); + preg_match( '/' . $this->permalinks['tag_base'] . '\/[^\/]+\//', $term['url'], $match_tag ); + + $base_page = $this->get_relative_archive_link(); + + /** + * We clear the link from taxonomies to replace them with the base archive page. + * For example: example.com/portfolio-category/test/?vp_filter=portfolio_category%3Aanother-category + * To example.com/?vp_filter=portfolio_category%3Aanother-category + */ + $changed_part_of_link = ! empty( $match_category ) ? $match_category[0] : ( ! empty( $match_tag ) ? $match_tag[0] : '' ); + $link = str_replace( $changed_part_of_link, $base_page, $term['url'] ); + + if ( is_array( $match_filter ) && ! empty( $match_filter ) ) { + // We extract the contents of the filter and form a new link. + $taxonomies = explode( ':', rawurldecode( $match_filter[1] ) ); + if ( is_array( $taxonomies ) && 'portfolio_category' === $taxonomies[0] ) { + $category_slug = $taxonomies[1]; + if ( strpos( $link, 'vp_filter' ) !== false ) { + $link = remove_query_arg( 'vp_filter', $link ); + } + $link = trailingslashit( $link ); + + if ( '' === $base_page ) { + $link = $link . $this->permalinks['category_base'] . '/' . $category_slug . '/'; + } else { + $link = str_replace( $base_page, $this->permalinks['category_base'] . '/' . $category_slug . '/', $link ); + } + + /** + * In the case where the base page of the archive is the home page, + * when loading taxonomy pages with pagination and filter, + * the order of the link may be violated. + * For example like this: example.com/page/2/portfolio-category/test/ + * + * In this case, we fix the link by checking its structure. + * We then rearrange the contents of the link and transform it into the following form: example.com/portfolio-category/test/page/2/ + */ + preg_match( '/page\/(\d+)\/' . $this->permalinks['category_base'] . '\/[^\/]+\//', $link, $invalid_format ); + + if ( is_array( $invalid_format ) && ! empty( $invalid_format ) ) { + $link = str_replace( $invalid_format[0], $this->permalinks['category_base'] . '/' . $category_slug . '/page/' . $invalid_format[1] . '/', $link ); + } + } + } + + $link = $this->remove_page_url( $link ); + + $terms[ $key ]['url'] = $link; + } + } + } + + return $terms; + } + + /** + * Init permalink settings. + * + * @return void + */ + public function permalink_settings_init() { + add_settings_section( + 'portfolio-permalink', + esc_html__( 'Portfolio permalinks', 'visual-portfolio' ), + array( $this, 'settings' ), + 'permalink' + ); + + add_settings_field( + 'vp_category_slug', + esc_html__( 'Portfolio category base', 'visual-portfolio' ), + array( $this, 'slug_input' ), + 'permalink', + 'optional', + array( + 'id' => 'vp_category_slug', + 'placeholder' => esc_attr_x( 'portfolio-category', 'slug', 'visual-portfolio' ), + 'value' => 'category_base', + ) + ); + + add_settings_field( + 'vp_tag_slug', + esc_html__( 'Portfolio tag base', 'visual-portfolio' ), + array( $this, 'slug_input' ), + 'permalink', + 'optional', + array( + 'id' => 'vp_tag_slug', + 'placeholder' => esc_attr_x( 'portfolio-tag', 'slug', 'visual-portfolio' ), + 'value' => 'tag_base', + ) + ); + } + + /** + * Get permalink settings for things like portfolios and taxonomies. + * + * The permalink settings are stored to the option instead of + * being blank and inheritting from the locale. This speeds up page loading + * times by negating the need to switch locales on each page load. + * + * This is more inline with WP core behavior which does not localize slugs. + * + * @param boolean $replace_portfolio_slug - replace portfolio slug automatically. Used in post type registration. + * + * @return array + */ + public static function get_permalink_structure( $replace_portfolio_slug = false ) { + $saved_permalinks = (array) get_option( 'portfolio_permalinks', array() ); + $permalinks = wp_parse_args( + array_filter( $saved_permalinks ), + array( + 'portfolio_base' => '%portfolio_page_slug%', + 'category_base' => _x( 'portfolio-category', 'slug', 'visual-portfolio' ), + 'tag_base' => _x( 'portfolio-tag', 'slug', 'visual-portfolio' ), + 'attribute_base' => '', + ) + ); + + if ( $saved_permalinks !== $permalinks ) { + update_option( 'portfolio_permalinks', $permalinks ); + } + + // Replace portfolio page slug. + if ( $replace_portfolio_slug && strpos( $permalinks['portfolio_base'], '%portfolio_page_slug%' ) !== false ) { + $permalinks['portfolio_base'] = str_replace( '%portfolio_page_slug%', self::get_portfolio_slug(), $permalinks['portfolio_base'] ); + } + + $permalinks['portfolio_base'] = ltrim( $permalinks['portfolio_base'], '/\\' ); + + $permalinks['portfolio_base'] = untrailingslashit( $permalinks['portfolio_base'] ); + $permalinks['category_base'] = untrailingslashit( $permalinks['category_base'] ); + $permalinks['tag_base'] = untrailingslashit( $permalinks['tag_base'] ); + + return $permalinks; + } + + /** + * Show a slug input box. + * + * @param array $attributes - Setting attributes. + * @return void + */ + public function slug_input( $attributes ) { + $id = $attributes['id']; + $placeholder = $attributes['placeholder']; + $value = $attributes['value']; + ?> + + portfolio would make your portfolio links like %sportfolio/sample-portfolio/. This setting affects portfolio URLs only, not things such as portfolio categories. We also recommend you use the %%portfolio_page_slug%% slug, which will automatically use the slug of you Portfolio Archive page.', 'visual-portfolio' ), esc_url( home_url( '/' ) ) ) ) ); + + $page_slug = '%portfolio_page_slug%'; + $default_slug = _x( 'portfolio', 'default-slug', 'visual-portfolio' ); + $current_base = trailingslashit( $this->permalinks['portfolio_base'] ); + $structures = array( + 0 => '', + 1 => trailingslashit( $page_slug ), + 2 => trailingslashit( $page_slug ) . trailingslashit( '%portfolio_category%' ), + + // Only used in the html output in the settings. + 99 => trailingslashit( $default_slug ), + ); + + ?> + + + + + + + + + + + + + + + + + + + + + + + archive_page ); + add_rewrite_tag( '%vp_page_query%', '([^&]+)' ); + add_rewrite_tag( '%vp_page_archive%', '([^&]+)' ); + add_rewrite_tag( '%vp_category%', '([^&]+)' ); + + add_rewrite_rule( + '^' . $slug . '/page/?([0-9]{1,})/?', + 'index.php?post_type=portfolio&vp_page_archive=1&vp_page_query=$matches[1]', + 'top' + ); + if ( (int) get_option( 'page_on_front' ) === (int) $this->archive_page ) { + add_rewrite_rule( + '^page/?([0-9]{1,})/?', + 'index.php?post_type=portfolio&vp_page_archive=1&vp_page_query=$matches[1]', + 'top' + ); + } + add_rewrite_rule( + '^' . $this->permalinks['category_base'] . '/([^/]*)/page/?([0-9]{1,})/?', + 'index.php?post_type=portfolio&vp_page_archive=1&portfolio_category=$matches[1]&vp_category=$matches[1]&vp_page_query=$matches[2]', + 'top' + ); + add_rewrite_rule( + '^' . $this->permalinks['category_base'] . '/([^/]*)/?', + 'index.php?post_type=portfolio&vp_page_archive=1&portfolio_category=$matches[1]&vp_category=$matches[1]&vp_page_query=1', + 'top' + ); + add_rewrite_rule( + '^' . $this->permalinks['tag_base'] . '/([^/]*)/page/?([0-9]{1,})/?', + 'index.php?post_type=portfolio&vp_page_archive=1&portfolio_tag=$matches[1]&vp_page_query=$matches[2]', + 'top' + ); + add_rewrite_rule( + '^' . $this->permalinks['tag_base'] . '/([^/]*)/?', + 'index.php?post_type=portfolio&vp_page_archive=1&portfolio_tag=$matches[1]&vp_page_query=1', + 'top' + ); + } + + /** + * Replace tags in Portfolio Permalinks. + * + * @param string $permalink - current permalink. + * @param WP_Post $post - current post. + * @return string + */ + public function portfolio_permalink_replacements( $permalink, $post ) { + // Category slug. + if ( strpos( $permalink, '%portfolio_category%' ) !== false ) { + $terms = get_the_terms( $post, 'portfolio_category' ); + if ( ! is_wp_error( $terms ) && ! empty( $terms ) && is_object( $terms[0] ) ) { + $term_slug = array_pop( $terms )->slug; + } else { + $term_slug = 'no-portfolio_category'; + } + + $permalink = str_replace( '%portfolio_category%', $term_slug, $permalink ); + } + + return $permalink; + } + + /** + * Override an archive page based on passed query arguments. + * + * @param WP_Query $query The query to check. + */ + public function maybe_override_archive( $query ) { + if ( is_admin() ) { + return; + } + + $post_type = 'portfolio'; + + // Maybe Redirect. + if ( is_page() ) { + $object_id = get_queried_object_id(); + $post_meta = get_post_meta( $object_id, '_vp_post_type_mapped', true ); + if ( ! $post_meta && get_query_var( 'paged' ) ) { + return; + } + } + + if ( + ( + is_post_type_archive( $post_type ) || + ( + (int) get_option( 'page_on_front' ) === (int) $this->archive_page && + isset( $object_id ) && + (int) $object_id === (int) $this->archive_page + ) + ) && + '' !== $this->archive_page && $query->is_main_query() + ) { + $post_id = absint( $this->archive_page ); + $post_id = Visual_Portfolio_3rd_WPML::get_object_id( $post_id ); + $query->set( 'post_type', 'page' ); + $query->set( 'page_id', $post_id ); + $query->set( 'original_archive_type', 'page' ); + $query->set( 'original_archive_id', $post_type ); + $query->set( 'term_tax', '' ); + $query->is_archive = false; + $query->is_single = true; + $query->is_singular = true; + $query->is_page = true; + $query->is_post_type_archive = false; + + if ( + isset( $query->query['vp_category'] ) && + ! empty( $query->query['vp_category'] ) && + ! isset( $query->query['vp_filter'] ) + ) { + $query->set( 'vp_filter', 'portfolio_category:' . $query->query['vp_category'] ); + } + + if ( isset( $query->query['portfolio_tag'] ) && isset( $query->query['vp_page_archive'] ) ) { + /** + * Fix WordPress Notices for Tag Taxonomy. + * If not set post type from queried object, header auto classes not set and generate notice error. + */ + $query->is_page = false; + $query->is_tag = true; + $post = new stdClass(); + $post->post_type = $post_type; + $post->ID = $post_id; + $query->queried_object = new WP_Post( $post ); + } + } + } + + /** + * Substituting query parameters before block output. + * + * @param array $args - Query arguments. + * @param array $options - Block options. + * @return array + */ + public function extend_query_args( $args, $options ) { + if ( + isset( $_REQUEST['vp_preview_post_id'] ) && + ! empty( $_REQUEST['vp_preview_post_id'] ) && + isset( $_REQUEST['vp_preview_nonce'] ) && + ! empty( $_REQUEST['vp_preview_nonce'] ) && + wp_verify_nonce( sanitize_key( $_REQUEST['vp_preview_nonce'] ), 'vp-ajax-nonce' ) + ) { + $post_id = intval( $_REQUEST['vp_preview_post_id'] ); + } + + $post_id = $post_id ?? $args['page_id'] ?? null; + + if ( $post_id && 'current_query' === $options['posts_source'] ) { + $post_meta = get_post_meta( (int) $post_id, '_vp_post_type_mapped', true ); + if ( ! empty( $post_meta ) && $post_meta ) { + + $args['post_type'] = $post_meta; + + if ( + isset( $args['vp_page_archive'] ) && + $args['vp_page_archive'] && + isset( $args['vp_page_query'] ) && + ! empty( $args['vp_page_query'] ) + ) { + $args['paged'] = $args['vp_page_query']; + } + + if ( isset( $args['page_id'] ) ) { + unset( $args['page_id'] ); + } + + unset( $args['p'] ); + $args['posts_per_page'] = $this->posts_per_page; + } + } + return $args; + } + + /** + * If not set default paged style - Delete pagination on archive pagination pages: /pages/. + * + * @param array $options - Block Options. + * @return array + */ + public function unset_pagination_archive_page( $options ) { + global $wp_query; + if ( $wp_query && isset( $wp_query->query_vars ) && is_array( $wp_query->query_vars ) && 'current_query' === $options['posts_source'] ) { + $is_page_archive = $wp_query->query_vars['vp_page_archive'] ?? false; + /** + * Also check the standard rewrite requests and find out if the page is an archive. + */ + $is_page_archive = isset( $wp_query->query_vars['paged'] ) && + isset( $wp_query->query_vars['original_archive_id'] ) && + 'portfolio' === $wp_query->query_vars['original_archive_id'] ? true : $is_page_archive; + + if ( $is_page_archive ) { + foreach ( $options['layout_elements'] as $container ) { + if ( ! empty( $container['elements'] ) ) { + $key = array_search( 'pagination', $container['elements'], true ); + if ( false !== $key && isset( $options['pagination'] ) ) { + if ( 'paged' === $options['pagination'] || is_tax() ) { + // phpcs:ignore WordPress.Security.NonceVerification + $vp_page = isset( $_REQUEST['vp_page'] ) && ! empty( $_REQUEST['vp_page'] ) ? sanitize_option( 'posts_per_page', wp_unslash( $_REQUEST['vp_page'] ) ) : null; + + $options['start_page'] = $wp_query->query_vars['vp_page_query'] ?? $vp_page ?? $wp_query->query_vars['paged'] ?? 1; + if ( 0 === $options['start_page'] && 0 === $wp_query->query_vars['paged'] ) { + $options['start_page'] = 1; + } + } + } + } + } + } + } + return $options; + } + + /** + * Update Post meta mapped after save general archive page option. + * + * @param int $post_id - Post ID. + * @return int + */ + public static function save_archive_page_option( $post_id ) { + + if ( is_numeric( $post_id ) ) { + + self::delete_post_type_mapped_meta(); + + visual_portfolio()->defer_flush_rewrite_rules(); + + update_post_meta( (int) $post_id, '_vp_post_type_mapped', 'portfolio' ); + } + + return $post_id; + } + + /** + * Delete pages list transient if page title updated. + * Rewrite flush rules if archive slug changed. + * + * @param int $post_ID - Post ID. + * @param array $data - Save Post data. + * @return void + */ + public function pre_page_update( $post_ID, $data ) { + if ( + 'page' === $data['post_type'] && + (int) $this->archive_page === (int) $post_ID && + get_post_field( 'post_name', $post_ID ) !== $data['post_name'] + ) { + visual_portfolio()->defer_flush_rewrite_rules(); + } + } + + /** + * Delete pages list transient if page status set as trashed. + * Also delete archive page and set old slug for default archive permalinks. + * + * @param int $post_ID - Post ID. + * @return void + */ + public function delete_archive_page( $post_ID ) { + if ( ! empty( $this->archive_page ) && (int) $post_ID === (int) $this->archive_page ) { + Settings::update_option( 'portfolio_archive_page', 'vp_general', '' ); + + self::delete_post_type_mapped_meta(); + } + } + + /** + * Rewrite Flush Rules after update front page option. + * We need this because when we set the Portfolio page as Front Page + * and then change this option back, our portfolio archive no longer displays the correct portfolio query. + * We have to refresh permalinks manually. + * + * @param array $old_value - Old value before update. + * @param array $value - New value after update. + * @param string $option - Name of option. + * @return void + */ + public function flush_rewrite_rules_after_update_front_page( $old_value, $value, $option ) { + if ( 'page_on_front' === $option ) { + visual_portfolio()->defer_flush_rewrite_rules(); + } + } + + /** + * Rewrite Flush Rules after update portfolio page option. + * + * @param array $old_value - Old value before update. + * @param array $value - New value after update. + * @param string $option - Name of option. + * @return void + */ + public function flush_rewrite_rules_after_update( $old_value, $value, $option ) { + if ( + isset( $old_value['portfolio_archive_page'] ) && + isset( $value['portfolio_archive_page'] ) && + 'vp_general' === $option + ) { + if ( + ! empty( $value['portfolio_archive_page'] ) && + $old_value['portfolio_archive_page'] === $value['portfolio_archive_page'] + ) { + visual_portfolio()->defer_flush_rewrite_rules(); + } + + if ( + empty( $value['portfolio_archive_page'] ) && + $old_value['portfolio_archive_page'] !== $value['portfolio_archive_page'] && + is_numeric( $old_value['portfolio_archive_page'] ) + ) { + self::delete_post_type_mapped_meta(); + + visual_portfolio()->defer_flush_rewrite_rules(); + } + } + } + + /** + * Remove mapped post meta from all pages. + * + * @return void + */ + private static function delete_post_type_mapped_meta() { + global $wpdb; + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $wpdb->query( + "DELETE FROM $wpdb->postmeta + WHERE meta_key = '_vp_post_type_mapped'" + ); + } + + /** + * Create default archive page. + * + * @param string $custom_slug - Default Archive Slug. + * @return void + */ + public static function create_archive_page( $custom_slug = 'portfolio' ) { + if ( ! get_option( '_vp_add_archive_page' ) && ! get_option( '_vp_trying_to_add_archive_page' ) ) { + + add_option( '_vp_trying_to_add_archive_page', true ); + + $args = array( + 'post_title' => esc_html__( 'Portfolio', 'visual-portfolio' ), + 'post_status' => 'publish', + 'post_type' => 'page', + 'post_name' => $custom_slug, + ); + + // Insert the post into the database. + $post_id = wp_insert_post( $args ); + + if ( ! is_wp_error( $post_id ) ) { + + Settings::update_option( 'portfolio_archive_page', 'vp_general', $post_id ); + + visual_portfolio()->defer_flush_rewrite_rules(); + + self::save_archive_page_option( $post_id ); + + $post = get_post( $post_id ); + + $slug = $post->post_name; + + wp_update_post( + wp_slash( + array( + 'ID' => $post_id, + 'post_content' => '', + ) + ) + ); + + add_option( '_vp_add_archive_page', $post_id ); + } + } + } + + /** + * Add a post display state for special Portfolio Archive page in the pages list table. + * + * @param array $post_states - An array of post display states. + * @param WP_Post $post - The current post object. + * @return array $post_states - An array of post display states. + */ + public function add_display_post_states( $post_states, $post ) { + if ( 'page' === $post->post_type ) { + // If successful, returns the post type slug. + $post_type = get_post_meta( $post->ID, '_vp_post_type_mapped', true ); + if ( $post_type && ! empty( $post_type ) ) { + $post_states[] = esc_html__( 'Portfolio Page', 'visual-portfolio' ); + } + } + return $post_states; + } + + /** + * Get Portfolio Archive Slug. + * + * @return string + */ + public static function get_portfolio_slug() { + // When deleting the archive page, we leave the old slug without overwriting the permalinks. + // In this case, instead of the archives page, a standard archives page with the corresponding template is substituted. + $custom_slug = _x( 'portfolio', 'default-slug', 'visual-portfolio' ); + + $archive_page = Settings::get_option( 'portfolio_archive_page', 'vp_general' ); + + if ( isset( $archive_page ) && ! empty( $archive_page ) ) { + // If there is a selected page of archives, we substitute its slug. + $custom_slug = get_post_field( 'post_name', $archive_page ); + } + + return $custom_slug; + } + + /** + * Get Portfolio Archive Label. + * + * @return string + */ + public static function get_portfolio_label() { + // When deleting the archive page, we leave the old slug without overwriting the permalinks. + // In this case, instead of the archives page, a standard archives page with the corresponding template is substituted. + $custom_slug = _x( 'Portfolio', 'default-label', 'visual-portfolio' ); + + $archive_page = Settings::get_option( 'portfolio_archive_page', 'vp_general' ); + + if ( isset( $archive_page ) && ! empty( $archive_page ) ) { + // If there is a selected page of archives, we substitute its slug. + $custom_slug = get_post_field( 'post_title', $archive_page ); + } + + return $custom_slug; + } + + /** + * Get Relative Archive Link. + * + * @return string + */ + private function get_relative_archive_link() { + $object_id = get_queried_object_id(); + + if ( + (int) get_option( 'page_on_front' ) === (int) $this->archive_page && + isset( $object_id ) && + (int) $object_id === (int) $this->archive_page + ) { + $relative = ''; + } else { + $relative = self::get_portfolio_slug() . '/'; + } + + return $relative; + } + + /** + * Change pagination links to friendly. + * For example, a link like: example.com/?vp_page=2 + * Has been converted to: example.com/page/2/ + * + * If the link already contains a page structure and GET parameters, then the conversion takes place as follows. + * For example, a link like: example.com/page/2/?vp_page=3 + * Has been converted to: example.com/page/3/ + * + * @param string $link - Paginate link. + * @param string|int $num_page - Changed number of page. + * @return string + */ + private function converting_paginate_link_to_friendly_url( $link, $num_page = null ) { + // Parsing the content of links. + preg_match( '/vp_page=(\d+)/', $link, $match_vp_page ); + preg_match( '/page\/(\d+)/', $link, $match_page ); + + if ( empty( $num_page ) && is_array( $match_vp_page ) && ! empty( $match_vp_page ) ) { + $num_page = $match_vp_page[1]; + } + + if ( ! empty( $num_page ) && is_array( $match_page ) && ! empty( $match_page ) ) { + $link = str_replace( $match_page[0], 'page/' . $num_page, $link ); + } + + if ( ! empty( $num_page ) && empty( $match_page ) ) { + $link = str_replace( '/?', '/page/' . $num_page . '/?', $link ); + } + + if ( strpos( $link, 'vp_page' ) !== false ) { + $link = remove_query_arg( 'vp_page', $link ); + } + + return $link; + } + + /** + * Check if post is Archive. + * + * @param array $options - Block Options. + * @param int $post_id - Post ID. + * @return boolean + */ + public static function is_archive( $options, $post_id = null ) { + global $wp_query; + $post_id = $post_id ?? get_the_ID() ?? null; + return ( + isset( $wp_query->query['vp_page_archive'] ) || + ( + isset( $wp_query->query_vars['original_archive_id'] ) && + 'portfolio' === $wp_query->query_vars['original_archive_id'] + ) || + ( + $post_id && + get_post_meta( $post_id, '_vp_post_type_mapped', true ) + ) + ) && + 'post-based' === $options['content_source'] && + 'current_query' === $options['posts_source']; + } +} +new Visual_Portfolio_Archive_Mapping(); diff --git a/classes/class-ask-review.php b/classes/class-ask-review.php new file mode 100644 index 00000000..194cf2bf --- /dev/null +++ b/classes/class-ask-review.php @@ -0,0 +1,137 @@ +option_name . '_state' ); + $time = (int) get_site_option( $this->option_name . '_time' ); + + if ( 'yes' === $state || 'already' === $state ) { + return false; + } + + // Save current time if nothing saved. + if ( ! $time ) { + $time = time(); + update_site_option( $this->option_name . '_time', $time ); + } + + // Allow notice if plugin used for more then 2 weeks. + if ( $time < strtotime( '-14 days' ) ) { + return true; + } + + return false; + } + + /** + * Display admin notice if needed. + */ + public function admin_notices() { + if ( ! $this->is_notice_allowed() ) { + return; + } + ?> +
+
+ +
+
+

+

+ ' . _x( 'Visual Portfolio', 'plugin name inside the review notice', 'visual-portfolio' ) . '' ) ); + ?> +
+ +

+

+ + + + + +
+ + +
+ + + +

+
+
+ wp_create_nonce( $this->option_name ), + ) + ); + } + + /** + * Handles Ajax request to persist notices dismissal. + * Uses check_ajax_referer to verify nonce. + */ + public function ajax_vpf_dismiss_ask_review_notice() { + check_ajax_referer( $this->option_name, 'nonce' ); + + $type = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : 'yes'; + + update_site_option( $this->option_name . '_state', $type ); + + // Update time if user clicked "No, maybe later" button. + if ( 'later' === $type ) { + $time = time(); + update_site_option( $this->option_name . '_time', $time ); + } + + wp_die(); + } +} + +new Visual_Portfolio_Ask_Review_Notice(); diff --git a/classes/class-assets.php b/classes/class-assets.php new file mode 100644 index 00000000..39448492 --- /dev/null +++ b/classes/class-assets.php @@ -0,0 +1,964 @@ + array(), + 'style' => array(), + 'template_style' => array(), + ); + + /** + * When styles already included in head. + * + * @var array + */ + private static $head_css_included = false; + + /** + * Visual_Portfolio_Assets constructor. + */ + public function __construct() { + // template_redirect is used instead of wp_enqueue_scripts just because some plugins use it and included an old isotope plugin. So, it was conflicted. + add_action( 'template_redirect', array( $this, 'register_scripts' ), 9 ); + add_action( 'wp_enqueue_scripts', array( $this, 'wp_enqueue_head_assets' ), 9 ); + + add_action( 'template_redirect', array( $this, 'popup_custom_styles' ) ); + add_action( 'template_redirect', array( $this, 'assets_for_default_wordpress_images' ) ); + + add_action( 'wp_footer', array( $this, 'wp_enqueue_foot_assets' ) ); + + add_action( 'wp_head', array( $this, 'localize_global_data' ) ); + + // noscript tag. + add_action( 'wp_head', array( $this, 'add_noscript_styles' ) ); + + // parse shortcodes from post content. + add_filter( 'wp', array( $this, 'maybe_parse_shortcodes_from_content' ), 10 ); + add_action( 'vpf_parse_blocks', array( $this, 'maybe_parse_blocks_from_content' ), 11 ); + + // enqueue runtime. + add_action( 'enqueue_block_editor_assets', 'Visual_Portfolio_Assets::enqueue_runtime', 11 ); + add_action( 'wp_enqueue_scripts', 'Visual_Portfolio_Assets::enqueue_runtime', 8 ); + } + + /** + * Check if Webpack HMR file available. + * + * @return boolean + */ + public static function is_webpack_hmr_support() { + return file_exists( visual_portfolio()->plugin_path . '/build/runtime.js' ); + } + + /** + * Enqueue runtime script. + */ + public static function enqueue_runtime() { + // HMR Webpack. + if ( self::is_webpack_hmr_support() ) { + self::enqueue_script( 'visual-portfolio-runtime', 'build/runtime', array(), null, false ); + } + } + + /** + * Get .asset.php file data. + * + * @param string $filepath asset file path. + * + * @return array + */ + public static function get_asset_file( $filepath ) { + $asset_path = visual_portfolio()->plugin_path . '/' . $filepath . '.asset.php'; + + if ( file_exists( $asset_path ) ) { + // phpcs:ignore WPThemeReview.CoreFunctionality.FileInclude.FileIncludeFound + return include $asset_path; + } + + return array( + 'dependencies' => array(), + 'version' => VISUAL_PORTFOLIO_VERSION, + ); + } + + /** + * Register script. + * + * @param string $name asset name. + * @param string $path file path. + * @param array $dependencies asset dependencies. + * @param string $version asset version. + * @param boolean $in_footer render in footer. + */ + public static function register_script( $name, $path, $dependencies = array(), $version = null, $in_footer = true ) { + $script_data = self::get_asset_file( $path ); + + if ( ! empty( $dependencies ) ) { + $script_data['dependencies'] = array_unique( + array_merge( + $script_data['dependencies'], + $dependencies + ) + ); + } + + wp_register_script( + $name, + visual_portfolio()->plugin_url . $path . '.js', + $script_data['dependencies'], + $version ?? $script_data['version'], + $in_footer + ); + } + + /** + * Enqueue script. + * + * @param string $name asset name. + * @param string $path file path. + * @param array $dependencies asset dependencies. + * @param string $version asset version. + * @param boolean $in_footer render in footer. + */ + public static function enqueue_script( $name, $path, $dependencies = array(), $version = null, $in_footer = true ) { + self::register_script( $name, $path, $dependencies, $version, $in_footer ); + + wp_enqueue_script( $name ); + } + + /** + * Register style + * + * @param string $name asset name. + * @param string $path file path. + * @param array $dependencies asset dependencies. + * @param string $version asset version. + */ + public static function register_style( $name, $path, $dependencies = array(), $version = null ) { + $style_data = self::get_asset_file( $path ); + + wp_register_style( + $name, + visual_portfolio()->plugin_url . $path . '.css', + $dependencies, + $version ?? $style_data['version'] + ); + } + + /** + * Enqueue style + * + * @param string $name asset name. + * @param string $path file path. + * @param array $dependencies asset dependencies. + * @param string $version asset version. + */ + public static function enqueue_style( $name, $path, $dependencies = array(), $version = null ) { + self::register_style( $name, $path, $dependencies, $version ); + + wp_enqueue_style( $name ); + } + + /** + * Store used assets, so we can enqueue it later. + * + * @param string $name - asset name. + * @param bool|string $value - just enqueue flag or url to asset. + * @param string $type - assets type [script|style|template_style]. + * @param int $priority - asset enqueue priority. + */ + public static function store_used_assets( $name, $value = true, $type = 'script', $priority = 10 ) { + if ( ! isset( self::$stored_assets[ $type ] ) ) { + return; + } + + if ( isset( self::$stored_assets[ $type ][ $name ] ) ) { + return; + } + + self::$stored_assets[ $type ][ $name ] = array( + 'value' => $value, + 'priority' => $priority, + ); + } + + /** + * Remove stored assets. May be used for advanced functionality. + * + * @param string $name - asset name. + * @param string $type - assets type [script|style|template_style]. + */ + public static function remove_stored_assets( $name, $type = 'script' ) { + if ( ! isset( self::$stored_assets[ $type ][ $name ] ) ) { + return; + } + + unset( self::$stored_assets[ $type ][ $name ] ); + } + + /** + * Enqueue stored assets. + * + * @param string $type - assets type [script|style|template_style]. + */ + public static function enqueue_stored_assets( $type = 'script' ) { + if ( ! isset( self::$stored_assets[ $type ] ) || empty( self::$stored_assets[ $type ] ) ) { + return; + } + + uasort( + self::$stored_assets[ $type ], + function ( $a, $b ) { + if ( $a === $b ) { + return 0; + } + + if ( isset( $a['priority'] ) && isset( $b['priority'] ) ) { + return $a['priority'] < $b['priority'] ? -1 : 1; + } + + return 0; + } + ); + + foreach ( self::$stored_assets[ $type ] as $name => $data ) { + if ( isset( $data['value'] ) && $data['value'] ) { + if ( 'script' === $type ) { + wp_enqueue_script( $name, '', array(), VISUAL_PORTFOLIO_VERSION, true ); + } elseif ( is_string( $data['value'] ) ) { + // Don't provide version for template style, + // it will be added automatically using `filemtime`. + visual_portfolio()->include_template_style( $name, $data['value'], array() ); + } else { + wp_enqueue_style( $name, '', array(), VISUAL_PORTFOLIO_VERSION ); + } + + self::$stored_assets[ $type ]['value'] = false; + } + } + } + + /** + * Enqueue assets based on layout data. + * + * @param array $options - layout data. + */ + public static function enqueue( $options ) { + $options = Visual_Portfolio_Get::get_options( $options ); + + do_action( 'vpf_before_assets_enqueue', $options, $options['id'] ); + + self::store_used_assets( 'visual-portfolio', true, 'style', 9 ); + self::store_used_assets( 'visual-portfolio-notices-default', true, 'style', 9 ); + self::store_used_assets( + 'visual-portfolio-notices-default', + 'notices/style', + 'template_style' + ); + + self::store_used_assets( 'visual-portfolio-errors-default', true, 'style', 9 ); + self::store_used_assets( + 'visual-portfolio-errors-default', + 'errors/style', + 'template_style' + ); + + // Additional styles for Elementor. + if ( class_exists( '\Elementor\Plugin' ) ) { + self::store_used_assets( 'visual-portfolio-elementor', true, 'style', 9 ); + } + + self::store_used_assets( 'visual-portfolio', true, 'script', 12 ); + + // Layout. + switch ( $options['layout'] ) { + case 'masonry': + self::store_used_assets( 'visual-portfolio-layout-masonry', true, 'script' ); + self::store_used_assets( 'visual-portfolio-layout-masonry', true, 'style' ); + break; + case 'grid': + self::store_used_assets( 'visual-portfolio-layout-grid', true, 'script' ); + self::store_used_assets( 'visual-portfolio-layout-grid', true, 'style' ); + break; + case 'tiles': + self::store_used_assets( 'visual-portfolio-layout-tiles', true, 'script' ); + self::store_used_assets( 'visual-portfolio-layout-tiles', true, 'style' ); + break; + case 'justified': + self::store_used_assets( 'visual-portfolio-layout-justified', true, 'script' ); + self::store_used_assets( 'visual-portfolio-layout-justified', true, 'style' ); + break; + case 'slider': + self::store_used_assets( 'visual-portfolio-layout-slider', true, 'script' ); + self::store_used_assets( 'visual-portfolio-layout-slider', true, 'style' ); + break; + } + + // Custom Scrollbar. + self::store_used_assets( 'visual-portfolio-custom-scrollbar', true, 'script' ); + self::store_used_assets( 'visual-portfolio-custom-scrollbar', true, 'style' ); + + // Items Style. + if ( $options['items_style'] ) { + $items_style_pref = ''; + + if ( 'default' !== $options['items_style'] ) { + $items_style_pref = '/' . $options['items_style']; + } + + switch ( $options['items_style'] ) { + case 'fly': + self::store_used_assets( 'visual-portfolio-items-style-fly', true, 'script' ); + break; + } + + self::store_used_assets( + 'visual-portfolio-items-style-' . $options['items_style'], + 'items-list/items-style' . $items_style_pref . '/style', + 'template_style' + ); + } + + // Images Lazy Loading. + if ( Visual_Portfolio_Settings::get_option( 'lazy_loading', 'vp_images' ) ) { + self::enqueue_lazyload_assets(); + } + + // Popup. + if ( 'popup_gallery' === $options['items_click_action'] ) { + self::enqueue_popup_assets(); + } + + $layout_elements = array(); + + if ( isset( $options['layout_elements']['top']['elements'] ) ) { + $layout_elements = array_merge( $layout_elements, $options['layout_elements']['top']['elements'] ); + } + if ( isset( $options['layout_elements']['bottom']['elements'] ) ) { + $layout_elements = array_merge( $layout_elements, $options['layout_elements']['bottom']['elements'] ); + } + + // Filter. + if ( in_array( 'filter', $layout_elements, true ) ) { + $filter_style_pref = ''; + + if ( 'default' !== $options['filter'] ) { + $filter_style_pref = '/' . $options['filter']; + } + + self::store_used_assets( + 'visual-portfolio-filter-' . $options['filter'], + 'items-list/filter' . $filter_style_pref . '/style', + 'template_style' + ); + } + + // Sort. + if ( in_array( 'sort', $layout_elements, true ) ) { + $sort_style_pref = ''; + + if ( 'default' !== $options['sort'] ) { + $sort_style_pref = '/' . $options['sort']; + } + + self::store_used_assets( + 'visual-portfolio-sort-' . $options['sort'], + 'items-list/sort' . $sort_style_pref . '/style', + 'template_style' + ); + } + + // Pagination. + if ( in_array( 'pagination', $layout_elements, true ) ) { + $pagination_style_pref = ''; + + if ( 'default' !== $options['pagination_style'] ) { + $pagination_style_pref = '/' . $options['pagination_style']; + } + + // Infinite scroll pagination script. + if ( 'infinite' === $options['pagination'] ) { + self::store_used_assets( 'visual-portfolio-pagination-infinite', true, 'script' ); + } + + // Minimal page pagination helpful script. + if ( 'minimal' === $options['pagination_style'] && 'paged' === $options['pagination'] ) { + self::store_used_assets( 'visual-portfolio-pagination-minimal-paged', true, 'script' ); + } + + self::store_used_assets( + 'visual-portfolio-pagination-' . $options['pagination_style'], + 'items-list/pagination' . $pagination_style_pref . '/style', + 'template_style' + ); + } + + // Dynamic styles. + // Always add it even if no custom CSS available to better render dynamic styles in preview. + $dynamic_styles = Visual_Portfolio_Controls_Dynamic_CSS::get( $options ); + $controls_css_handle = 'vp-dynamic-styles-' . $options['id']; + + if ( ! wp_style_is( $controls_css_handle, 'enqueued' ) ) { + $dynamic_styles = wp_kses( $dynamic_styles, array( '\'', '\"' ) ); + $dynamic_styles = str_replace( '>', '>', $dynamic_styles ); + + $dynamic_styles_inline_style = apply_filters( 'vpf_enqueue_dynamic_styles_inline_style', ! self::$head_css_included, $dynamic_styles, $controls_css_handle ); + + // Enqueue custom CSS. + if ( $dynamic_styles_inline_style ) { + wp_register_style( $controls_css_handle, false, array(), VISUAL_PORTFOLIO_VERSION ); + wp_enqueue_style( $controls_css_handle ); + wp_add_inline_style( $controls_css_handle, $dynamic_styles ? $dynamic_styles : ' ' ); + + // Enqueue JS instead of CSS when rendering in to prevent W3C errors. + } elseif ( ! wp_script_is( $controls_css_handle, 'enqueued' ) ) { + wp_register_script( $controls_css_handle, false, array(), VISUAL_PORTFOLIO_VERSION, true ); + wp_enqueue_script( $controls_css_handle ); + wp_add_inline_script( + $controls_css_handle, + '(function(){ + var styleTag = document.createElement("style"); + styleTag.id = "' . esc_attr( $controls_css_handle ) . '-inline-css"; + styleTag.innerHTML = ' . wp_json_encode( $dynamic_styles ? $dynamic_styles : ' ' ) . '; + document.body.appendChild(styleTag); + }());' + ); + } + } + + self::store_used_assets( $controls_css_handle, true, 'style' ); + + do_action( 'vpf_after_assets_enqueue', $options, $options['id'] ); + } + + /** + * Enqueue popup assets. + * + * @return void + */ + public static function enqueue_popup_assets() { + $popup_vendor = Visual_Portfolio_Settings::get_option( 'vendor', 'vp_popup_gallery' ); + + // Photoswipe. + if ( 'photoswipe' === $popup_vendor && apply_filters( 'vpf_enqueue_plugin_photoswipe', true ) ) { + self::store_used_assets( 'visual-portfolio-plugin-photoswipe', true, 'script' ); + self::store_used_assets( 'visual-portfolio-popup-photoswipe', true, 'style' ); + + // Fancybox. + } elseif ( 'fancybox' === $popup_vendor && apply_filters( 'vpf_enqueue_plugin_fancybox', true ) ) { + self::store_used_assets( 'visual-portfolio-plugin-fancybox', true, 'script' ); + self::store_used_assets( 'visual-portfolio-popup-fancybox', true, 'style' ); + } + } + + /** + * Enqueue lazyload assets. + * + * @return void + */ + public static function enqueue_lazyload_assets() { + // Disable lazyload assets using filter. + // Same filter used in `class-images.php`. + if ( ! apply_filters( 'vpf_images_lazyload', true ) ) { + return; + } + + self::store_used_assets( 'visual-portfolio-lazyload', true, 'script' ); + self::store_used_assets( 'visual-portfolio-lazyload', true, 'style' ); + + // lazy load fallback. + add_action( 'wp_head', 'Visual_Portfolio_Assets::add_lazyload_fallback_script' ); + } + + /** + * Register scripts that will be used in the future when portfolio will be printed. + */ + public function register_scripts() { + $vp_deps = array( 'jquery', 'imagesloaded' ); + $vp_style_deps = array(); + + $popup_vendor = Visual_Portfolio_Settings::get_option( 'vendor', 'vp_popup_gallery' ); + + do_action( 'vpf_before_assets_register' ); + + // Isotope. + if ( apply_filters( 'vpf_enqueue_plugin_isotope', true ) ) { + self::register_script( 'isotope', 'assets/vendor/isotope-layout/dist/isotope.pkgd.min', array( 'jquery' ), '3.0.6' ); + } + + // fjGallery. + if ( apply_filters( 'vpf_enqueue_plugin_flickr_justified_gallery', true ) ) { + self::register_script( 'flickr-justified-gallery', 'assets/vendor/flickr-justified-gallery/dist/fjGallery.min', array( 'jquery' ), '2.1.2' ); + } + + // PhotoSwipe. + if ( 'photoswipe' === $popup_vendor && apply_filters( 'vpf_enqueue_plugin_photoswipe', true ) ) { + self::register_style( 'photoswipe', 'assets/vendor/photoswipe/dist/photoswipe', array(), '4.1.3' ); + self::register_style( 'photoswipe-default-skin', 'assets/vendor/photoswipe/dist/default-skin/default-skin', array( 'photoswipe' ), '4.1.3' ); + self::register_script( 'photoswipe', 'assets/vendor/photoswipe/dist/photoswipe.min', array( 'jquery' ), '4.1.3' ); + self::register_script( 'photoswipe-ui-default', 'assets/vendor/photoswipe/dist/photoswipe-ui-default.min', array( 'jquery', 'photoswipe' ), '4.1.3' ); + + // Fancybox. + } elseif ( 'fancybox' === $popup_vendor && apply_filters( 'vpf_enqueue_plugin_fancybox', true ) ) { + self::register_style( 'fancybox', 'assets/vendor/fancybox/dist/jquery.fancybox.min', array(), '3.5.7' ); + self::register_script( 'fancybox', 'assets/vendor/fancybox/dist/jquery.fancybox.min', array( 'jquery' ), '3.5.7' ); + } + + // Swiper. + if ( apply_filters( 'vpf_enqueue_plugin_swiper', true ) ) { + self::register_style( 'swiper', 'assets/vendor/swiper/swiper-bundle.min', array(), '8.4.7' ); + self::register_script( 'swiper', 'assets/vendor/swiper/swiper-bundle.min', array(), '8.4.7' ); + } + + // Simplebar. + if ( apply_filters( 'vpf_enqueue_plugin_simplebar', true ) ) { + self::register_style( 'simplebar', 'assets/vendor/simplebar/dist/simplebar.min', array(), '5.3.0' ); + self::register_script( 'simplebar', 'assets/vendor/simplebar/dist/simplebar.min', array(), '5.3.0' ); + } + + // LazySizes. + if ( apply_filters( 'vpf_enqueue_plugin_lazysizes', true ) ) { + self::register_script( 'lazysizes-config', 'build/assets/js/lazysizes-cfg', array() ); + self::register_script( 'lazysizes-object-fit-cover', 'build/assets/js/lazysizes-object-fit-cover', array(), '4.1.0' ); + self::register_script( 'lazysizes-swiper-duplicates-load', 'build/assets/js/lazysizes-swiper-duplicates-load', array() ); + self::register_script( 'lazysizes', 'assets/vendor/lazysizes/lazysizes.min', array( 'lazysizes-config', 'lazysizes-object-fit-cover', 'lazysizes-swiper-duplicates-load' ), '5.3.2' ); + } + + // Visual Portfolio CSS. + $vp_styles = array( + 'visual-portfolio' => array( 'build/assets/css/main', $vp_style_deps ), + 'visual-portfolio-elementor' => array( 'build/assets/css/elementor', array( 'visual-portfolio' ) ), + 'visual-portfolio-lazyload' => array( 'build/assets/css/lazyload', array() ), + 'visual-portfolio-custom-scrollbar' => array( 'build/assets/css/custom-scrollbar', array( 'simplebar' ) ), + 'visual-portfolio-layout-justified' => array( 'build/assets/css/layout-justified', array( 'visual-portfolio' ) ), + 'visual-portfolio-layout-slider' => array( 'build/assets/css/layout-slider', array( 'visual-portfolio', 'swiper' ) ), + 'visual-portfolio-layout-masonry' => array( 'build/assets/css/layout-masonry', array( 'visual-portfolio' ) ), + 'visual-portfolio-layout-grid' => array( 'build/assets/css/layout-grid', array( 'visual-portfolio' ) ), + 'visual-portfolio-layout-tiles' => array( 'build/assets/css/layout-tiles', array( 'visual-portfolio' ) ), + 'visual-portfolio-popup-fancybox' => array( 'build/assets/css/popup-fancybox', array( 'visual-portfolio', 'fancybox' ) ), + 'visual-portfolio-popup-photoswipe' => array( 'build/assets/css/popup-photoswipe', array( 'visual-portfolio', 'photoswipe-default-skin' ) ), + ); + + foreach ( $vp_styles as $name => $data ) { + self::register_style( $name, $data[0], $data[1] ); + wp_style_add_data( $name, 'rtl', 'replace' ); + wp_style_add_data( $name, 'suffix', '.min' ); + } + + // Visual Portfolio JS. + $vp_scripts = array( + 'visual-portfolio' => array( + 'build/assets/js/main', + $vp_deps, + ), + 'visual-portfolio-plugin-isotope' => array( + 'build/assets/js/plugin-isotope', + array( + 'jquery', + 'isotope', + 'wp-compose', + ), + ), + 'visual-portfolio-plugin-fj-gallery' => array( + 'build/assets/js/plugin-fj-gallery', + array( + 'jquery', + 'flickr-justified-gallery', + ), + ), + 'visual-portfolio-plugin-swiper' => array( + 'build/assets/js/plugin-swiper', + array( + 'jquery', + 'swiper', + ), + ), + 'visual-portfolio-custom-scrollbar' => array( + 'build/assets/js/custom-scrollbar', + array( + 'jquery', + 'simplebar', + ), + ), + 'visual-portfolio-lazyload' => array( + 'build/assets/js/lazyload', + array( + 'lazysizes', + ), + ), + 'visual-portfolio-popup-gallery' => array( + 'build/assets/js/popup-gallery', + array( + 'jquery', + ), + ), + 'visual-portfolio-plugin-photoswipe' => array( + 'build/assets/js/plugin-photoswipe', + array( + 'jquery', + 'photoswipe-ui-default', + 'visual-portfolio-popup-gallery', + ), + ), + 'visual-portfolio-plugin-fancybox' => array( + 'build/assets/js/plugin-fancybox', + array( + 'jquery', + 'fancybox', + 'visual-portfolio-popup-gallery', + ), + ), + 'visual-portfolio-layout-masonry' => array( + 'build/assets/js/layout-masonry', + array( + 'jquery', + 'visual-portfolio-plugin-isotope', + ), + ), + 'visual-portfolio-layout-grid' => array( + 'build/assets/js/layout-grid', + array( + 'jquery', + 'visual-portfolio-plugin-isotope', + ), + ), + 'visual-portfolio-layout-tiles' => array( + 'build/assets/js/layout-tiles', + array( + 'jquery', + 'visual-portfolio-plugin-isotope', + ), + ), + 'visual-portfolio-layout-justified' => array( + 'build/assets/js/layout-justified', + array( + 'jquery', + 'visual-portfolio-plugin-fj-gallery', + ), + ), + 'visual-portfolio-layout-slider' => array( + 'build/assets/js/layout-slider', + array( + 'jquery', + 'visual-portfolio-plugin-swiper', + ), + ), + 'visual-portfolio-items-style-fly' => array( + 'build/assets/js/items-style-fly', + array( + 'jquery', + ), + ), + 'visual-portfolio-pagination-infinite' => array( + 'build/assets/js/pagination-infinite', + array( + 'jquery', + ), + ), + 'visual-portfolio-pagination-minimal-paged' => array( + 'build/assets/js/pagination-minimal-paged', + array( + 'jquery', + ), + ), + ); + + foreach ( $vp_scripts as $name => $data ) { + self::register_script( $name, $data[0], $data[1] ); + } + + do_action( 'vpf_after_assets_register' ); + } + + /** + * Dynamic styles for popup gallery plugins. + */ + public function popup_custom_styles() { + $bg_color = Visual_Portfolio_Settings::get_option( 'background_color', 'vp_popup_gallery' ); + + if ( $bg_color ) { + wp_add_inline_style( 'visual-portfolio-popup-fancybox', '.vp-fancybox .fancybox-bg { background-color: ' . esc_attr( $bg_color ) . '; }' ); + wp_add_inline_style( 'visual-portfolio-popup-photoswipe', '.vp-pswp .pswp__bg { background-color: ' . esc_attr( $bg_color ) . '; }' ); + } + } + + /** + * Add popup for default WordPress images. + */ + public function assets_for_default_wordpress_images() { + if ( Visual_Portfolio_Settings::get_option( 'enable_on_wordpress_images', 'vp_popup_gallery' ) ) { + self::enqueue_popup_assets(); + } + if ( 'full' === Visual_Portfolio_Settings::get_option( 'lazy_loading', 'vp_images' ) ) { + self::enqueue_lazyload_assets(); + } + } + + /** + * Add global Visual Portfolio data. + */ + public function localize_global_data() { + $data = array( + 'version' => VISUAL_PORTFOLIO_VERSION, + 'pro' => false, + '__' => array( + // translators: %s - plugin name. + 'couldnt_retrieve_vp' => sprintf( __( 'Couldn\'t retrieve %s ID.', 'visual-portfolio' ), visual_portfolio()->plugin_name ), + + 'pswp_close' => esc_attr__( 'Close (Esc)', 'visual-portfolio' ), + 'pswp_share' => esc_attr__( 'Share', 'visual-portfolio' ), + 'pswp_fs' => esc_attr__( 'Toggle fullscreen', 'visual-portfolio' ), + 'pswp_zoom' => esc_attr__( 'Zoom in/out', 'visual-portfolio' ), + 'pswp_prev' => esc_attr__( 'Previous (arrow left)', 'visual-portfolio' ), + 'pswp_next' => esc_attr__( 'Next (arrow right)', 'visual-portfolio' ), + 'pswp_share_fb' => esc_attr__( 'Share on Facebook', 'visual-portfolio' ), + 'pswp_share_tw' => esc_attr__( 'Tweet', 'visual-portfolio' ), + 'pswp_share_pin' => esc_attr__( 'Pin it', 'visual-portfolio' ), + + 'fancybox_close' => esc_attr__( 'Close', 'visual-portfolio' ), + 'fancybox_next' => esc_attr__( 'Next', 'visual-portfolio' ), + 'fancybox_prev' => esc_attr__( 'Previous', 'visual-portfolio' ), + 'fancybox_error' => __( 'The requested content cannot be loaded.
Please try again later.', 'visual-portfolio' ), + 'fancybox_play_start' => esc_attr__( 'Start slideshow', 'visual-portfolio' ), + 'fancybox_play_stop' => esc_attr__( 'Pause slideshow', 'visual-portfolio' ), + 'fancybox_full_screen' => esc_attr__( 'Full screen', 'visual-portfolio' ), + 'fancybox_thumbs' => esc_attr__( 'Thumbnails', 'visual-portfolio' ), + 'fancybox_download' => esc_attr__( 'Download', 'visual-portfolio' ), + 'fancybox_share' => esc_attr__( 'Share', 'visual-portfolio' ), + 'fancybox_zoom' => esc_attr__( 'Zoom', 'visual-portfolio' ), + ), + 'settingsPopupGallery' => array( + // Default WordPress Images. + 'enable_on_wordpress_images' => Visual_Portfolio_Settings::get_option( 'enable_on_wordpress_images', 'vp_popup_gallery' ), + + // Vendor. + 'vendor' => Visual_Portfolio_Settings::get_option( 'vendor', 'vp_popup_gallery' ), + + // Deep Linking. + 'deep_linking' => Visual_Portfolio_Settings::get_option( 'deep_linking', 'vp_popup_gallery' ), + 'deep_linking_url_to_share_images' => Visual_Portfolio_Settings::get_option( 'deep_linking_url_to_share_images', 'vp_popup_gallery' ), + + // General. + 'show_arrows' => Visual_Portfolio_Settings::get_option( 'show_arrows', 'vp_popup_gallery' ), + 'show_counter' => Visual_Portfolio_Settings::get_option( 'show_counter', 'vp_popup_gallery' ), + 'show_zoom_button' => Visual_Portfolio_Settings::get_option( 'show_zoom_button', 'vp_popup_gallery' ), + 'show_fullscreen_button' => Visual_Portfolio_Settings::get_option( 'show_fullscreen_button', 'vp_popup_gallery' ), + 'show_share_button' => Visual_Portfolio_Settings::get_option( 'show_share_button', 'vp_popup_gallery' ), + 'show_close_button' => Visual_Portfolio_Settings::get_option( 'show_close_button', 'vp_popup_gallery' ), + + // Fancybox. + 'show_thumbs' => Visual_Portfolio_Settings::get_option( 'show_thumbs', 'vp_popup_gallery' ), + 'show_download_button' => Visual_Portfolio_Settings::get_option( 'show_download_button', 'vp_popup_gallery' ), + 'show_slideshow' => Visual_Portfolio_Settings::get_option( 'show_slideshow', 'vp_popup_gallery' ), + + 'click_to_zoom' => Visual_Portfolio_Settings::get_option( 'click_to_zoom', 'vp_popup_gallery' ), + 'restore_focus' => Visual_Portfolio_Settings::get_option( 'restore_focus', 'vp_popup_gallery' ), + ), + + // Screen sizes (breakpoints) for responsive feature: xs, sm, md, lg, xl. + 'screenSizes' => Visual_Portfolio_Breakpoints::get_breakpoints(), + ); + + $data = apply_filters( 'vpf_global_data', $data ); + + echo "\n"; + } + + /** + * Enqueue styles in head. + */ + public function wp_enqueue_head_assets() { + self::enqueue_stored_assets( 'style' ); + self::enqueue_stored_assets( 'template_style' ); + + self::$head_css_included = true; + } + + /** + * Enqueue scripts and styles in foot. + */ + public function wp_enqueue_foot_assets() { + self::enqueue_stored_assets( 'style' ); + self::enqueue_stored_assets( 'template_style' ); + self::enqueue_stored_assets( 'script' ); + } + + /** + * Add noscript styles. + * Previously we used the `style_loader_tag` filter to add noscript to enqueued CSS, + * but it is not working properly with optimizations plugins. + */ + public function add_noscript_styles() { + $styles = ''; + $styles_path = visual_portfolio()->plugin_path . '/build/assets/css/noscript.css'; + + if ( file_exists( $styles_path ) ) { + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + $styles = file_get_contents( $styles_path ); + $styles = str_replace( '>', '>', $styles ); + } + + if ( ! $styles ) { + return; + } + + ?> + + plugin_url . 'assets/css/lazyload-fallback.min.css?ver=' . VISUAL_PORTFOLIO_VERSION; + $js_url = visual_portfolio()->plugin_url . 'assets/js/lazyload-fallback.min.js?ver=' . VISUAL_PORTFOLIO_VERSION; + + ?> + + posts ) ) { + return; + } + + $posts = $wp_query->posts; + $pattern = get_shortcode_regex(); + + $layout_ids = array(); + + // parse all posts content. + foreach ( $posts as $post ) { + if ( + isset( $post->post_content ) + && preg_match_all( '/' . $pattern . '/s', $post->post_content, $matches ) + && array_key_exists( 2, $matches ) + && in_array( 'visual_portfolio', $matches[2], true ) + ) { + $keys = array(); + $shortcodes = array(); + + foreach ( $matches[0] as $key => $value ) { + // $matches[3] return the shortcode attribute as string + // replace space with '&' for parse_str() function. + $get = str_replace( ' ', '&', $matches[3][ $key ] ); + parse_str( $get, $output ); + + // get all shortcode attribute keys. + $keys = array_unique( array_merge( $keys, array_keys( $output ) ) ); + $shortcodes[] = $output; + } + + if ( $keys && $shortcodes ) { + // Loop the result array and add the missing shortcode attribute key. + foreach ( $shortcodes as $key => $value ) { + // Loop the shortcode attribute key. + foreach ( $keys as $attr_key ) { + $shortcodes[ $key ][ $attr_key ] = isset( $shortcodes[ $key ][ $attr_key ] ) ? $shortcodes[ $key ][ $attr_key ] : null; + } + + // sort the array key. + ksort( $shortcodes[ $key ] ); + } + } + + // get all IDs from shortcodes. + foreach ( $shortcodes as $shortcode ) { + if ( isset( $shortcode['id'] ) && $shortcode['id'] && ! in_array( $shortcode['id'], $layout_ids, true ) ) { + $layout_ids[] = str_replace( '"', '', $shortcode['id'] ); + } + } + } + } + + if ( ! empty( $layout_ids ) ) { + foreach ( $layout_ids as $id ) { + self::enqueue( array( 'id' => $id ) ); + } + } + } + + /** + * Parse blocks from content. + * + * @param array $blocks - blocks list. + */ + public function maybe_parse_blocks_from_content( $blocks ) { + if ( empty( $blocks ) ) { + return; + } + + foreach ( $blocks as $block ) { + // Block. + if ( + isset( $block['blockName'] ) && + 'visual-portfolio/block' === $block['blockName'] && + isset( $block['attrs']['content_source'] ) && + isset( $block['attrs']['block_id'] ) + ) { + self::enqueue( $block['attrs'] ); + + // Saved block. + } elseif ( + isset( $block['blockName'] ) && + ( + 'visual-portfolio/saved' === $block['blockName'] || + 'nk/visual-portfolio' === $block['blockName'] + ) && + isset( $block['attrs']['id'] ) + ) { + self::enqueue( $block['attrs'] ); + } + } + } +} + +new Visual_Portfolio_Assets(); diff --git a/classes/class-breakpoints.php b/classes/class-breakpoints.php new file mode 100644 index 00000000..97a978f7 --- /dev/null +++ b/classes/class-breakpoints.php @@ -0,0 +1,183 @@ + self::get_default_breakpoint_xs(), + 'sm' => self::get_default_breakpoint_sm(), + 'md' => self::get_default_breakpoint_md(), + 'lg' => self::get_default_breakpoint_lg(), + 'xl' => self::get_default_breakpoint_xl(), + ); + } + + /** + * Get Default Extra Small Breakpoint. + * + * @return int + */ + public static function get_default_breakpoint_xs() { + return apply_filters( 'vpf_default_breakpoint_xs', self::$default_xs ); + } + + /** + * Get Extra Small Breakpoint. + * + * @return int + */ + public static function get_breakpoint_xs() { + return apply_filters( 'vpf_breakpoint_xs', self::get_default_breakpoint_xs() ); + } + + /** + * Get Default Mobile Breakpoint. + * + * @return int + */ + public static function get_default_breakpoint_sm() { + return apply_filters( 'vpf_default_breakpoint_sm', self::$default_sm ); + } + + /** + * Get Mobile Breakpoint. + * + * @return int + */ + public static function get_breakpoint_sm() { + return apply_filters( 'vpf_breakpoint_sm', self::get_default_breakpoint_sm() ); + } + + /** + * Get Default Tablet Breakpoint. + * + * @return int + */ + public static function get_default_breakpoint_md() { + return apply_filters( 'vpf_default_breakpoint_md', self::$default_md ); + } + + /** + * Get Tablet Breakpoint. + * + * @return int + */ + public static function get_breakpoint_md() { + return apply_filters( 'vpf_breakpoint_md', self::get_default_breakpoint_md() ); + } + + /** + * Get Default Desktop Breakpoint. + * + * @return int + */ + public static function get_default_breakpoint_lg() { + return apply_filters( 'vpf_default_breakpoint_lg', self::$default_lg ); + } + + /** + * Get Desktop Breakpoint. + * + * @return int + */ + public static function get_breakpoint_lg() { + return apply_filters( 'vpf_breakpoint_lg', self::get_default_breakpoint_lg() ); + } + + /** + * Get Default Large Desktop Breakpoint. + * + * @return int + */ + public static function get_default_breakpoint_xl() { + return apply_filters( 'vpf_default_breakpoint_xl', self::$default_xl ); + } + + /** + * Get Large Desktop Breakpoint. + * + * @return int + */ + public static function get_breakpoint_xl() { + return apply_filters( 'vpf_breakpoint_xl', self::get_default_breakpoint_xl() ); + } +} diff --git a/classes/class-controls.php b/classes/class-controls.php new file mode 100644 index 00000000..47c3f5f3 --- /dev/null +++ b/classes/class-controls.php @@ -0,0 +1,356 @@ + '', + + 'type' => 'text', + 'label' => false, + 'description' => false, + 'group' => false, + 'name' => '', + 'value' => '', + 'placeholder' => '', + 'readonly' => false, + 'value_callback' => '', + 'sanitize_callback' => '', + 'reload_iframe' => true, + + // control-specific args. + // notice. + 'status' => 'info', + // select. + 'options' => array(), + 'searchable' => false, + 'multiple' => false, + 'creatable' => false, + // range. + 'min' => '', + 'max' => '', + 'step' => '1', + // textarea. + 'cols' => '', + 'rows' => '', + // color. + 'alpha' => false, + 'gradient' => false, + // align. + 'extended' => false, + // code editor. + 'mode' => 'css', + 'max_lines' => 20, + 'min_lines' => 5, + 'allow_modal' => true, + 'classes_tree' => false, + 'encode' => false, + 'code_placeholder' => '', + // elements selector. + 'locations' => array(), + // gallery. + 'focal_point' => false, + + // hint, deprecated. + 'hint' => false, + 'hint_place' => 'top', + + // display in setup wizard. + 'setup_wizard' => false, + + // support for WPML. + 'wpml' => false, + + // condition. + 'condition' => array( + /** + * Array of arrays with data: + * 'control' - control name. + * 'operator' - operator (==, !==, >, <, >=, <=). + * 'value' - condition value. + */ + ), + + // style. + 'style' => array( + /** + * Array of arrays with data: + * 'element' - CSS selector string (.vp-portfolio__item, .vp-portfolio__item-overlay, etc). + * 'property' - CSS property (color, font-size, etc). + * 'mask' - CSS value mask, for ex. "$px". + */ + ), + + 'class' => '', + 'wrapper_class' => '', + ); + + /** + * Visual_Portfolio_Controls constructor. + */ + public function __construct() { + add_action( 'wp_ajax_vp_dynamic_control_callback', array( $this, 'ajax_dynamic_control_callback' ) ); + } + + /** + * Dynamic control AJAX callback. + */ + public function ajax_dynamic_control_callback() { + check_ajax_referer( 'vp-ajax-nonce', 'nonce' ); + if ( ! isset( $_POST['vp_control_name'] ) ) { + wp_die(); + } + + $result = null; + $found = null; + $controls = self::get_registered_array(); + + // find control callback. + foreach ( $controls as $control ) { + if ( + isset( $control['name'] ) && + $control['name'] === $_POST['vp_control_name'] && + isset( $control['value_callback'] ) && + is_callable( $control['value_callback'] ) + ) { + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash + $attributes = isset( $_POST['vp_attributes'] ) ? Visual_Portfolio_Security::sanitize_attributes( $_POST['vp_attributes'] ) : array(); + $found = true; + $result = call_user_func( $control['value_callback'], $attributes, $control ); + break; + } + } + + if ( null === $found ) { + echo wp_json_encode( + array( + 'response' => esc_attr__( 'Dynamic control callback function is not found.', 'visual-portfolio' ), + 'error' => true, + ) + ); + } else { + echo wp_json_encode( + array( + 'response' => $result, + 'success' => true, + ) + ); + } + + wp_die(); + } + + /** + * Register category to print in the future. + * + * @param array $categories - categories args. + */ + public static function register_categories( $categories = array() ) { + self::$registered_categories = array_merge( self::$registered_categories, $categories ); + } + + /** + * Register control to print in the future. + * + * @param array $args - control args. + */ + public static function register( $args = array() ) { + if ( ! isset( $args['name'] ) ) { + return; + } + self::$registered_fields[ $args['name'] ] = apply_filters( 'vpf_register_control', $args, $args['name'] ); + + do_action( 'vpf_registered_control', $args['name'], $args ); + } + + /** + * Get all registered controls. + * + * @return array + */ + public static function get_registered_array() { + // Return cached version of all controls. + if ( ! empty( self::$cached_all_registered_controls ) ) { + return self::$cached_all_registered_controls; + } + + $result = array(); + + foreach ( self::$registered_fields as $k => $args ) { + $result[ $k ] = array_merge( self::$default_args, $args ); + + // Gallery image controls. + if ( 'gallery' === $result[ $k ]['type'] && isset( $result[ $k ]['image_controls'] ) && ! empty( $result[ $k ]['image_controls'] ) ) { + $img_controls = array(); + + // Extend. + // phpcs:ignore Squiz.PHP.CommentedOutCode.Found, Squiz.Commenting.BlockComment.NoEmptyLineBefore + /* + * Example: + array( + 'title' => array( + 'type' => 'text', + 'label' => esc_html__( 'Title', 'visual-portfolio' ), + ), + 'description' => array( + 'type' => 'textarea', + 'label' => esc_html__( 'Description', 'visual-portfolio' ), + ), + ) + */ + $result[ $k ]['image_controls'] = apply_filters( 'vpf_extend_image_controls', $result[ $k ]['image_controls'], $result[ $k ]['name'] ); + + // Get default controls data. + foreach ( $result[ $k ]['image_controls'] as $i => $img_args ) { + $img_controls[ $i ] = array_merge( self::$default_args, $img_args ); + } + + $result[ $k ]['image_controls'] = $img_controls; + } + + $result[ $k ] = apply_filters( 'vpf_registered_control_args', $result[ $k ] ); + } + + self::$cached_all_registered_controls = apply_filters( 'vpf_registered_controls', $result ); + + return self::$cached_all_registered_controls; + } + + /** + * Get all registered categories. + * + * @return array + */ + public static function get_registered_categories() { + return self::$registered_categories; + } + + /** + * Get registered control value. + * + * @param string $name - field name. + * @param int|bool $post_id - post id to get meta data. + * + * @return mixed + */ + public static function get_registered_value( $name, $post_id = false ) { + // get meta data. + $result = null; + + // get meta data from saved layout. + // get all layout meta at once and cache them (works faster). + if ( $post_id ) { + if ( ! isset( self::$cached_saved_layout_meta[ $post_id ] ) ) { + $saved_meta = get_post_meta( $post_id ); + $result_meta = array(); + + // We should unserialize array data as in standard function https://developer.wordpress.org/reference/functions/get_metadata_raw/. + if ( is_array( $saved_meta ) ) { + foreach ( $saved_meta as $key => $val ) { + if ( isset( $val[0] ) ) { + $result_meta[ $key ] = maybe_unserialize( $val[0] ); + } + } + } + + self::$cached_saved_layout_meta[ $post_id ] = $result_meta; + } + if ( isset( self::$cached_saved_layout_meta[ $post_id ] ) && isset( self::$cached_saved_layout_meta[ $post_id ][ 'vp_' . $name ] ) ) { + $result = self::$cached_saved_layout_meta[ $post_id ][ 'vp_' . $name ]; + } + } + + // registered data. + $registered_array = self::get_registered_array(); + $registered_data = isset( $registered_array[ $name ] ) ? $registered_array[ $name ] : false; + + // find default. + $default = null; + if ( isset( $registered_data ) ) { + $default = isset( $registered_data['default'] ) ? $registered_data['default'] : $default; + } + if ( ! isset( $result ) && isset( $default ) ) { + $result = $default; + } + + // filter. + $result = apply_filters( 'vpf_control_value', $result, $name, $post_id ); + + // fix for gallery array. + if ( isset( $registered_data['type'] ) && 'gallery' === $registered_data['type'] ) { + $result = (array) ( is_string( $result ) ? json_decode( $result, true ) : $result ); + + // add image url if doesn't exist. + foreach ( $result as $k => $data ) { + if ( ! isset( $data['imgUrl'] ) && isset( $data['id'] ) ) { + $result[ $k ]['imgUrl'] = Visual_Portfolio_Images::wp_get_attachment_image_url( $data['id'], 'full' ); + $result[ $k ]['imgThumbnailUrl'] = Visual_Portfolio_Images::wp_get_attachment_image_url( $data['id'], 'thumbnail' ); + } + } + } + + // fix bool values. + if ( 'false' === $result ) { + $result = false; + } + if ( 'true' === $result ) { + $result = true; + } + + if ( 'custom_css' === $name && $result ) { + // Decode. + $result = visual_portfolio_decode( $result ); + + // Fix for old plugin versions (< 2.0). + $result = str_replace( '>', '>', $result ); + } + + return $result; + } +} + +new Visual_Portfolio_Controls(); diff --git a/classes/class-custom-post-meta.php b/classes/class-custom-post-meta.php new file mode 100644 index 00000000..dce40b0f --- /dev/null +++ b/classes/class-custom-post-meta.php @@ -0,0 +1,419 @@ +is_block_editor() ) { + return true; + } + + return false; + } + + /** + * Add video post format. + */ + public static function add_extra_post_format() { + global $_wp_theme_features; + + $formats = array( 'image', 'video' ); + + // Add existing formats. + if ( isset( $_wp_theme_features['post-formats'] ) && isset( $_wp_theme_features['post-formats'][0] ) ) { + $formats = array_merge( (array) $_wp_theme_features['post-formats'][0], $formats ); + } + $formats = array_unique( $formats ); + + add_theme_support( 'post-formats', $formats ); + } + + /** + * Register post meta. + */ + public static function register_post_meta() { + $post_type_names = array_keys( get_post_types() ); + + foreach ( $post_type_names as $post_type ) { + if ( ! is_post_type_viewable( $post_type ) ) { + continue; + } + + // Register meta for all post types. + register_meta( + 'post', + '_vp_format_video_url', + array( + 'object_subtype' => $post_type, + 'type' => 'string', + 'single' => true, + 'show_in_rest' => true, + 'auth_callback' => array( __CLASS__, 'rest_auth' ), + ) + ); + register_meta( + 'post', + '_vp_image_focal_point', + array( + 'object_subtype' => $post_type, + 'type' => 'object', + 'single' => true, + 'show_in_rest' => array( + 'schema' => array( + 'type' => 'object', + 'properties' => array( + 'x' => array( + 'type' => 'number', + ), + 'y' => array( + 'type' => 'number', + ), + ), + ), + ), + 'auth_callback' => array( __CLASS__, 'rest_auth' ), + ) + ); + + // Add support for 'custom-fields' to work in Gutenberg. + add_post_type_support( $post_type, 'custom-fields' ); + } + } + + /** + * Determines REST API authentication. + * + * @param bool $allowed Whether it is allowed. + * @param string $meta_key The meta key being checked. + * @param int $post_id The post ID being checked. + * @param int $user_id The user ID being checked. + * @param string $cap The current capability. + * @param array $caps All capabilities. + * @return bool Whether the user can do it. + */ + // phpcs:ignore + public static function rest_auth( $allowed, $meta_key, $post_id, $user_id, $cap, $caps ) { + return user_can( $user_id, 'edit_post', $post_id ); + } + + /** + * Add post format metaboxes. + * + * @param string $post_type post type. + */ + public static function add_post_format_metaboxes( $post_type ) { + // Prevent if Gutenberg enabled. + if ( self::is_gutenberg() ) { + return; + } + + // Prevent if no Video post format supported. + if ( ! post_type_supports( $post_type, 'post-formats' ) ) { + return; + } + + add_meta_box( + 'vp_format_video', + esc_html__( 'Video', 'visual-portfolio' ), + array( __CLASS__, 'add_video_format_metabox' ), + null, + 'side', + 'default' + ); + } + + /** + * Add Video Format metabox + * + * @param object $post The post object. + */ + public static function add_video_format_metabox( $post ) { + wp_nonce_field( basename( __FILE__ ), 'vp_format_video_nonce' ); + + $video_url = self::get_video_format_url( $post->ID ); + $oembed_html = false; + + $wpkses_iframe = array( + 'iframe' => array( + 'src' => array(), + 'height' => array(), + 'width' => array(), + 'frameborder' => array(), + 'allowfullscreen' => array(), + ), + ); + + if ( $video_url ) { + $oembed = visual_portfolio()->get_oembed_data( $video_url ); + + if ( $oembed && isset( $oembed['html'] ) ) { + $oembed_html = $oembed['html']; + } + } + ?> + +

+ +
+ +
+ + $reading_time ) { + $reading_time = esc_html__( '< 1', 'visual-portfolio' ); + } else { + $reading_time = ceil( $reading_time ); + } + + return $reading_time; + } + + /** + * Calculate words count. + * + * @param int $post_id The post ID. + */ + public static function calculate_words_count( $post_id ) { + if ( ! $post_id ) { + $post_id = get_the_ID(); + } + + $content = get_the_content( null, false, $post_id ); + $content = wp_strip_all_tags( $content ); + $words_count = count( preg_split( '/\s+/', $content ) ); + + return $words_count; + } +} + +Visual_Portfolio_Custom_Post_Meta::init(); diff --git a/classes/class-custom-post-type.php b/classes/class-custom-post-type.php new file mode 100644 index 00000000..1a3aee54 --- /dev/null +++ b/classes/class-custom-post-type.php @@ -0,0 +1,869 @@ + array( + // We have to use label from the actual Portfolio page + // because 3rd-party breadcrumbs will display this name and it is + // required to show breadcrumbs like: + // + // Home > Portfolio > Project Name + // + // Instead of this one: + // Home > Projects > Project Name. + 'name' => $custom_label, + 'singular_name' => _x( 'Project', 'Post Type Singular Name', 'visual-portfolio' ), + 'menu_name' => visual_portfolio()->plugin_name, + 'parent_item_colon' => __( 'Parent Project', 'visual-portfolio' ), + 'all_items' => __( 'Projects', 'visual-portfolio' ), + 'view_item' => __( 'View Project', 'visual-portfolio' ), + 'add_new_item' => __( 'Add New Project', 'visual-portfolio' ), + 'add_new' => __( 'Add New', 'visual-portfolio' ), + 'edit_item' => __( 'Edit Project', 'visual-portfolio' ), + 'update_item' => __( 'Update Project', 'visual-portfolio' ), + 'search_items' => __( 'Search Project', 'visual-portfolio' ), + 'not_found' => __( 'Not Found', 'visual-portfolio' ), + 'not_found_in_trash' => __( 'Not found in Trash', 'visual-portfolio' ), + ), + 'public' => true, + 'publicly_queryable' => true, + 'has_archive' => $custom_slug, + 'show_ui' => true, + + // adding to custom menu manually. + 'show_in_menu' => true, + 'show_in_admin_bar' => true, + 'show_in_rest' => true, + 'menu_icon' => 'dashicons-visual-portfolio', + 'taxonomies' => array( + 'portfolio_category', + 'portfolio_tag', + ), + 'map_meta_cap' => true, + 'capability_type' => 'portfolio', + 'rewrite' => array( + 'slug' => $permalinks['portfolio_base'], + 'with_front' => false, + ), + 'supports' => array( + 'title', + 'editor', + 'author', + 'thumbnail', + 'comments', + 'revisions', + 'excerpt', + 'post-formats', + 'page-attributes', + 'custom-fields', + ), + ) + ); + + register_taxonomy( + 'portfolio_category', + 'portfolio', + array( + 'label' => esc_html__( 'Portfolio Categories', 'visual-portfolio' ), + 'labels' => array( + 'menu_name' => esc_html__( 'Categories', 'visual-portfolio' ), + ), + 'rewrite' => array( + 'slug' => $permalinks['category_base'], + ), + 'hierarchical' => true, + 'publicly_queryable' => true, + 'show_in_nav_menus' => true, + 'show_in_rest' => true, + 'show_admin_column' => true, + 'map_meta_cap' => true, + 'capability_type' => 'portfolio', + ) + ); + register_taxonomy( + 'portfolio_tag', + 'portfolio', + array( + 'label' => esc_html__( 'Portfolio Tags', 'visual-portfolio' ), + 'labels' => array( + 'menu_name' => esc_html__( 'Tags', 'visual-portfolio' ), + ), + 'rewrite' => array( + 'slug' => $permalinks['tag_base'], + ), + 'hierarchical' => false, + 'publicly_queryable' => true, + 'show_in_nav_menus' => true, + 'show_in_rest' => true, + 'show_admin_column' => true, + 'map_meta_cap' => true, + 'capability_type' => 'portfolio', + ) + ); + } + + // portfolio lists post type. + register_post_type( + 'vp_lists', + array( + 'labels' => array( + 'name' => _x( 'Saved Layouts', 'Post Type General Name', 'visual-portfolio' ), + 'singular_name' => _x( 'Saved Layout', 'Post Type Singular Name', 'visual-portfolio' ), + 'menu_name' => visual_portfolio()->plugin_name, + 'parent_item_colon' => __( 'Parent Project', 'visual-portfolio' ), + 'all_items' => __( 'Saved Layouts', 'visual-portfolio' ), + 'view_item' => __( 'View Saved Layout', 'visual-portfolio' ), + 'add_new_item' => __( 'Add New Saved Layout', 'visual-portfolio' ), + 'add_new' => __( 'Add New', 'visual-portfolio' ), + 'edit_item' => __( 'Edit Saved Layout', 'visual-portfolio' ), + 'update_item' => __( 'Update Saved Layout', 'visual-portfolio' ), + 'search_items' => __( 'Search Saved Layout', 'visual-portfolio' ), + 'not_found' => __( 'Not Found', 'visual-portfolio' ), + 'not_found_in_trash' => __( 'Not found in Trash', 'visual-portfolio' ), + ), + 'public' => false, + 'has_archive' => false, + 'show_ui' => true, + + // adding to custom menu manually. + 'show_in_menu' => self::get_menu_slug(), + 'show_in_rest' => true, + 'map_meta_cap' => true, + 'capability_type' => 'vp_list', + 'rewrite' => true, + 'supports' => array( + 'title', + 'editor', + 'revisions', + ), + 'template' => array( + array( + 'visual-portfolio/saved-editor', + ), + ), + // we can't use it since blocks didn't inserted in some posts. + // 'template_lock' => 'all',. + ) + ); + } + + /** + * Add filter by custom taxonomies + * + * @param String $post_type - post type name. + */ + public function filter_custom_post_by_taxonomies( $post_type ) { + // Apply this only on a specific post type. + if ( 'portfolio' !== $post_type ) { + return; + } + + // A list of taxonomy slugs to filter by. + $taxonomies = array( 'portfolio_category', 'portfolio_tag' ); + + foreach ( $taxonomies as $taxonomy_slug ) { + // Retrieve taxonomy data. + $taxonomy_obj = get_taxonomy( $taxonomy_slug ); + $taxonomy_name = $taxonomy_obj->labels->name; + + // Retrieve taxonomy terms. + $terms = get_terms( $taxonomy_slug ); + + // Display filter HTML. + echo ''; + } + } + + /** + * Add Roles + */ + public function add_role_caps() { + if ( ! is_blog_installed() ) { + return; + } + + global $wp_version; + + $check_string = 'Plugin: ' . VISUAL_PORTFOLIO_VERSION . ' WP: ' . $wp_version; + + if ( get_option( 'visual_portfolio_updated_caps' ) === $check_string ) { + return; + } + + $wp_roles = wp_roles(); + + if ( ! isset( $wp_roles ) || empty( $wp_roles ) || ! $wp_roles ) { + return; + } + + $author = $wp_roles->get_role( 'author' ); + + $wp_roles->add_role( + 'portfolio_manager', + __( 'Portfolio Manager', 'visual-portfolio' ), + $author->capabilities + ); + $wp_roles->add_role( + 'portfolio_author', + __( 'Portfolio Author', 'visual-portfolio' ), + $author->capabilities + ); + + $portfolio_cap = array( + 'read_portfolio', + 'read_private_portfolio', + 'read_private_portfolios', + 'edit_portfolio', + 'edit_portfolios', + 'edit_others_portfolios', + 'edit_private_portfolios', + 'edit_published_portfolios', + 'delete_portfolio', + 'delete_portfolios', + 'delete_others_portfolios', + 'delete_private_portfolios', + 'delete_published_portfolios', + 'publish_portfolios', + + // Terms. + 'manage_portfolio_terms', + 'edit_portfolio_terms', + 'delete_portfolio_terms', + 'assign_portfolio_terms', + ); + + $lists_cap = array( + 'read_vp_list', + 'read_private_vp_list', + 'read_private_vp_lists', + 'edit_vp_list', + 'edit_vp_lists', + 'edit_others_vp_lists', + 'edit_private_vp_lists', + 'edit_published_vp_lists', + 'delete_vp_list', + 'delete_vp_lists', + 'delete_others_vp_lists', + 'delete_private_vp_lists', + 'delete_published_vp_lists', + 'publish_vp_lists', + ); + + /** + * Add capacities + */ + foreach ( $portfolio_cap as $cap ) { + $wp_roles->add_cap( 'portfolio_manager', $cap ); + $wp_roles->add_cap( 'portfolio_author', $cap ); + $wp_roles->add_cap( 'administrator', $cap ); + $wp_roles->add_cap( 'editor', $cap ); + } + foreach ( $lists_cap as $cap ) { + $wp_roles->add_cap( 'portfolio_manager', $cap ); + $wp_roles->add_cap( 'administrator', $cap ); + } + + update_option( 'visual_portfolio_updated_caps', $check_string ); + } + + /** + * Remove screen options from vp list page. + * + * @param bool $return return default value. + * @param object $screen_object screen object. + * + * @return bool + */ + public function remove_screen_options( $return, $screen_object ) { + if ( 'vp_lists' === $screen_object->id ) { + return false; + } + return $return; + } + + /** + * Add featured image in portfolio list + * + * @param array $columns columns of the table. + * + * @return array + */ + public function add_portfolio_img_column( $columns = array() ) { + $column_meta = array( + 'portfolio_post_thumbs' => esc_html__( 'Thumbnail', 'visual-portfolio' ), + ); + + // insert after first column. + $columns = array_slice( $columns, 0, 1, true ) + $column_meta + array_slice( $columns, 1, null, true ); + + return $columns; + } + + /** + * Add thumb to the column + * + * @param bool $column_name column name. + */ + public function manage_portfolio_img_column( $column_name = false ) { + if ( 'portfolio_post_thumbs' === $column_name ) { + echo ''; + if ( has_post_thumbnail() ) { + the_post_thumbnail( 'thumbnail' ); + } + echo ''; + } + } + + /** + * Show notice in vp_lists admin list page. + */ + public function add_vp_lists_notice() { + $current_screen = get_current_screen(); + + if ( ! isset( $current_screen->post_type ) || 'vp_lists' !== $current_screen->post_type ) { + return; + } + + ?> +
+
+ +
+
+

+ + +

+

+ avoid using Saved Layouts. See here more info about %2$s Blocks.', 'visual-portfolio' ), + 'https://visualportfolio.co/docs/portfolio-blocks/', + visual_portfolio()->plugin_name + ) + ); + ?> +

+

+ Reusable Blocks.', 'visual-portfolio' ), 'https://www.wpbeginner.com/beginners-guide/how-to-create-a-reusable-block-in-wordpress/' ) ); + ?> +

+

+ read more info in documentation. Since WordPress moved from Shortcodes to Blocks system, we prepared for you advanced blocks.', 'visual-portfolio' ), 'https://visualportfolio.co/docs/saved-layouts-and-shortcodes/' ) ); + ?> +

+
+
+ esc_html__( 'Icon', 'visual-portfolio' ), + ); + + // insert after first column. + $columns = array_slice( $columns, 0, 1, true ) + $column_icon + array_slice( $columns, 1, null, true ); + + // Shortcode column. + $column_shortcode = array( + 'vp_lists_post_shortcode' => esc_html__( 'Shortcode', 'visual-portfolio' ), + ); + + // insert before last column. + $columns = array_slice( $columns, 0, count( $columns ) - 1, true ) + $column_shortcode + array_slice( $columns, count( $columns ) - 1, null, true ); + + return $columns; + } + + /** + * Add icons and shortcode columns in vp_lists admin. + * + * @param bool $column_name column name. + */ + public function manage_vp_lists_custom_columns( $column_name = false ) { + if ( 'vp_lists_post_icon' === $column_name ) { + $all_layouts = Visual_Portfolio_Get::get_all_layouts(); + $opts = Visual_Portfolio_Get::get_options( array( 'id' => get_the_ID() ) ); + $layout = isset( $opts['layout'] ) ? $opts['layout'] : false; + $icon = ''; + + if ( $layout ) { + foreach ( $all_layouts as $name => $data ) { + if ( $name === $layout && isset( $data['icon'] ) ) { + $icon = $data['icon']; + } + } + + echo ''; + echo wp_kses( $icon, 'vp_svg' ); + echo ''; + } + } + + if ( 'vp_lists_post_shortcode' === $column_name ) { + echo ''; + } + } + + /** + * Add custom filtering selects for vp_lists admin screen. + */ + public function restrict_manage_posts_vp_lists() { + global $typenow; + + if ( 'vp_lists' === $typenow ) { + $all_layouts = Visual_Portfolio_Get::get_all_layouts(); + $all_items_styles = Visual_Portfolio_Get::get_all_items_styles(); + $all_content_sources = array( + 'post-based' => esc_html__( 'Posts', 'visual-portfolio' ), + 'images' => esc_html__( 'Images', 'visual-portfolio' ), + 'social-stream' => esc_html__( 'Social', 'visual-portfolio' ), + ); + + // phpcs:ignore WordPress.Security.NonceVerification + $selected_layout = isset( $_GET['vp_layout'] ) ? sanitize_text_field( wp_unslash( $_GET['vp_layout'] ) ) : ''; + // phpcs:ignore WordPress.Security.NonceVerification + $selected_items_style = isset( $_GET['vp_items_style'] ) ? sanitize_text_field( wp_unslash( $_GET['vp_items_style'] ) ) : ''; + // phpcs:ignore WordPress.Security.NonceVerification + $selected_content_source = isset( $_GET['vp_content_source'] ) ? sanitize_text_field( wp_unslash( $_GET['vp_content_source'] ) ) : ''; + + ?> + + + + query_vars; + + if ( 'edit.php' === $pagenow && isset( $q_vars['post_type'] ) && 'vp_lists' === $q_vars['post_type'] ) { + $meta_query = array(); + + // phpcs:ignore WordPress.Security.NonceVerification + $filter_layout = isset( $_GET['vp_layout'] ) ? sanitize_text_field( wp_unslash( $_GET['vp_layout'] ) ) : ''; + // phpcs:ignore WordPress.Security.NonceVerification + $filter_items_style = isset( $_GET['vp_items_style'] ) ? sanitize_text_field( wp_unslash( $_GET['vp_items_style'] ) ) : ''; + // phpcs:ignore WordPress.Security.NonceVerification + $filter_content_source = isset( $_GET['vp_content_source'] ) ? sanitize_text_field( wp_unslash( $_GET['vp_content_source'] ) ) : ''; + + if ( $filter_layout ) { + $meta_query[] = array( + 'key' => 'vp_layout', + 'value' => $filter_layout, + ); + } + if ( $filter_items_style ) { + $meta_query[] = array( + 'key' => 'vp_items_style', + 'value' => $filter_items_style, + ); + } + if ( $filter_content_source ) { + $meta_query[] = array( + 'key' => 'vp_content_source', + 'value' => $filter_content_source, + ); + } + + if ( ! empty( $meta_query ) ) { + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query + $q_vars['meta_query'] = $meta_query; + } + } + } + + /** + * Allowed blocks for vp_lists post type. + * + * @param array $allowed_block_types - blocks. + * @param object $editor_context - editor context. + * @return array + */ + public function vp_lists_allowed_block_types_all( $allowed_block_types, $editor_context ) { + if ( empty( $editor_context->post ) || 'vp_lists' !== $editor_context->post->post_type ) { + return $allowed_block_types; + } + + return array( 'visual-portfolio/saved-editor' ); + } + + /** + * Add admin dropdown menu with all used Layouts on the current page. + */ + public function wp_before_admin_bar_render() { + global $wp_admin_bar; + + if ( ! is_super_admin() || ! is_admin_bar_showing() ) { + return; + } + + // add all nodes of all Slider. + $layouts = Visual_Portfolio_Get::get_all_used_layouts(); + $layouts = array_unique( $layouts ); + + if ( ! empty( $layouts ) ) { + $wp_admin_bar->add_node( + array( + 'parent' => false, + 'id' => 'visual_portfolio', + 'title' => visual_portfolio()->plugin_name, + 'href' => admin_url( 'edit.php?post_type=vp_lists' ), + ) + ); + + // get visual-portfolio post types by IDs. + // Don't use WP_Query on the admin side https://core.trac.wordpress.org/ticket/18408 . + $vp_query = get_posts( + array( + 'post_type' => 'vp_lists', + 'posts_per_page' => -1, + 'paged' => -1, + 'post__in' => $layouts, + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + ) + ); + foreach ( $vp_query as $post ) { + $wp_admin_bar->add_node( + array( + 'parent' => 'visual_portfolio', + 'id' => 'vp_list_' . esc_html( $post->ID ), + 'title' => esc_html( $post->post_title ), + 'href' => admin_url( 'post.php?post=' . $post->ID ) . '&action=edit', + ) + ); + } + } + } + + /** + * Force set Gutenberg editor for 'vp_lists' in Classic Editor plugin. + * + * @param array $editors Associative array of the editors and whether they are enabled for the post type. + * @param string $post_type The post type. + */ + public function vp_lists_classic_plugin_force_gutenberg( $editors, $post_type ) { + if ( 'vp_lists' !== $post_type ) { + return $editors; + } + + return array( + 'classic_editor' => false, + 'block_editor' => true, + ); + } + + /** + * Force set Gutenberg editor for 'vp_lists' in Classic Editor plugin. + * + * @param boolean $use_block_editor Use block editor. + * @param string $post_type The post type. + */ + public function vp_lists_classic_plugin_force_gutenberg_2( $use_block_editor, $post_type ) { + if ( 'vp_lists' !== $post_type ) { + return $use_block_editor; + } + + return true; + } + + /** + * Force set Gutenberg editor for 'vp_lists' in 3rd-party plugins/themes, that uses their own builders. + * + * @param boolean $use_block_editor Use block editor. + * @param object $post The post object. + */ + public function vp_lists_classic_plugin_force_gutenberg_3( $use_block_editor, $post ) { + if ( isset( $post->post_type ) && 'vp_lists' === $post->post_type ) { + return true; + } + + return $use_block_editor; + } + + /** + * Force enable Gutenberg in 'vp_lists' for users with disabled option "Visual Editor". + * + * @param boolean $enabled Rich edit enabled. + */ + public function vp_lists_user_can_richedit_force( $enabled ) { + global $post_type; + + if ( isset( $post_type ) && 'vp_lists' !== $post_type ) { + return $enabled; + } + + return true; + } + + /** + * Add Admin Page + */ + public function admin_menu() { + // Remove Add New submenu item. + remove_submenu_page( self::get_menu_slug(), 'post-new.php?post_type=portfolio' ); + + // Documentation menu link. + add_submenu_page( + self::get_menu_slug(), + esc_html__( 'Documentation', 'visual-portfolio' ), + esc_html__( 'Documentation', 'visual-portfolio' ), + 'manage_options', + Visual_Portfolio_Admin::get_plugin_site_url( + array( + 'sub_path' => 'docs/getting-started', + 'utm_campaign' => 'docs', + ) + ) + ); + } + + /** + * Add Proofing Admin Page. + * + * @return void + */ + public function add_proofing_admin_menu() { + // Proofing menu link. + add_submenu_page( + self::get_menu_slug(), + esc_html__( 'Proofing', 'visual-portfolio' ), + esc_html__( 'Proofing', 'visual-portfolio' ), + 'manage_options', + 'vpf_proofing_page', + array( $this, 'go_proofing_pro_page' ) + ); + } + + /** + * Proofing. + * Render of proofing page. + */ + public function go_proofing_pro_page() { + // phpcs:ignore WordPress.Security.NonceVerification + if ( ! isset( $_GET['page'] ) || empty( $_GET['page'] ) ) { + return; + } + + // phpcs:ignore WordPress.Security.NonceVerification + if ( 'vpf_proofing_page' === $_GET['page'] ) { + $pro_url = Visual_Portfolio_Admin::get_plugin_site_url( + array( + 'utm_medium' => 'settings_page', + 'utm_campaign' => 'proofing', + ) + ); + ?> + + + + + + + + add_deprecated_filter( 'vpf_print_layout_control_args', '2.9.0', 'vpf_registered_control_args' ); + $this->add_deprecated_filter( 'vpf_get_layout_option', '2.9.0', 'vpf_control_value' ); + $this->add_deprecated_filter( 'vpf_extend_popup_image', '2.9.0', 'vpf_popup_image_data' ); + $this->add_deprecated_filter( 'vpf_extend_custom_popup_image', '2.9.0', 'vpf_popup_custom_image_data' ); + $this->add_deprecated_filter( 'vpf_print_popup_data', '2.9.0', 'vpf_popup_output' ); + $this->add_deprecated_filter( 'vpf_wp_get_attachment_image_extend', '2.9.0', 'vpf_wp_get_attachment_image' ); + + // Deprecated some builtin_controls for skins v3.0.0. + add_filter( 'vpf_items_style_builtin_controls_options', array( $this, 'deprecated_vpf_items_style_builtin_controls_options' ), 20 ); + add_filter( 'vpf_items_style_builtin_controls', array( $this, 'deprecated_vpf_items_style_builtin_controls' ), 20, 4 ); + add_filter( 'vpf_get_options', array( $this, 'deprecated_items_styles_attributes' ), 20, 2 ); + + // Deprecated image args for wp kses since v2.10.4. + // Since v2.20.0 we are using the `vp_image` kses. + add_filter( 'vpf_image_item_args', array( $this, 'deprecated_image_kses_args' ), 9 ); + add_filter( 'vpf_post_item_args', array( $this, 'deprecated_image_kses_args' ), 9 ); + + // Deprecated image noscript argument since v2.6.0. + add_filter( 'vpf_each_item_args', array( $this, 'deprecated_noscript_args' ), 9 ); + } + + /** + * Add Deprecated Filter + * + * @param string $deprecated The deprecated hook. + * @param string $version The version this hook was deprecated. + * @param string $replacement The replacement hook. + */ + public function add_deprecated_filter( $deprecated, $version, $replacement ) { + // Store replacement data. + $this->hooks[] = array( + 'type' => 'filter', + 'deprecated' => $deprecated, + 'replacement' => $replacement, + 'version' => $version, + ); + + // Add generic handler. + // Use a priority of 10, and accepted args of 10 (ignored by WP). + add_filter( $replacement, array( $this, 'apply_deprecated_hook' ), 10, 10 ); + } + + /** + * Add Deprecated Action + * + * @param string $deprecated The deprecated hook. + * @param string $version The version this hook was deprecated. + * @param string $replacement The replacement hook. + */ + public function add_deprecated_action( $deprecated, $version, $replacement ) { + // Store replacement data. + $this->hooks[] = array( + 'type' => 'action', + 'deprecated' => $deprecated, + 'replacement' => $replacement, + 'version' => $version, + ); + + // Add generic handler. + // Use a priority of 10, and accepted args of 10 (ignored by WP). + add_action( $replacement, array( $this, 'apply_deprecated_hook' ), 10, 10 ); + } + + /** + * Apply Deprecated Hook + * + * Apply a deprecated filter during apply_filters() or do_action(). + * + * @return mixed + */ + public function apply_deprecated_hook() { + // Get current hook. + $hook_name = current_filter(); + + // Get args provided to function. + $args = func_get_args(); + + foreach ( $this->hooks as $hook_data ) { + if ( $hook_name !== $hook_data['replacement'] ) { + continue; + } + + // Check if anyone is hooked into this deprecated hook. + if ( has_filter( $hook_data['deprecated'] ) ) { + // Log warning. + // Most probably we will add it later. + // + // _deprecated_hook( $hook_data['deprecated'], $hook_data['version'], $hook_name ); . + + // Apply filters. + if ( 'filter' === $hook_data['type'] ) { + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound + $args[0] = apply_filters_ref_array( $hook_data['deprecated'], $args ); + + // Or do action. + } else { + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound + do_action_ref_array( $hook_data['deprecated'], $args ); + } + } + } + + // Return first arg. + return $args[0]; + } + + /** + * Restore some old builtin_controls for skins. + * + * @param array $builtin_controls - builtin default controls. + * + * @return array + */ + public function deprecated_vpf_items_style_builtin_controls_options( $builtin_controls ) { + return array_merge( + $builtin_controls, + array( + 'images_rounded_corners' => true, + 'show_title' => true, + 'show_categories' => true, + 'show_date' => true, + 'show_author' => true, + 'show_comments_count' => true, + 'show_views_count' => true, + 'show_reading_time' => true, + 'show_excerpt' => true, + 'show_icons' => true, + 'align' => true, + ) + ); + } + + /** + * Restore some old builtin_controls for skins. + * + * @param array $fields - builtin fields. + * @param string $option_name - option name. + * @param array $options - builtin field options. + * @param string $style_name - items style name. + * + * @return array + */ + public function deprecated_vpf_items_style_builtin_controls( $fields, $option_name, $options, $style_name ) { + switch ( $option_name ) { + case 'images_rounded_corners': + $fields[] = array( + 'type' => 'range', + 'label' => esc_html__( 'Images Rounded Corners', 'visual-portfolio' ), + 'name' => 'images_rounded_corners', + 'min' => 0, + 'max' => 100, + 'default' => 0, + 'style' => array( + array( + 'element' => '.vp-portfolio__items-style-' . $style_name, + 'property' => '--vp-items-style-' . $style_name . '--image__border-radius', + 'mask' => '$px', + ), + ), + ); + break; + case 'show_title': + $fields[] = array( + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Display Title', 'visual-portfolio' ), + 'name' => 'show_title', + 'default' => true, + ); + break; + case 'show_categories': + $fields[] = array( + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Display Categories', 'visual-portfolio' ), + 'name' => 'show_categories', + 'group' => 'items_style_categories', + 'default' => true, + ); + $fields[] = array( + 'type' => 'range', + 'label' => esc_html__( 'Categories Count', 'visual-portfolio' ), + 'name' => 'categories_count', + 'group' => 'items_style_categories', + 'min' => 1, + 'max' => 20, + 'default' => 1, + 'condition' => array( + array( + 'control' => 'show_categories', + ), + ), + ); + break; + case 'show_date': + $fields[] = array( + 'type' => 'radio', + 'label' => esc_html__( 'Display Date', 'visual-portfolio' ), + 'name' => 'show_date', + 'group' => 'items_style_date', + 'default' => 'false', + 'options' => array( + 'false' => esc_html__( 'Hide', 'visual-portfolio' ), + 'true' => esc_html__( 'Default', 'visual-portfolio' ), + 'human' => esc_html__( 'Human Format', 'visual-portfolio' ), + ), + ); + $fields[] = array( + 'type' => 'text', + 'name' => 'date_format', + 'group' => 'items_style_date', + 'default' => 'F j, Y', + 'description' => esc_attr__( 'Date format example: F j, Y', 'visual-portfolio' ), + 'wpml' => true, + 'condition' => array( + array( + 'control' => 'show_date', + ), + ), + ); + break; + case 'show_author': + $fields[] = array( + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Display Author', 'visual-portfolio' ), + 'name' => 'show_author', + 'default' => false, + ); + break; + case 'show_comments_count': + $fields[] = array( + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Display Comments Count', 'visual-portfolio' ), + 'name' => 'show_comments_count', + 'default' => false, + 'condition' => array( + array( + 'control' => 'GLOBAL_content_source', + 'value' => 'post-based', + ), + ), + ); + break; + case 'show_views_count': + $fields[] = array( + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Display Views Count', 'visual-portfolio' ), + 'name' => 'show_views_count', + 'default' => false, + 'condition' => array( + array( + 'control' => 'GLOBAL_content_source', + 'value' => 'post-based', + ), + ), + ); + break; + case 'show_reading_time': + $fields[] = array( + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Display Reading Time', 'visual-portfolio' ), + 'name' => 'show_reading_time', + 'default' => false, + 'condition' => array( + array( + 'control' => 'GLOBAL_content_source', + 'value' => 'post-based', + ), + ), + ); + break; + case 'show_excerpt': + $fields[] = array( + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Display Excerpt', 'visual-portfolio' ), + 'name' => 'show_excerpt', + 'group' => 'items_style_excerpt', + 'default' => false, + ); + $fields[] = array( + 'type' => 'number', + 'label' => esc_html__( 'Excerpt Words Count', 'visual-portfolio' ), + 'name' => 'excerpt_words_count', + 'group' => 'items_style_excerpt', + 'default' => 15, + 'min' => 1, + 'max' => 200, + 'condition' => array( + array( + 'control' => 'show_excerpt', + ), + ), + ); + break; + case 'show_icons': + $fields[] = array( + 'type' => 'checkbox', + 'alongside' => esc_html__( 'Display Icon', 'visual-portfolio' ), + 'name' => 'show_icon', + 'default' => false, + ); + break; + case 'align': + $fields[] = array( + 'type' => 'align', + 'label' => esc_html__( 'Caption Align', 'visual-portfolio' ), + 'name' => 'align', + 'default' => 'center', + 'extended' => 'extended' === $options, + ); + break; + // no default. + } + + return $fields; + } + + /** + * Add attributes to block rendering as a fallback + * to prevent errors in changed templates. + * + * @param array $options - block options. + * @param array $attrs - block attributes. + * + * @return array + */ + public function deprecated_items_styles_attributes( $options, $attrs ) { + $styles = array( 'default', 'fade', 'fly', 'emerge' ); + + foreach ( $styles as $style ) { + // Restore align option. + if ( ! isset( $options[ 'items_style_' . $style . '__align' ] ) ) { + $options[ 'items_style_' . $style . '__align' ] = $attrs[ 'items_style_' . $style . '__align' ] ?? 'center'; + } + } + + return $options; + } + + /** + * Allowed attributes for wp_kses used in vp images. + * + * @param array $args vp item args. + * + * @return array + */ + public function deprecated_image_kses_args( $args ) { + if ( ! isset( $args['image_allowed_html'] ) ) { + $args['image_allowed_html'] = array(); + } + if ( ! isset( $args['image_allowed_html']['img'] ) ) { + $args['image_allowed_html']['img'] = array(); + } + + $args['image_allowed_html']['noscript'] = array(); + $args['image_allowed_html']['img'] = array_merge( + $args['image_allowed_html']['img'], + array( + 'src' => array(), + 'srcset' => array(), + 'sizes' => array(), + 'alt' => array(), + 'class' => array(), + 'width' => array(), + 'height' => array(), + + // Lazy loading attributes. + 'loading' => array(), + 'data-src' => array(), + 'data-sizes' => array(), + 'data-srcset' => array(), + 'data-no-lazy' => array(), + ) + ); + + return $args; + } + + /** + * Add noscript string to prevent errors in old templates. + * + * @param array $args vp item args. + * + * @return array + */ + public function deprecated_noscript_args( $args ) { + $args['image_noscript'] = ''; + + return $args; + } +} + +new Visual_Portfolio_Deprecations(); diff --git a/classes/class-get-portfolio.php b/classes/class-get-portfolio.php new file mode 100644 index 00000000..5142c624 --- /dev/null +++ b/classes/class-get-portfolio.php @@ -0,0 +1,2828 @@ +plugin_path . 'vendors/slugify/RuleProvider/RuleProviderInterface.php'; + require_once visual_portfolio()->plugin_path . 'vendors/slugify/RuleProvider/DefaultRuleProvider.php'; + require_once visual_portfolio()->plugin_path . 'vendors/slugify/SlugifyInterface.php'; + require_once visual_portfolio()->plugin_path . 'vendors/slugify/Slugify.php'; +} +/** + * Class Visual_Portfolio_Get + */ +class Visual_Portfolio_Get { + /** + * ID of the current printed portfolio + * + * @var int + */ + private static $id = 0; + + /** + * ID of the current printed single filter + * + * @var int + */ + private static $filter_id = 0; + + /** + * ID of the current printed single sort + * + * @var int + */ + private static $sort_id = 0; + + /** + * Random number for random order. + * + * @var int + */ + private static $rand_seed_session = false; + + /** + * Array with already used IDs on the page. Used for option 'Avoid Duplicates' + * + * @var array + */ + private static $used_posts = array(); + + /** + * Check for main query to avoid duplication. + * + * @var array + */ + private static $check_main_query = true; + + /** + * Array with all used layout IDs + * + * @var array + */ + private static $used_layouts = array(); + + /** + * Get all available layouts. + * + * @return array + */ + public static function get_all_layouts() { + // phpcs:ignore Squiz.PHP.CommentedOutCode.Found, Squiz.Commenting.BlockComment.NoEmptyLineBefore + /* + * Example: + array( + 'new_layout' => array( + 'title' => esc_html__( 'New Layout', 'text_domain' ), + 'controls' => array( + ... controls ... + ), + ), + ) + */ + $layouts = apply_filters( 'vpf_extend_layouts', array() ); + + // Extend specific layout controls. + foreach ( $layouts as $name => $layout ) { + if ( isset( $layout['controls'] ) ) { + // phpcs:ignore Squiz.PHP.CommentedOutCode.Found, Squiz.Commenting.BlockComment.NoEmptyLineBefore + /* + * Example: + array( + ... controls ... + ) + */ + $layouts[ $name ]['controls'] = apply_filters( 'vpf_extend_layout_' . $name . '_controls', $layout['controls'] ); + } + } + + return $layouts; + } + + /** + * Get all available items styles. + * + * @return array + */ + public static function get_all_items_styles() { + // phpcs:ignore Squiz.PHP.CommentedOutCode.Found, Squiz.Commenting.BlockComment.NoEmptyLineBefore + /* + * Example: + array( + 'new_items_style' => array( + 'title' => esc_html__( 'New Items Style', 'visual-portfolio' ), + 'builtin_controls' => array( + 'images_rounded_corners' => true, + 'show_title' => true, + 'show_categories' => true, + 'show_date' => true, + 'show_author' => true, + 'show_comments_count' => true, + 'show_views_count' => true, + 'show_reading_time' => true, + 'show_excerpt' => true, + 'show_icons' => false, + 'align' => true, + ), + 'controls' => array( + ... controls ... + ), + ), + ) + */ + $items_styles = apply_filters( 'vpf_extend_items_styles', array() ); + + // Extend specific item style controls. + foreach ( $items_styles as $name => $style ) { + if ( isset( $style['controls'] ) ) { + // phpcs:ignore Squiz.PHP.CommentedOutCode.Found, Squiz.Commenting.BlockComment.NoEmptyLineBefore + /* + * Example: + array( + ... controls ... + ) + */ + $items_styles[ $name ]['controls'] = apply_filters( 'vpf_extend_item_style_' . $name . '_controls', $style['controls'] ); + } + } + + return $items_styles; + } + + /** + * Get all available options of post. + * + * @param array $atts options for portfolio list to print. + * @return array + */ + public static function get_options( $atts = array() ) { + $id = isset( $atts['id'] ) ? $atts['id'] : false; + $block_id = isset( $atts['block_id'] ) ? $atts['block_id'] : false; + + if ( ! $id && ! $block_id ) { + return false; + } + + $result = array(); + + $registered = Visual_Portfolio_Controls::get_registered_array(); + + // Get default or saved layout options. + foreach ( $registered as $item ) { + if ( isset( $atts[ $item['name'] ] ) ) { + $result[ $item['name'] ] = $atts[ $item['name'] ]; + } else { + $result[ $item['name'] ] = Visual_Portfolio_Controls::get_registered_value( $item['name'], $block_id ? false : $id ); + } + + // fix bool values. + if ( 'false' === $result[ $item['name'] ] ) { + $result[ $item['name'] ] = false; + } + if ( 'true' === $result[ $item['name'] ] ) { + $result[ $item['name'] ] = true; + } + } + + if ( ! isset( $result['id'] ) ) { + $result['id'] = $block_id ? $block_id : $id; + } + + // filter. + $result = apply_filters( 'vpf_get_options', $result, $atts ); + + return $result; + } + + /** + * Check if portfolio showed in preview mode. + */ + public static function is_preview() { + $frame = false; + if ( isset( $_POST['vp_preview_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['vp_preview_nonce'] ), 'vp-ajax-nonce' ) ) { + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash + $frame = isset( $_POST['vp_preview_frame'] ) ? Visual_Portfolio_Security::sanitize_boolean( $_POST['vp_preview_frame'] ) : false; + $id = isset( $_POST['vp_preview_frame_id'] ) ? sanitize_text_field( wp_unslash( $_POST['vp_preview_frame_id'] ) ) : false; + + // Elementor preview. + if ( ! $frame && ! $id && isset( $_REQUEST['vp_preview_type'] ) && 'elementor' === $_REQUEST['vp_preview_type'] ) { + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash + $frame = isset( $_REQUEST['vp_preview_frame'] ) ? Visual_Portfolio_Security::sanitize_boolean( $_REQUEST['vp_preview_frame'] ) : false; + } + } + + return $frame; + } + + /** + * Allow taxonomies to show in Filter + * + * @param string $taxonomy taxonomy name. + * + * @return bool + */ + public static function allow_taxonomies_for_filter( $taxonomy ) { + // check taxonomies from settings. + $custom_taxonomies = Visual_Portfolio_Settings::get_option( 'filter_taxonomies', 'vp_general' ); + $custom_taxonomies = explode( ',', $custom_taxonomies ); + $custom_taxonomies_result = false; + if ( $custom_taxonomies && ! empty( $custom_taxonomies ) ) { + foreach ( $custom_taxonomies as $tax ) { + $custom_taxonomies_result = $custom_taxonomies_result || $taxonomy === $tax; + } + } + + return apply_filters( + 'vpf_allow_taxonomy_for_filter', + $custom_taxonomies_result + || strpos( $taxonomy, 'category' ) !== false + || strpos( $taxonomy, 'jetpack-portfolio-type' ) !== false + || 'product_cat' === $taxonomy, + $taxonomy + ); + } + + /** + * Prepare config, that will be used for output. + * + * @param array $atts options for portfolio list to print. + * + * @return array|bool + */ + public static function get_output_config( $atts = array() ) { + if ( ! is_array( $atts ) ) { + return ''; + } + + $options = self::get_options( $atts ); + + if ( ! $options ) { + return false; + } + + do_action( 'vpf_before_get_output', $options ); + + self::$used_layouts[] = $options['id']; + + // generate unique ID. + $uid = ++self::$id; + $uid = hash( 'crc32b', $uid . $options['id'] ); + $class = 'vp-portfolio vp-uid-' . $uid; + + // Add ID to class. + $class .= ' vp-id-' . $options['id']; + + // Add custom class. + if ( isset( $atts['class'] ) ) { + $class .= ' ' . $atts['class']; + } + + // Add custom CSS from VC. + if ( function_exists( 'vc_shortcode_custom_css_class' ) && isset( $atts['vc_css'] ) ) { + $class .= ' ' . vc_shortcode_custom_css_class( $atts['vc_css'] ); + } + + // stretch class. + if ( $options['stretch'] ) { + $class .= ' vp-portfolio__stretch'; + } + + // Filter to replace the main layout with custom. Particularly needed for password protection or age verification. + $custom_output = apply_filters( 'vpf_custom_output', false, $uid, $class, $options ); + + if ( $custom_output ) { + return array( + 'custom_output' => $custom_output, + ); + } + + $no_image = Visual_Portfolio_Settings::get_option( 'no_image', 'vp_general' ); + + // prepare image sizes. + $img_size_popup = 'vp_xl_popup'; + $img_size_md_popup = 'vp_md_popup'; + $img_size_sm_popup = 'vp_sm_popup'; + $img_size = 'vp_xl'; + $columns_count = false; + + switch ( $options['layout'] ) { + case 'masonry': + $columns_count = (int) $options['masonry_columns']; + break; + case 'grid': + $columns_count = (int) $options['grid_columns']; + break; + case 'tiles': + $columns_count = explode( '|', $options['tiles_type'], 1 ); + $columns_count = (int) $columns_count[0]; + break; + } + + switch ( $columns_count ) { + case 1: + $img_size = 'vp_xl'; + break; + case 2: + $img_size = 'vp_xl'; + break; + case 3: + $img_size = 'vp_xl'; + break; + case 4: + $img_size = 'vp_lg'; + break; + case 5: + $img_size = 'vp_lg'; + break; + } + + $is_preview = self::is_preview(); + $start_page = self::get_current_page_number(); + $is_images = 'images' === $options['content_source']; + $is_social = 'social-stream' === $options['content_source']; + + if ( $is_images || $is_social ) { + $query_opts = self::get_query_params( $options, false, $options['id'] ); + + if ( isset( $query_opts['max_num_pages'] ) ) { + $max_pages = (int) ( $query_opts['max_num_pages'] < $start_page ? $start_page : $query_opts['max_num_pages'] ); + } else { + $max_pages = $start_page; + } + } else { + // Get query params. + $query_opts = self::get_query_params( $options, false, $options['id'] ); + + // stupid hack as wp_reset_postdata() function is not working for some reason... + $old_post = $GLOBALS['post']; + + // get Post List. + $portfolio_query = new WP_Query( $query_opts ); + + $max_pages = (int) ( $portfolio_query->max_num_pages < $start_page ? $start_page : $portfolio_query->max_num_pages ); + } + + $next_page_url = ( ! $max_pages || $max_pages >= $start_page + 1 ) ? self::get_pagenum_link( + array( + 'vp_page' => $start_page + 1, + ) + ) : false; + + $options['start_page'] = $start_page; + $options['max_pages'] = $max_pages; + $options['next_page_url'] = $next_page_url; + + /** + * Prepare data-attributes. + */ + $data_attrs = array( + 'data-vp-layout' => $options['layout'], + 'data-vp-content-source' => $options['content_source'], + 'data-vp-items-style' => $options['items_style'], + 'data-vp-items-click-action' => $options['items_click_action'], + 'data-vp-items-gap' => $options['items_gap'], + 'data-vp-items-gap-vertical' => $options['items_gap_vertical'], + 'data-vp-pagination' => $options['pagination'], + 'data-vp-next-page-url' => $next_page_url, + ); + + if ( + ( + 'post-based' === $options['content_source'] && + 'rand' === $options['posts_order_by'] + ) || + ( + isset( $options['images'] ) && + ! empty( $options['images'] ) && + is_array( $options['images'] ) && + 'rand' === $options['images_order_by'] + ) + ) { + $data_attrs['data-vp-random-seed'] = self::get_rand_seed_session(); + } + + if ( 'tiles' === $options['layout'] || $is_preview ) { + $data_attrs['data-vp-tiles-type'] = $options['tiles_type']; + } + if ( 'masonry' === $options['layout'] || $is_preview ) { + $data_attrs['data-vp-masonry-columns'] = $options['masonry_columns']; + + if ( $options['masonry_images_aspect_ratio'] ) { + $data_attrs['data-vp-masonry-images-aspect-ratio'] = $options['masonry_images_aspect_ratio']; + } + } + if ( 'grid' === $options['layout'] || $is_preview ) { + $data_attrs['data-vp-grid-columns'] = $options['grid_columns']; + + if ( $options['grid_images_aspect_ratio'] ) { + $data_attrs['data-vp-grid-images-aspect-ratio'] = $options['grid_images_aspect_ratio']; + } + } + if ( 'justified' === $options['layout'] || $is_preview ) { + $data_attrs['data-vp-justified-row-height'] = $options['justified_row_height']; + $data_attrs['data-vp-justified-row-height-tolerance'] = $options['justified_row_height_tolerance']; + $data_attrs['data-vp-justified-max-rows-count'] = $options['justified_max_rows_count']; + $data_attrs['data-vp-justified-last-row'] = $options['justified_last_row']; + } + + if ( 'slider' === $options['layout'] || $is_preview ) { + $data_attrs['data-vp-slider-effect'] = $options['slider_effect']; + + switch ( $options['slider_items_height_type'] ) { + case 'auto': + $data_attrs['data-vp-slider-items-height'] = 'auto'; + break; + case 'static': + $data_attrs['data-vp-slider-items-height'] = ( $options['slider_items_height_static'] ? $options['slider_items_height_static'] : '200' ) . 'px'; + $data_attrs['data-vp-slider-items-min-height'] = $options['slider_items_min_height']; + break; + case 'dynamic': + $data_attrs['data-vp-slider-items-height'] = ( $options['slider_items_height_dynamic'] ? $options['slider_items_height_dynamic'] : '80' ) . '%'; + $data_attrs['data-vp-slider-items-min-height'] = $options['slider_items_min_height']; + break; + // no default. + } + + switch ( $options['slider_slides_per_view_type'] ) { + case 'auto': + $data_attrs['data-vp-slider-slides-per-view'] = 'auto'; + break; + case 'custom': + $data_attrs['data-vp-slider-slides-per-view'] = $options['slider_slides_per_view_custom'] ? $options['slider_slides_per_view_custom'] : '3'; + break; + // no default. + } + + $data_attrs['data-vp-slider-speed'] = $options['slider_speed']; + $data_attrs['data-vp-slider-autoplay'] = $options['slider_autoplay']; + $data_attrs['data-vp-slider-autoplay-hover-pause'] = $options['slider_autoplay_hover_pause'] ? 'true' : 'false'; + $data_attrs['data-vp-slider-centered-slides'] = $options['slider_centered_slides'] ? 'true' : 'false'; + $data_attrs['data-vp-slider-loop'] = $options['slider_loop'] ? 'true' : 'false'; + $data_attrs['data-vp-slider-free-mode'] = $options['slider_free_mode'] ? 'true' : 'false'; + $data_attrs['data-vp-slider-free-mode-sticky'] = $options['slider_free_mode_sticky'] ? 'true' : 'false'; + $data_attrs['data-vp-slider-arrows'] = $options['slider_arrows'] ? 'true' : 'false'; + $data_attrs['data-vp-slider-bullets'] = $options['slider_bullets'] ? 'true' : 'false'; + $data_attrs['data-vp-slider-bullets-dynamic'] = $options['slider_bullets_dynamic'] ? 'true' : 'false'; + $data_attrs['data-vp-slider-mousewheel'] = $options['slider_mousewheel'] ? 'true' : 'false'; + + $data_attrs['data-vp-slider-thumbnails'] = $options['slider_thumbnails'] ? 'true' : 'false'; + + if ( $options['slider_thumbnails'] ) { + $data_attrs['data-vp-slider-thumbnails-height'] = 'auto'; + $data_attrs['data-vp-slider-thumbnails-gap'] = $options['slider_thumbnails_gap'] ? $options['slider_thumbnails_gap'] : '0'; + + switch ( $options['slider_thumbnails_height_type'] ) { + case 'auto': + $data_attrs['data-vp-slider-thumbnails-height'] = 'auto'; + break; + case 'static': + $data_attrs['data-vp-slider-thumbnails-height'] = ( $options['slider_thumbnails_height_static'] ? $options['slider_thumbnails_height_static'] : '100' ) . 'px'; + break; + case 'dynamic': + $data_attrs['data-vp-slider-thumbnails-height'] = ( $options['slider_thumbnails_height_dynamic'] ? $options['slider_thumbnails_height_dynamic'] : '30' ) . '%'; + break; + // no default. + } + + switch ( $options['slider_thumbnails_per_view_type'] ) { + case 'auto': + $data_attrs['data-vp-slider-thumbnails-per-view'] = 'auto'; + break; + case 'custom': + $data_attrs['data-vp-slider-thumbnails-per-view'] = $options['slider_thumbnails_per_view_custom'] ? $options['slider_thumbnails_per_view_custom'] : '6'; + break; + // no default. + } + } + } + + // get options for the current style. + $style_options = array(); + $style_options_slug = 'items_style_' . $options['items_style'] . '__'; + foreach ( $options as $k => $opt ) { + // add option to array. + if ( substr( $k, 0, strlen( $style_options_slug ) ) === $style_options_slug ) { + $opt_name = str_replace( $style_options_slug, '', $k ); + + $style_options[ $opt_name ] = $opt; + } + + // remove style options from the options list. + if ( substr( $k, 0, strlen( 'items_style_' ) ) === 'items_style_' ) { + unset( $options[ $k ] ); + } + } + + // phpcs:ignore Squiz.PHP.CommentedOutCode.Found, Squiz.Commenting.BlockComment.NoEmptyLineBefore + /* + * Example: + array( + 'data-vp-my-attribute' => 'data', + ) + */ + $data_attrs = apply_filters( 'vpf_extend_portfolio_data_attributes', $data_attrs, $options, $style_options ); + $class = apply_filters( 'vpf_extend_portfolio_class', $class, $options, $style_options ); + + $items_class = 'vp-portfolio__items vp-portfolio__items-style-' . $options['items_style']; + + if ( isset( $style_options['show_overlay'] ) && $style_options['show_overlay'] ) { + $items_class .= ' vp-portfolio__items-show-overlay-' . $style_options['show_overlay']; + } + if ( isset( $style_options['show_caption'] ) && $style_options['show_caption'] ) { + $items_class .= ' vp-portfolio__items-show-caption-' . $style_options['show_caption']; + } + + if ( isset( $style_options['show_img_overlay'] ) && $style_options['show_img_overlay'] ) { + $items_class .= ' vp-portfolio__items-show-img-overlay-' . $style_options['show_img_overlay']; + } + + $items_class = apply_filters( 'vpf_extend_portfolio_items_class', $items_class, $options, $style_options ); + + /** + * Prepare each item args. + */ + $each_item_args = array( + 'uid' => '', + 'post_id' => '', + 'url' => '', + 'title' => '', + 'excerpt' => '', + 'content' => '', + 'comments_count' => '', + 'comments_url' => '', + 'author' => '', + 'author_url' => '', + 'author_avatar' => '', + 'views_count' => '', + 'reading_time' => '', + 'format' => '', + 'published' => '', + 'published_time' => '', + 'categories' => array(), + 'filter' => '', + 'video' => '', + 'image_id' => '', + 'img_size_popup' => $img_size_popup, + 'img_size_md_popup' => $img_size_md_popup, + 'img_size_sm_popup' => $img_size_sm_popup, + 'img_size' => $img_size, + 'no_image' => $no_image, + 'opts' => $style_options, + 'vp_opts' => $options, + ); + + $items = array(); + + if ( ( $is_images || $is_social ) && + isset( $query_opts['images'] ) && + is_array( $query_opts['images'] ) && + ! empty( $query_opts['images'] ) ) { + + foreach ( $query_opts['images'] as $img ) { + // Get category taxonomies for data filter. + $filter_values = array(); + $categories = array(); + + if ( isset( $img['categories'] ) && is_array( $img['categories'] ) ) { + foreach ( $img['categories'] as $cat ) { + $slug = self::create_slug( $cat ); + + if ( ! in_array( $slug, $filter_values, true ) ) { + // add in filter. + $filter_values[] = $slug; + + // add in categories array. + $url = self::get_pagenum_link( + array( + 'vp_filter' => rawurlencode( $slug ), + 'vp_page' => 1, + ) + ); + + $categories[] = array( + 'slug' => $slug, + 'label' => $cat, + 'description' => '', + 'count' => '', + 'taxonomy' => '', + 'id' => 0, + 'parent' => 0, + 'url' => $url, + ); + } + } + } + + $args = array_merge( + $each_item_args, + array( + 'uid' => isset( $img['uid'] ) && $img['uid'] ? $img['uid'] : '', + 'url' => isset( $img['url'] ) && $img['url'] ? $img['url'] : Visual_Portfolio_Images::wp_get_attachment_image_url( $img['id'], $img_size_popup ), + 'title' => isset( $img['title'] ) && $img['title'] ? $img['title'] : '', + 'content' => isset( $img['description'] ) && $img['description'] ? $img['description'] : '', + 'format' => isset( $img['format'] ) && $img['format'] ? $img['format'] : 'standard', + 'published_time' => isset( $img['published_time'] ) && $img['published_time'] ? $img['published_time'] : '', + 'filter' => implode( ',', $filter_values ), + 'image_id' => intval( $img['id'] ), + 'focal_point' => isset( $img['focalPoint'] ) && $img['focalPoint'] ? $img['focalPoint'] : '', + 'allow_popup' => ! isset( $img['url'] ) || ! $img['url'], + 'categories' => $categories, + 'author' => isset( $img['author'] ) && $img['author'] ? $img['author'] : '', + 'author_url' => isset( $img['author'] ) && isset( $img['author_url'] ) && $img['author'] && $img['author_url'] ? $img['author_url'] : '', + ) + ); + + // Excerpt. + if ( isset( $args['opts']['show_excerpt'] ) && $args['content'] ) { + $args['excerpt'] = wp_trim_words( $args['content'], $args['opts']['excerpt_words_count'], '...' ); + } + + if ( 'video' === $args['format'] && isset( $img['video_url'] ) && $img['video_url'] ) { + $args['video'] = $img['video_url']; + } + + $args = apply_filters( 'vpf_image_item_args', $args, $img ); + + $items[] = $args; + } + } elseif ( isset( $portfolio_query ) ) { + while ( $portfolio_query->have_posts() ) { + $portfolio_query->the_post(); + + $the_post = get_post(); + + self::$used_posts[] = get_the_ID(); + + // Get category taxonomies for data filter. + $filter_values = array(); + $categories = array(); + $all_taxonomies = get_object_taxonomies( $the_post ); + + foreach ( $all_taxonomies as $cat ) { + // allow only specific taxonomies for filter. + if ( ! self::allow_taxonomies_for_filter( $cat ) ) { + continue; + } + + $category = get_the_terms( $the_post, $cat ); + + if ( $category && ! in_array( $category, $filter_values, true ) ) { + foreach ( $category as $cat_item ) { + // add in filter. + $filter_values[] = $cat_item->slug; + + // add in categories array. + $unique_name = rawurlencode( $cat_item->taxonomy . ':' ) . $cat_item->slug; + $url = self::get_pagenum_link( + array( + 'vp_filter' => $unique_name, + 'vp_page' => 1, + ) + ); + $categories[] = array( + 'slug' => $cat_item->slug, + 'label' => $cat_item->name, + 'description' => $cat_item->description, + 'count' => $cat_item->count, + 'taxonomy' => $cat_item->taxonomy, + 'id' => $cat_item->term_id, + 'parent' => $cat_item->parent, + 'url' => $url, + ); + } + } + } + + $args = array_merge( + $each_item_args, + array( + 'uid' => hash( 'crc32b', 'post-' . get_the_ID() ), + 'post_id' => get_the_ID(), + 'url' => get_permalink(), + 'title' => get_the_title(), + 'content' => get_the_content(), + 'format' => get_post_format() ? get_post_format() : 'standard', + 'published_time' => get_the_date( 'Y-m-d H:i:s', $the_post ), + 'filter' => implode( ',', $filter_values ), + 'image_id' => 'attachment' === get_post_type() ? get_the_ID() : get_post_thumbnail_id( get_the_ID() ), + 'focal_point' => Visual_Portfolio_Custom_Post_Meta::get_featured_image_focal_point( get_the_ID() ), + 'categories' => $categories, + 'comments_count' => get_comments_number( get_the_ID() ), + 'comments_url' => get_comments_link( get_the_ID() ), + 'views_count' => Visual_Portfolio_Custom_Post_Meta::get_views_count( get_the_ID() ), + 'reading_time' => Visual_Portfolio_Custom_Post_Meta::get_reading_time( get_the_ID() ), + ) + ); + + // Author. + $author_id = get_the_author_meta( 'ID' ); + if ( $author_id ) { + $args['author'] = get_the_author(); + $args['author_url'] = get_author_posts_url( $author_id ); + $args['author_avatar'] = get_avatar_url( $author_id, array( 'size' => 50 ) ); + } + + // Excerpt. + if ( isset( $args['opts']['show_excerpt'] ) && $args['opts']['show_excerpt'] ) { + $args['excerpt'] = wp_trim_words( do_shortcode( has_excerpt() ? get_the_excerpt() : $args['content'] ), $args['opts']['excerpt_words_count'], '...' ); + } + + $args['allow_popup'] = isset( $args['image_id'] ) && $args['image_id']; + + if ( 'video' === $args['format'] ) { + $video_url = Visual_Portfolio_Custom_Post_Meta::get_video_format_url( get_the_ID() ); + if ( $video_url ) { + $args['video'] = $video_url; + $args['allow_popup'] = true; + } + } + + $args = apply_filters( 'vpf_post_item_args', $args, $args['post_id'] ); + + $items[] = $args; + } + + $portfolio_query->reset_postdata(); + + // Sometimes, when we use WPBakery Page Builder, without this reset output is wrong. + wp_reset_postdata(); + + // stupid hack as wp_reset_postdata() function is not working in some situations... + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + $GLOBALS['post'] = $old_post; + } + + $notices = array(); + + // No items found notice. + if ( empty( $items ) ) { + $class .= ' vp-portfolio-not-found'; + + // Don't display any output if no items found (works on frontend only). + if ( $options['no_items_notice'] && ( $is_preview || 'notice' === $options['no_items_action'] ) ) { + $notices[] = $options['no_items_notice']; + } + } + + $errors = array(); + + if ( isset( $query_opts['error'] ) && ! empty( $query_opts['error'] ) && ( $is_preview || is_admin_bar_showing() ) ) { + $class .= ' vp-portfolio-errors'; + $errors[] = $query_opts['error']; + } + + $result = array( + 'options' => $options, + 'style_options' => $style_options, + 'class' => $class, + 'data_attrs' => $data_attrs, + 'items_class' => $items_class, + 'items' => $items, + 'notices' => $notices, + 'errors' => $errors, + 'img_size_popup' => $img_size_popup, + 'img_size_md_popup' => $img_size_md_popup, + 'img_size_sm_popup' => $img_size_sm_popup, + 'img_size' => $img_size, + ); + + return $result; + } + + /** + * Print portfolio by post ID or options + * + * @param array $atts options for portfolio list to print. + * + * @return string + */ + public static function get( $atts = array() ) { + $config = self::get_output_config( $atts ); + + if ( ! $config ) { + return ''; + } + + if ( isset( $config['custom_output'] ) ) { + return $config['custom_output']; + } + + $options = $config['options']; + $style_options = $config['style_options']; + $data_attrs = $config['data_attrs']; + $class = $config['class']; + $items = $config['items']; + $items_class = $config['items_class']; + $notices = $config['notices']; + $errors = $config['errors']; + + // Insert styles and scripts. + Visual_Portfolio_Assets::enqueue( $atts ); + + // No items found. + if ( empty( $items ) ) { + if ( empty( $notices ) ) { + return ''; + } + + ob_start(); + + ?> +
+ +
+ include_template( + 'items-list/wrapper-start', + array( + 'options' => $options, + 'style_options' => $style_options, + 'data_attrs' => $data_attrs, + 'class' => $class, + ) + ); + + if ( ! empty( $errors ) ) { + ?> + + +
+ include_template( + 'items-list/items-wrapper-start', + array( + 'options' => $options, + 'style_options' => $style_options, + 'class' => $items_class, + ) + ); + + do_action( 'vpf_after_items_wrapper_start', $options, $style_options ); + + /** + * Each item. + */ + if ( is_array( $items ) && ! empty( $items ) ) { + foreach ( $items as $item_args ) { + self::each_item( $item_args ); + } + } + + /** + * Items wrapper end. + */ + do_action( 'vpf_before_items_wrapper_end', $options, $style_options ); + + visual_portfolio()->include_template( + 'items-list/items-wrapper-end', + array( + 'options' => $options, + 'style_options' => $style_options, + ) + ); + + do_action( 'vpf_after_items_wrapper_end', $options, $style_options ); + + // Slider arrows and bullets. + if ( 'slider' === $options['layout'] ) { + if ( $options['slider_arrows'] ) { + visual_portfolio()->include_template( + 'items-list/layouts/slider/arrows', + array( + 'options' => $options, + 'style_options' => $style_options, + ) + ); + } + if ( $options['slider_bullets'] ) { + visual_portfolio()->include_template( + 'items-list/layouts/slider/bullets', + array( + 'options' => $options, + 'style_options' => $style_options, + ) + ); + } + } + + ?> +
+ include_template( + 'items-list/layouts/slider/thumbnails', + array( + 'options' => $options, + 'style_options' => $style_options, + 'thumbnails' => $slider_thumbnails, + 'img_size' => $config['img_size'], + ) + ); + } + + /** + * Bottom layout elements. + */ + self::print_layout_elements( 'bottom', $options ); + + /** + * Wrapper end. + */ + do_action( 'vpf_before_wrapper_end', $options, $style_options ); + + visual_portfolio()->include_template( + 'items-list/wrapper-end', + array( + 'options' => $options, + 'style_options' => $style_options, + ) + ); + + do_action( 'vpf_after_wrapper_end', $options, $style_options ); + + do_action( 'vpf_after_get_output', $options, $style_options ); + + return ob_get_clean(); + } + + /** + * Print layout elements like Filter, Sort, Search and Pagination. + * + * @param string $position elements position. + * @param array $options options for portfolio list to print. + */ + public static function print_layout_elements( $position, $options ) { + $options = apply_filters( 'vpf_layout_element_options', $options ); + $layout_elements = $options['layout_elements']; + + if ( ! isset( $layout_elements[ $position ] ) || ! isset( $layout_elements[ $position ]['elements'] ) ) { + return; + } + + $registered = Visual_Portfolio_Controls::get_registered_array(); + $control_data = $registered['layout_elements']; + $class_name = 'vp-portfolio__layout-elements'; + + if ( $position ) { + $class_name .= ' vp-portfolio__layout-elements-' . $position; + } + + if ( isset( $layout_elements[ $position ]['align'] ) ) { + $class_name .= ' vp-portfolio__layout-elements-align-' . $layout_elements[ $position ]['align']; + } + + ob_start(); + foreach ( $layout_elements[ $position ]['elements'] as $element ) { + if ( isset( $control_data['options'][ $element ]['render_callback'] ) && is_callable( $control_data['options'][ $element ]['render_callback'] ) ) { + call_user_func( $control_data['options'][ $element ]['render_callback'], $options, $element, $position ); + } + do_action( 'vpf_layout_elements', $options, $element, $position ); + } + $elements_content = ob_get_clean(); + + if ( ! $elements_content ) { + return; + } + + ?> +
+ +
+ $atts['type'], + 'filter_show_count' => 'true' === $atts['show_count'], + 'filter_text_all' => $atts['text_all'] ?? esc_attr__( 'All', 'visual-portfolio' ), + ) + ); + + $align = $atts['align'] ?? 'center'; + + // generate unique ID. + $uid = ++self::$filter_id; + $uid = hash( 'crc32b', $uid . $options['id'] ); + + $class = 'vp-single-filter vp-filter-uid-' . $uid . ' vp-id-' . $options['id']; + + // Add custom class. + if ( isset( $atts['class'] ) ) { + $class .= ' ' . $atts['class']; + } + + ob_start(); + + ?> +
+
+ +
+
+ $atts['type'], + ) + ); + + $align = $atts['align'] ?? 'center'; + + // generate unique ID. + $uid = ++self::$sort_id; + $uid = hash( 'crc32b', $uid . $options['id'] ); + + $class = 'vp-single-sort vp-sort-uid-' . $uid . ' vp-id-' . $options['id']; + + // Add custom class. + if ( isset( $atts['class'] ) ) { + $class .= ' ' . $atts['class']; + } + + ob_start(); + + ?> +
+
+ +
+
+ $img ) { + $options['images'][ $k ]['uid'] = hash( 'crc32b', 'image-' . $k . $img['id'] ); + } + + if ( $count < 0 ) { + $count = 99999; + } + + // Load certain taxonomies. + $images = array(); + + // phpcs:ignore WordPress.Security.NonceVerification + if ( ! $for_filter && ( isset( $_GET['vp_filter'] ) || isset( $query_opts['vp_filter'] ) ) ) { + // phpcs:ignore WordPress.Security.NonceVerification + $category = sanitize_text_field( wp_unslash( $_GET['vp_filter'] ?? $query_opts['vp_filter'] ) ); + + foreach ( $options['images'] as $img ) { + if ( isset( $img['categories'] ) && is_array( $img['categories'] ) ) { + foreach ( $img['categories'] as $cat ) { + if ( self::create_slug( $cat ) === $category ) { + $images[] = $img; + break; + } + } + } + } + } else { + $images = $options['images']; + } + + $images_ids = array(); + foreach ( $images as $k => $img ) { + $images_ids[] = (int) $img['id']; + } + + // Find all used attachments. + $all_attachments = get_posts( + array( + 'post_type' => 'attachment', + 'posts_per_page' => -1, + 'paged' => -1, + 'post__in' => $images_ids, + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + ) + ); + + // prepare titles and descriptions. + foreach ( $images as $k => $img ) { + $img_meta = array( + 'title' => '', + 'description' => '', + 'caption' => '', + 'alt' => '', + 'none' => '', + 'date' => '', + ); + + // Find current attachment post data. + $attachment = false; + foreach ( $all_attachments as $post ) { + if ( $post->ID === (int) $img['id'] ) { + $attachment = $post; + break; + } + } + + if ( $attachment ) { + // get image meta if needed. + if ( 'none' !== $options['images_titles_source'] || 'none' !== $options['images_descriptions_source'] ) { + if ( $attachment && 'attachment' === $attachment->post_type ) { + $img_meta['title'] = $attachment->post_title; + $img_meta['description'] = $attachment->post_content; + $img_meta['caption'] = wp_get_attachment_caption( $attachment->ID ); + $img_meta['alt'] = get_post_meta( $attachment->ID, '_wp_attachment_image_alt', true ); + } + } + + // title. + if ( 'custom' !== $options['images_titles_source'] ) { + $images[ $k ]['title'] = isset( $img_meta[ $options['images_titles_source'] ] ) ? $img_meta[ $options['images_titles_source'] ] : ''; + } + + // description. + if ( 'custom' !== $options['images_descriptions_source'] ) { + $images[ $k ]['description'] = isset( $img_meta[ $options['images_descriptions_source'] ] ) ? $img_meta[ $options['images_descriptions_source'] ] : ''; + } + + // add published date. + $images[ $k ]['published_time'] = get_the_date( 'Y-m-d H:i:s', $attachment ); + } + } + + // order. + $custom_order = false; + $custom_order_direction = $options['images_order_direction']; + + if ( isset( $options['images_order_by'] ) ) { + $custom_order = $options['images_order_by']; + } + + // custom sorting. + // phpcs:ignore WordPress.Security.NonceVerification + if ( isset( $_GET['vp_sort'] ) ) { + // phpcs:ignore WordPress.Security.NonceVerification + $custom_get_order = sanitize_text_field( wp_unslash( $_GET['vp_sort'] ) ); + + switch ( $custom_get_order ) { + case 'title': + case 'date': + $custom_order = $custom_get_order; + $custom_order_direction = 'asc'; + break; + case 'title_desc': + $custom_order = 'title'; + $custom_order_direction = 'desc'; + break; + case 'date_desc': + $custom_order = 'date'; + $custom_order_direction = 'desc'; + break; + } + } + + if ( $custom_order && ! empty( $images ) ) { + switch ( $custom_order ) { + case 'date': + case 'title': + $sort_tmp = array(); + $new_images = array(); + $sort_by = 'date'; + + if ( 'title' === $custom_order ) { + $sort_by = 'title'; + } + + foreach ( $images as &$ma ) { + $sort_tmp[] = &$ma[ $sort_by ]; + } + + array_multisort( $sort_tmp, $images ); + foreach ( $images as &$ma ) { + $new_images[] = $ma; + } + + $images = $new_images; + break; + case 'rand': + // We don't need to randomize order for filter, + // because filter list will be always changed once AJAX loaded. + if ( ! $for_filter ) { + // phpcs:ignore WordPress.WP.AlternativeFunctions.rand_seeding_mt_srand + mt_srand( self::get_rand_seed_session() ); + for ( $i = count( $images ) - 1; $i > 0; $i-- ) { + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.rand_mt_rand + $j = @mt_rand( 0, $i ); + $tmp = $images[ $i ]; + $images[ $i ] = $images[ $j ]; + $images[ $j ] = $tmp; + } + } + + break; + } + if ( 'desc' === $custom_order_direction ) { + $images = array_reverse( $images ); + } + } + + // pages count. + $query_opts['max_num_pages'] = ceil( count( $images ) / $count ); + + $start_from_item = ( $paged - 1 ) * $count; + $end_on_item = $start_from_item + $count; + + if ( $for_filter ) { + $start_from_item = 0; + $end_on_item = 99999; + } + + // get images for current page only. + foreach ( (array) $images as $k => $img ) { + $i = $k + 1; + if ( $i > $start_from_item && $i <= $end_on_item ) { + $query_opts['images'][] = $img; + } + } + } else { + $query_opts = array( + 'posts_per_page' => $count, + 'paged' => $paged, + 'orderby' => 'post_date', + 'order' => 'DESC', + 'post_type' => 'portfolio', + ); + + // Get all available categories for filter. + if ( $for_filter ) { + $query_opts['posts_per_page'] = -1; + $query_opts['paged'] = -1; + } + + // Post based. + if ( 'post-based' === $options['content_source'] ) { + // Exclude IDs. + if ( ! empty( $options['posts_excluded_ids'] ) ) { + $query_opts['post__not_in'] = $options['posts_excluded_ids']; + } + + // Order By. + switch ( $options['posts_order_by'] ) { + case 'title': + $query_opts['orderby'] = 'title'; + break; + + case 'id': + $query_opts['orderby'] = 'ID'; + break; + + case 'post__in': + $query_opts['orderby'] = 'post__in'; + break; + + case 'menu_order': + // We should order by `menu_order` and as fallback order by `post_date`. + $query_opts['orderby'] = array( + 'menu_order' => $options['posts_order_direction'], + 'post_date' => 'desc', + ); + break; + + case 'comment_count': + $query_opts['orderby'] = 'comment_count'; + break; + + case 'modified': + $query_opts['orderby'] = 'modified'; + break; + + case 'rand': + // Update ORDER BY clause to use vpf_random_seed. + $query_opts['orderby'] = 'RAND(' . self::get_rand_seed_session() . ')'; + break; + + default: + $query_opts['orderby'] = 'post_date'; + break; + } + + // Order. + $query_opts['order'] = $options['posts_order_direction']; + + if ( 'ids' === $options['posts_source'] ) { // IDs. + $query_opts['post_type'] = 'any'; + $query_opts['post__not_in'] = array(); + + if ( ! empty( $options['posts_ids'] ) ) { + $query_opts['post__in'] = $options['posts_ids']; + } + } elseif ( 'custom_query' === $options['posts_source'] ) { // Custom Query. + $query_opts['post_type'] = 'any'; + + $tmp_arr = array(); + parse_str( html_entity_decode( $options['posts_custom_query'] ), $tmp_arr ); + $query_opts = array_merge( $query_opts, $tmp_arr ); + } elseif ( 'current_query' === $options['posts_source'] ) { + global $wp_query; + + if ( $wp_query && isset( $wp_query->query_vars ) && is_array( $wp_query->query_vars ) ) { + $query_vars = $wp_query->query_vars; + + // Unset `offset` because if is set, $wp_query overrides/ignores the paged parameter and breaks pagination. + if ( isset( $query_vars['offset'] ) ) { + unset( $query_vars['offset'] ); + } + + // Add post type. + if ( empty( $query_vars['post_type'] ) && is_singular() ) { + $query_vars['post_type'] = get_post_type( get_the_ID() ); + } + + // Add pagination paged value. + if ( $query_opts['paged'] && ( ! isset( $query_vars['paged'] ) || ! $query_vars['paged'] ) ) { + $query_vars['paged'] = $query_opts['paged']; + } + + $query_opts = $query_vars; + } + } else { + $query_opts['post_type'] = $options['posts_source']; + + // Post Types Set. + if ( 'post_types_set' === $options['posts_source'] ) { + $query_opts['post_type'] = (array) $options['post_types_set']; + } + + // Taxonomies. + if ( ! empty( $options['posts_taxonomies'] ) && ! isset( $query_opts['tax_query'] ) ) { + $terms_list = get_terms( + get_object_taxonomies( is_array( $query_opts['post_type'] ) ? $query_opts['post_type'] : array( $query_opts['post_type'] ) ), + array( + 'hide_empty' => false, + ) + ); + + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query + $query_opts['tax_query'] = array( + // We save strings like 'or', 'and' + // but to use it we need these strings in uppercase. + 'relation' => strtoupper( $options['posts_taxonomies_relation'] ), + ); + + // We need this empty array, because when taxonomy selected, + // and posts don't have this taxonomy, we will see all available posts. + // Related topic: https://wordpress.org/support/topic/exclude-certain-category-from-filter/. + if ( 'OR' === $query_opts['tax_query']['relation'] ) { + $query_opts['tax_query'][] = array(); + } + + foreach ( $options['posts_taxonomies'] as $taxonomy ) { + $taxonomy_name = null; + + foreach ( $terms_list as $term ) { + if ( $term->term_id === (int) $taxonomy ) { + $taxonomy_name = $term->taxonomy; + continue; + } + } + + if ( $taxonomy_name ) { + $query_opts['tax_query'][] = array( + 'taxonomy' => $taxonomy_name, + 'field' => 'id', + 'terms' => $taxonomy, + ); + } + } + } + + // Offset. + if ( $options['posts_offset'] ) { + $query_opts['offset'] = $options['posts_offset'] + ( $paged - 1 ) * $count; + } + } + + // Avoid duplicates. + // We should prevent this when using filter, since all current posts will be excluded + // from the filter query and we may not see all filter buttons. + if ( ! $for_filter && $options['posts_avoid_duplicate_posts'] ) { + $not_id = (array) ( isset( $query_opts['post__not_in'] ) ? $query_opts['post__not_in'] : array() ); + $query_opts['post__not_in'] = array_merge( $not_id, self::get_all_used_posts() ); + + // Remove posts from post__in. + if ( isset( $query_opts['post__in'] ) ) { + $query_opts['post__in'] = array_diff( (array) $query_opts['post__in'], (array) $query_opts['post__not_in'] ); + } + } + } + + // Custom sorting. + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( isset( $_GET['vp_sort'] ) ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $custom_get_order = sanitize_text_field( wp_unslash( $_GET['vp_sort'] ) ); + $custom_order = false; + $custom_order_direction = false; + + switch ( $custom_get_order ) { + case 'title': + case 'date': + $custom_order = 'post_' . $custom_get_order; + $custom_order_direction = 'asc'; + break; + case 'title_desc': + $custom_order = 'post_title'; + $custom_order_direction = 'desc'; + break; + case 'date_desc': + $custom_order = 'post_date'; + $custom_order_direction = 'desc'; + break; + } + + if ( $custom_order && $custom_order_direction ) { + $query_opts['orderby'] = $custom_order; + $query_opts['order'] = $custom_order_direction; + } + } + + // Load certain taxonomies using custom filter. + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( ! $for_filter && ( isset( $_GET['vp_filter'] ) || isset( $query_opts['vp_filter'] ) ) ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $taxonomies = sanitize_text_field( wp_unslash( $_GET['vp_filter'] ?? $query_opts['vp_filter'] ) ); + $taxonomies = explode( ':', $taxonomies ); + + if ( $taxonomies && isset( $taxonomies[0] ) && isset( $taxonomies[1] ) ) { + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query + $query_opts['tax_query'] = array( + 'relation' => 'AND', + array( + 'taxonomy' => $taxonomies[0], + 'field' => 'slug', + 'terms' => $taxonomies[1], + ), + isset( $query_opts['tax_query'] ) ? $query_opts['tax_query'] : '', + ); + } + } + } + + $query_opts = apply_filters( 'vpf_extend_query_args', $query_opts, $options, $layout_id ); + + return $query_opts; + } + + /** + * Print notice + * + * @param string $notice notice string. + */ + public static function notice( $notice ) { + if ( ! $notice ) { + return; + } + visual_portfolio()->include_template( + 'notices/notices', + array( + 'notice' => $notice, + ) + ); + } + + /** + * Print error + * + * @param string $error error string. + */ + public static function error( $error ) { + if ( ! $error ) { + return; + } + visual_portfolio()->include_template( + 'errors/errors', + array( + 'error' => $error, + ) + ); + } + + /** + * Print filters + * + * @param array $vp_options current vp_list options. + */ + public static function filter( $vp_options ) { + if ( ! $vp_options['filter'] ) { + return; + } + + $is_images = 'images' === $vp_options['content_source']; + $is_social = 'social-stream' === $vp_options['content_source']; + $query_opts = self::get_query_params( $vp_options, true ); + + // Get active item. + $active_item = self::get_filter_active_item( $query_opts ); + + if ( $is_images || $is_social ) { + $term_items = self::get_images_terms( $query_opts, $active_item ); + } else { + $portfolio_query = new WP_Query( $query_opts ); + $term_items = self::get_posts_terms( $portfolio_query, $active_item ); + } + + // Add 'All' active item. + if ( ! empty( $term_items['terms'] ) && $vp_options['filter_text_all'] ) { + array_unshift( + $term_items['terms'], + array( + 'filter' => '*', + 'label' => $vp_options['filter_text_all'], + 'description' => false, + 'count' => false, + 'id' => 0, + 'parent' => 0, + 'active' => ! $term_items['there_is_active'], + 'url' => self::get_pagenum_link( + array( + 'vp_filter' => '', + 'vp_page' => 1, + ) + ), + 'class' => 'vp-filter__item' . ( ! $term_items['there_is_active'] ? ' vp-filter__item-active' : '' ), + ) + ); + } + + // No filters available. + if ( empty( $term_items['terms'] ) ) { + return; + } + + // get options for the current filter. + $filter_options = array(); + $filter_options_slug = 'filter_' . $vp_options['filter'] . '__'; + + foreach ( $vp_options as $k => $opt ) { + // add option to array. + if ( substr( $k, 0, strlen( $filter_options_slug ) ) === $filter_options_slug ) { + $opt_name = str_replace( $filter_options_slug, '', $k ); + $filter_options[ $opt_name ] = $opt; + } + + // remove style options from the options list. + if ( substr( $k, 0, strlen( $filter_options_slug ) ) === $filter_options_slug ) { + unset( $vp_options[ $k ] ); + } + } + + $args = array( + 'class' => 'vp-filter', + // phpcs:ignore Squiz.PHP.CommentedOutCode.Found, Squiz.Commenting.BlockComment.NoEmptyLineBefore + /* + * Example: + array( + array( + 'filter' => '*', + 'label' => $options['filter_text_all'], + 'description' => false, + 'count' => false, + 'active' => true, + 'url' => Visual_Portfolio_Get::get_pagenum_link( + array( + 'vp_filter' => '', + 'vp_page' => 1, + ) + ), + 'class' => 'vp-filter__item', + ), + ) + */ + 'items' => apply_filters( 'vpf_extend_filter_items', $term_items['terms'], $vp_options ), + 'show_count' => $vp_options['filter_show_count'], + 'opts' => $filter_options, + 'vp_opts' => $vp_options, + ); + + ?> +
+ include_template( 'items-list/filter' . $filter_style_pref . '/filter', $args ); + + // We need to include these styles, since users can insert filters using separate shortcode. + Visual_Portfolio_Assets::store_used_assets( + 'visual-portfolio-filter-' . $vp_options['filter'], + 'items-list/filter' . $filter_style_pref . '/style', + 'template_style' + ); + + ?> +
+ have_posts() ) { + $portfolio_query->the_post(); + $all_taxonomies = get_object_taxonomies( get_post() ); + + foreach ( $all_taxonomies as $cat ) { + // allow only specific taxonomies for filter. + if ( ! self::allow_taxonomies_for_filter( $cat ) ) { + continue; + } + + // Retrieve terms. + $category = get_the_terms( get_post(), $cat ); + if ( ! $category ) { + continue; + } + + // Prepare each terms array. + foreach ( $category as $cat_item ) { + if ( ! in_array( $cat_item->term_id, $term_ids, true ) ) { + $term_ids[] = $cat_item->term_id; + } + if ( ! in_array( $cat_item->taxonomy, $term_taxonomies, true ) ) { + $term_taxonomies[] = $cat_item->taxonomy; + } + } + } + } + + $portfolio_query->reset_postdata(); + + // Sometimes, when we use WPBakery Page Builder, without this reset output is wrong. + wp_reset_postdata(); + + // stupid hack as wp_reset_postdata() function is not working in some situations... + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + $GLOBALS['post'] = $old_post; + + // Get all available terms and then pick only needed by ID + // we need this to support reordering plugins. + $all_terms = get_terms( + array( + 'taxonomy' => $term_taxonomies, + 'hide_empty' => true, + ) + ); + + if ( isset( $all_terms ) && is_array( $all_terms ) ) { + foreach ( $all_terms as $term ) { + if ( in_array( $term->term_id, $term_ids, true ) ) { + $unique_name = rawurlencode( $term->taxonomy . ':' ) . $term->slug; + + $url = self::get_pagenum_link( + array( + 'vp_filter' => $unique_name, + 'vp_page' => 1, + ) + ); + + $is_active = rawurldecode( $unique_name ) === $active_item; + + $terms[ $unique_name ] = array( + 'filter' => $term->slug, + 'label' => $term->name, + 'description' => $term->description, + 'count' => $term->count, + 'taxonomy' => $term->taxonomy, + 'id' => $term->term_id, + 'parent' => $term->parent, + 'active' => $is_active, + 'url' => $url, + 'class' => 'vp-filter__item' . ( $is_active ? ' vp-filter__item-active' : '' ), + ); + + if ( $is_active ) { + $there_is_active = true; + } + } + } + } + + return array( + 'terms' => $terms, + 'there_is_active' => $there_is_active, + ); + } + + /** + * Get images terms. + * + * @param array $query_opts - Query array params. + * @param boolean $active_item - Active filter item. + * @return array + */ + public static function get_images_terms( $query_opts, $active_item ) { + $terms = array(); + $there_is_active = false; + // calculate categories count. + $categories_count = array(); + foreach ( $query_opts['images'] as $img ) { + if ( isset( $img['categories'] ) && is_array( $img['categories'] ) ) { + foreach ( $img['categories'] as $cat ) { + $categories_count[ $cat ] = ( isset( $categories_count[ $cat ] ) ? $categories_count[ $cat ] : 0 ) + 1; + } + } + } + + foreach ( $query_opts['images'] as $img ) { + if ( isset( $img['categories'] ) && is_array( $img['categories'] ) ) { + foreach ( $img['categories'] as $cat ) { + $slug = self::create_slug( $cat ); + $url = self::get_pagenum_link( + array( + 'vp_filter' => rawurlencode( $slug ), + 'vp_page' => 1, + ) + ); + + // add in terms array. + $terms[ $slug ] = array( + 'filter' => $slug, + 'label' => $cat, + 'description' => '', + 'count' => isset( $categories_count[ $cat ] ) && $categories_count[ $cat ] ? $categories_count[ $cat ] : '', + 'taxonomy' => 'category', + 'id' => 0, + 'parent' => 0, + 'active' => $active_item === $slug, + 'url' => $url, + 'class' => 'vp-filter__item' . ( $active_item === $slug ? ' vp-filter__item-active' : '' ), + ); + + if ( $active_item === $slug ) { + $there_is_active = true; + } + } + } + } + + return array( + 'terms' => $terms, + 'there_is_active' => $there_is_active, + ); + } + + /** + * Get filter active item. + * + * @param array $query_opts - Query array params. + * @return boolean + */ + public static function get_filter_active_item( $query_opts ) { + // Get active item. + $active_item = false; + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( ( isset( $_GET['vp_filter'] ) || isset( $query_opts['vp_filter'] ) ) ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $active_item = sanitize_text_field( wp_unslash( $_GET['vp_filter'] ?? $query_opts['vp_filter'] ) ); + } + + return $active_item; + } + + /** + * Print sort + * + * @param array $vp_options current vp_list options. + */ + public static function sort( $vp_options ) { + if ( ! $vp_options['sort'] ) { + return; + } + + $terms = array(); + + // Get active item. + $active_item = false; + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( isset( $_GET['vp_sort'] ) ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $active_item = sanitize_text_field( wp_unslash( $_GET['vp_sort'] ) ); + } + + $sort_items = apply_filters( + 'vpf_extend_sort_items', + array( + '' => esc_html__( 'Default sorting', 'visual-portfolio' ), + 'date_desc' => esc_html__( 'Sort by date (newest)', 'visual-portfolio' ), + 'date' => esc_html__( 'Sort by date (oldest)', 'visual-portfolio' ), + 'title' => esc_html__( 'Sort by title (A-Z)', 'visual-portfolio' ), + 'title_desc' => esc_html__( 'Sort by title (Z-A)', 'visual-portfolio' ), + ), + $vp_options + ); + + foreach ( $sort_items as $slug => $label ) { + $url = apply_filters( + 'vpf_extend_sort_item_url', + self::get_pagenum_link( + array( + 'vp_sort' => rawurlencode( $slug ), + 'vp_page' => 1, + ) + ), + $slug, + $vp_options + ); + + $is_active = ! $active_item && ! $slug ? true : $active_item === $slug; + + // add in terms array. + $terms[ $slug ] = array( + 'sort' => $slug, + 'label' => $label, + 'description' => '', + 'active' => $is_active, + 'url' => $url, + 'class' => 'vp-sort__item' . ( $is_active ? ' vp-sort__item-active' : '' ), + ); + } + + // get options for the current sort. + $sort_options = array(); + $sort_options_slug = 'sort_' . $vp_options['sort'] . '__'; + + foreach ( $vp_options as $k => $opt ) { + // add option to array. + if ( substr( $k, 0, strlen( $sort_options_slug ) ) === $sort_options_slug ) { + $opt_name = str_replace( $sort_options_slug, '', $k ); + $sort_options[ $opt_name ] = $opt; + } + + // remove style options from the options list. + if ( substr( $k, 0, strlen( $sort_options_slug ) ) === $sort_options_slug ) { + unset( $vp_options[ $k ] ); + } + } + + $args = array( + 'class' => 'vp-sort', + 'items' => $terms, + 'opts' => $sort_options, + 'vp_opts' => $vp_options, + ); + + ?> +
+ include_template( 'items-list/sort' . $sort_style_pref . '/sort', $args ); + + // We need to include these styles, since users can insert sort using separate shortcode. + Visual_Portfolio_Assets::store_used_assets( + 'visual-portfolio-sort-' . $vp_options['sort'], + 'items-list/sort' . $sort_style_pref . '/style', + 'template_style' + ); + + ?> +
+ ' ) ) { + $args['opts']['read_more_url'] = $args['url'] . '#more-' . $args['post_id']; + } else { + $args['opts']['show_read_more'] = false; + } + } else { + $args['opts']['read_more_url'] = $args['url']; + } + } + + // Click action. + $args['url_target'] = false; + $args['url_rel'] = false; + + switch ( $args['vp_opts']['items_click_action'] ) { + case 'popup_gallery': + break; + case false: + $args['url'] = false; + break; + default: + $args['url_target'] = $args['vp_opts']['items_click_action_url_target'] ? $args['vp_opts']['items_click_action_url_target'] : false; + $args['url_rel'] = $args['vp_opts']['items_click_action_url_rel'] ? $args['vp_opts']['items_click_action_url_rel'] : false; + break; + } + + // No Image. + if ( ! $args['image'] && $args['no_image'] ) { + $args['image'] = Visual_Portfolio_Images::get_attachment_image( $args['no_image'], $args['img_size'], false, '' ); + } + + // Class. + $args['class'] = 'vp-portfolio__item-wrap'; + if ( $is_posts ) { + // post_class functionality. + $args['class'] = join( ' ', get_post_class( $args['class'], $args['post_id'] ) ); + } + + if ( ! $is_posts && is_array( $args['categories'] ) && ! empty( $args['categories'] ) ) { + foreach ( $args['categories'] as $category ) { + $args['class'] .= ' category-' . $category['slug']; + } + } + + if ( $args['uid'] ) { + $args['class'] .= ' vp-portfolio__item-uid-' . esc_attr( $args['uid'] ); + } + + $args = apply_filters( 'vpf_each_item_args', $args ); + + // Tag Name. + $tag_name = $is_posts ? 'article' : 'div'; + $tag_name = apply_filters( 'vpf_each_item_tag_name', $tag_name, $args ); + + $attrs = array( + 'class' => $args['class'], + 'data-vp-filter' => $args['filter'], + ); + + if ( $args['focal_point'] && ! empty( $args['focal_point'] ) ) { + $attrs['style'] = '--vp-images__object-position: ' . esc_attr( 100 * floatval( $args['focal_point']['x'] ) ) . '% ' . esc_attr( 100 * floatval( $args['focal_point']['y'] ) ) . '%;'; + } + + $attrs = apply_filters( 'vpf_each_item_tag_attrs', $attrs, $args ); + ?> + + < + $val ) { + echo esc_attr( $name ) . '="' . esc_attr( $val ) . '" '; + } + ?> + > + + +
+ include_template( 'items-list/items-style' . $items_style_pref . '/image', $args ); + visual_portfolio()->include_template( 'items-list/items-style' . $items_style_pref . '/meta', $args ); + + do_action( 'vpf_each_item_end', $args ); + ?> +
+ + > + post_type ) { + $img_meta = wp_get_attachment_image_src( $args['image_id'], $args['img_size_popup'] ); + $img_md_meta = wp_get_attachment_image_src( $args['image_id'], $args['img_size_md_popup'] ); + $img_sm_meta = wp_get_attachment_image_src( $args['image_id'], $args['img_size_sm_popup'] ); + + $popup_image = apply_filters( + 'vpf_popup_image_data', + array( + 'id' => $args['image_id'], + 'title' => $attachment->post_title, + 'description' => $attachment->post_content, + 'caption' => wp_get_attachment_caption( $attachment->ID ), + 'alt' => get_post_meta( $attachment->ID, '_wp_attachment_image_alt', true ), + 'url' => $img_meta[0], + 'srcset' => wp_get_attachment_image_srcset( $args['image_id'], $args['img_size_popup'] ), + 'width' => $img_meta[1], + 'height' => $img_meta[2], + 'md_url' => $img_md_meta[0], + 'md_width' => $img_md_meta[1], + 'md_height' => $img_md_meta[2], + 'sm_url' => $img_sm_meta[0], + 'sm_width' => $img_sm_meta[1], + 'sm_height' => $img_sm_meta[2], + 'item_title' => $args['title'], + 'item_description' => $args['content'], + 'item_author' => $args['author'], + 'item_author_url' => $args['author_url'], + ) + ); + } elseif ( $args['image_id'] ) { + $popup_image = apply_filters( 'vpf_popup_custom_image_data', false, $args['image_id'] ); + + // Check items title and description availability. + if ( $popup_image && ! isset( $popup_image['item_title'] ) ) { + $popup_image['item_title'] = $popup_image['title'] ?? ''; + } + if ( $popup_image && ! isset( $popup_image['item_description'] ) ) { + $popup_image['item_description'] = $popup_image['description'] ?? ''; + } + if ( $popup_image && ! isset( $popup_image['item_author'] ) ) { + $popup_image['item_author'] = $popup_image['author'] ?? ''; + } + } + } + return $popup_image; + } + + /** + * Get item popup video data. + * + * @param array $args - item args. + * + * @return array + */ + public static function get_popup_video( $args ) { + return array( + 'url' => $args['format_video_url'], + 'poster' => wp_get_attachment_image_url( $args['image_id'], 'full' ), + 'item_title' => $args['title'] ?? null, + 'item_description' => $args['content'] ?? null, + 'item_author' => $args['author'] ?? null, + 'item_author_url' => $args['author_url'] ?? null, + ); + } + + /** + * Print image popup. + * + * @param array $image_data - item popup image data. + * @param array $args - item args. + * + * @return string|bool + */ + public static function popup_image_output( $image_data, $args ) { + $popup_output = false; + + if ( $image_data ) { + ob_start(); + + $title_source = $args['vp_opts']['items_click_action_popup_title_source'] ? $args['vp_opts']['items_click_action_popup_title_source'] : ''; + $description_source = $args['vp_opts']['items_click_action_popup_description_source'] ? $args['vp_opts']['items_click_action_popup_description_source'] : ''; + + visual_portfolio()->include_template( + 'popup/image-popup-data', + array( + 'title_source' => $title_source, + 'description_source' => $description_source, + 'image_data' => $image_data, + 'args' => $args, + 'opts' => $args['vp_opts'], + ) + ); + + $popup_output = ob_get_clean(); + } + + return $popup_output; + } + + /** + * Print video popup. + * + * @param array $video_data - item popup video data. + * @param array $args - item args. + * + * @return string|bool + */ + public static function popup_video_output( $video_data, $args ) { + $popup_output = false; + + if ( $video_data ) { + ob_start(); + + $title_source = $args['vp_opts']['items_click_action_popup_title_source'] ? $args['vp_opts']['items_click_action_popup_title_source'] : ''; + $description_source = $args['vp_opts']['items_click_action_popup_description_source'] ? $args['vp_opts']['items_click_action_popup_description_source'] : ''; + + visual_portfolio()->include_template( + 'popup/video-popup-data', + array( + 'title_source' => $title_source, + 'description_source' => $description_source, + 'video_data' => $video_data, + + // We need to check existence of args to prevent possible conflicts. + 'args' => isset( $args ) ? $args : null, + 'opts' => isset( $args['vp_opts'] ) ? $args['vp_opts'] : null, + ) + ); + + $popup_output = ob_get_clean(); + } + + return $popup_output; + } + + /** + * Print pagination + * + * @param array $vp_options - current vp_list options. + */ + public static function pagination( $vp_options ) { + if ( ! $vp_options['pagination_style'] || ! $vp_options['pagination'] ) { + return; + } + + // get options for the current pagination. + $pagination_options = array(); + $pagination_options_slug = 'pagination_' . $vp_options['pagination_style'] . '__'; + foreach ( $vp_options as $k => $opt ) { + // add option to array. + if ( substr( $k, 0, strlen( $pagination_options_slug ) ) === $pagination_options_slug ) { + $opt_name = str_replace( $pagination_options_slug, '', $k ); + $pagination_options[ $opt_name ] = $opt; + } + + // remove style options from the options list. + if ( substr( $k, 0, strlen( $pagination_options_slug ) ) === $pagination_options_slug ) { + unset( $vp_options[ $k ] ); + } + } + + $args = array( + 'type' => $vp_options['pagination'], + 'next_page_url' => $vp_options['next_page_url'], + 'start_page' => $vp_options['start_page'], + 'max_pages' => $vp_options['max_pages'], + 'class' => 'vp-pagination', + 'opts' => $pagination_options, + 'vp_opts' => $vp_options, + ); + + $args = apply_filters( 'vpf_pagination_args', $args, $vp_options ); + + // No more posts. + if ( ! $args['next_page_url'] ) { + $args['class'] .= ' vp-pagination__no-more'; + } + + ?> +
+ include_template( 'items-list/pagination' . $pagination_style_pref . '/' . $vp_options['pagination'], $args ); + } + + break; + default: + // Scroll to top. + if ( $vp_options['pagination_paged__scroll_top'] ) { + $args['class'] .= ' vp-pagination__scroll-top'; + + if ( isset( $vp_options['pagination_paged__scroll_top_offset'] ) ) { + $args['scroll_top_offset'] = $vp_options['pagination_paged__scroll_top_offset']; + } + } + + // parse html string and make arrays. + $filtered_links = self::get_pagination_links( $args, $vp_options ); + + if ( ! empty( $filtered_links ) ) { + $args['items'] = $filtered_links; + visual_portfolio()->include_template( 'items-list/pagination' . $pagination_style_pref . '/paged', $args ); + } + + break; + } + + ?> +
+ esc_url_raw( + str_replace( + 999999999, + '%#%', + remove_query_arg( + 'add-to-cart', + self::get_pagenum_link( + array( + 'vp_page' => 999999999, + ) + ) + ) + ) + ), + 'format' => '', + 'type' => 'array', + 'current' => $args['start_page'], + 'total' => $args['max_pages'], + 'prev_text' => '<', + 'next_text' => '>', + 'end_size' => 1, + 'mid_size' => 2, + ) + ); + + // parse html string and make arrays. + $filtered_links = array(); + if ( $pagination_links ) { + foreach ( $pagination_links as $link ) { + $tag_data = self::extract_tags( $link, array( 'a', 'span' ) ); + $tag_data = ! empty( $tag_data ) ? $tag_data[0] : $tag_data; + + if ( ! empty( $tag_data ) ) { + $atts = isset( $tag_data['attributes'] ) ? $tag_data['attributes'] : false; + $href = $atts && isset( $atts['href'] ) ? $atts['href'] : false; + $class = $atts && isset( $atts['class'] ) ? $atts['class'] : ''; + $label = isset( $tag_data['contents'] ) ? $tag_data['contents'] : false; + + $arr = array( + 'url' => $href, + 'label' => $label, + 'class' => 'vp-pagination__item', + 'active' => strpos( $class, 'current' ) !== false, + 'is_prev_arrow' => strpos( $class, 'prev' ) !== false, + 'is_next_arrow' => strpos( $class, 'next' ) !== false, + 'is_dots' => strpos( $class, 'dots' ) !== false, + ); + + if ( $arr['active'] ) { + $arr['class'] .= ' vp-pagination__item-active'; + } + if ( $arr['is_prev_arrow'] ) { + $arr['class'] .= ' vp-pagination__item-prev'; + } + if ( $arr['is_next_arrow'] ) { + $arr['class'] .= ' vp-pagination__item-next'; + } + if ( $arr['is_dots'] ) { + $arr['class'] .= ' vp-pagination__item-dots'; + } + + // skip arrows if disabled. + if ( ! $vp_options['pagination_paged__show_arrows'] && ( $arr['is_prev_arrow'] || $arr['is_next_arrow'] ) ) { + continue; + } + + // skip numbers if disabled. + if ( ! $vp_options['pagination_paged__show_numbers'] && ! $arr['is_prev_arrow'] && ! $arr['is_next_arrow'] ) { + continue; + } + + $filtered_links[] = apply_filters( 'vpf_pagination_item_data', $arr, $args, $vp_options ); + } + } + } + + return $filtered_links; + } + + /** + * Return current page url with paged support. + * + * @param array $query_arg - custom query arg. + * @return string + */ + public static function get_pagenum_link( $query_arg = array() ) { + // Use current page url. + global $wp; + $current_url = ''; + + // We should use REQUEST_URI in case if the user used non-default permalinks. + // For example, this one: + // - /index.php/%postname% . + if ( isset( $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'] ) ) { + $current_url = esc_url_raw( wp_unslash( ( is_ssl() ? 'https' : 'http' ) . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ) ); + } else { + $current_url = trailingslashit( home_url( $wp->request ) ); + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( ! empty( $_GET ) ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $current_url = add_query_arg( array_map( 'sanitize_text_field', wp_unslash( $_GET ) ), $current_url ); + } + } + + if ( isset( $query_arg['vp_filter'] ) && ! $query_arg['vp_filter'] ) { + unset( $query_arg['vp_filter'] ); + $current_url = remove_query_arg( 'vp_filter', $current_url ); + } + if ( isset( $query_arg['vp_sort'] ) && ! $query_arg['vp_sort'] ) { + unset( $query_arg['vp_sort'] ); + $current_url = remove_query_arg( 'vp_sort', $current_url ); + } + if ( isset( $query_arg['vp_page'] ) && 1 === $query_arg['vp_page'] ) { + unset( $query_arg['vp_page'] ); + $current_url = remove_query_arg( 'vp_page', $current_url ); + } + + // Add custom query args. + $current_url = add_query_arg( $query_arg, $current_url ); + + $current_url = apply_filters( 'vpf_get_pagenum_link', $current_url, $query_arg ); + + return $current_url; + } + + /** + * Create slug from user input string. + * + * @param string $str - user string. + * @param string $delimiter - words delimiter. + * + * @return string + */ + public static function create_slug( $str, $delimiter = '_' ) { + $slug = $str; + + if ( class_exists( 'Cocur\Slugify\Slugify' ) ) { + $slugify = new Cocur\Slugify\Slugify(); + $slug = $slugify->slugify( $str, $delimiter ); + + // Sometimes Slugify can't add slug to string with unicode. + // We will return the standard string. + if ( ! $slug ) { + $slug = $str; + } + } + + return $slug; + } + + /** + * Get list with all used posts on the current page. + * + * @return array + */ + public static function get_all_used_posts() { + // add post IDs from main query. + if ( self::$check_main_query && ! self::is_preview() ) { + self::$check_main_query = false; + + global $wp_query; + + if ( isset( $wp_query ) && isset( $wp_query->posts ) ) { + foreach ( $wp_query->posts as $post ) { + self::$used_posts[] = $post->ID; + } + } + } + + return self::$used_posts; + } + + /** + * Get list with all used portfolios on the current page. + * + * @return array + */ + public static function get_all_used_layouts() { + return self::$used_layouts; + } + + /** + * Extract specific HTML tags and their attributes from a string. + * + * Found in http://w-shadow.com/blog/2009/10/20/how-to-extract-html-tags-and-their-attributes-with-php/ + * + * You can either specify one tag, an array of tag names, or a regular expression that matches the tag name(s). + * If multiple tags are specified you must also set the $selfclosing parameter and it must be the same for + * all specified tags (so you can't extract both normal and self-closing tags in one go). + * + * The function returns a numerically indexed array of extracted tags. Each entry is an associative array + * with these keys : + * tag_name - the name of the extracted tag, e.g. "a" or "img". + * offset - the numberic offset of the first character of the tag within the HTML source. + * contents - the inner HTML of the tag. This is always empty for self-closing tags. + * attributes - a name -> value array of the tag's attributes, or an empty array if the tag has none. + * full_tag - the entire matched tag, e.g. 'example.com'. This key + * will only be present if you set $return_the_entire_tag to true. + * + * @param string $html The HTML code to search for tags. + * @param string|array $tag The tag(s) to extract. + * @param bool $selfclosing Whether the tag is self-closing or not. Setting it to null will force the script to try and make an educated guess. + * @param bool $return_the_entire_tag Return the entire matched tag in 'full_tag' key of the results array. + * @param string $charset The character set of the HTML code. Defaults to ISO-8859-1. + * + * @return array An array of extracted tags, or an empty array if no matching tags were found. + */ + private static function extract_tags( $html, $tag, $selfclosing = null, $return_the_entire_tag = false, $charset = 'ISO-8859-1' ) { + + if ( is_array( $tag ) ) { + $tag = implode( '|', $tag ); + } + + // If the user didn't specify if $tag is a self-closing tag we try to auto-detect it by checking against a list of known self-closing tags. + $selfclosing_tags = array( 'area', 'base', 'basefont', 'br', 'hr', 'input', 'img', 'link', 'meta', 'col', 'param' ); + if ( is_null( $selfclosing ) ) { + $selfclosing = in_array( $tag, $selfclosing_tags, true ); + } + + // The regexp is different for normal and self-closing tags because I can't figure out how to make a sufficiently robust unified one. + if ( $selfclosing ) { + $tag_pattern = + '@<(?P' . $tag . ') # \s[^>]+)? # attributes, if any + \s*/?> # /> or just >, being lenient here + @xsi'; + } else { + $tag_pattern = + '@<(?P' . $tag . ') # \s[^>]+)? # attributes, if any + \s*> # > + (?P.*?) # tag contents + # the closing + @xsi'; + } + + $attribute_pattern = + '@ + (?P\w+) # attribute name + \s*=\s* + ( + (?P[\"\'])(?P.*?)(?P=quote) # a quoted value + | # or + (?P[^\s"\']+?)(?:\s+|$) # an unquoted value (terminated by whitespace or EOF) + ) + @xsi'; + + // Find all tags. + if ( ! preg_match_all( $tag_pattern, $html, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE ) ) { + // Return an empty array if we didn't find anything. + return array(); + } + + $tags = array(); + foreach ( $matches as $match ) { + + // Parse tag attributes, if any. + $attributes = array(); + if ( ! empty( $match['attributes'][0] ) ) { + + if ( preg_match_all( $attribute_pattern, $match['attributes'][0], $attribute_data, PREG_SET_ORDER ) ) { + // Turn the attribute data into a name->value array. + foreach ( $attribute_data as $attr ) { + if ( ! empty( $attr['value_quoted'] ) ) { + $value = $attr['value_quoted']; + } elseif ( ! empty( $attr['value_unquoted'] ) ) { + $value = $attr['value_unquoted']; + } else { + $value = ''; + } + + // Passing the value through html_entity_decode is handy when you want to extract link URLs or something like that. You might want to remove or modify this call if it doesn't fit your situation. + $value = html_entity_decode( $value, ENT_QUOTES, $charset ); + + $attributes[ $attr['name'] ] = $value; + } + } + } + + $tag = array( + 'tag_name' => $match['tag'][0], + 'offset' => $match[0][1], + 'contents' => ! empty( $match['contents'] ) ? $match['contents'][0] : '', // empty for self-closing tags. + 'attributes' => $attributes, + ); + if ( $return_the_entire_tag ) { + $tag['full_tag'] = $match[0][0]; + } + + $tags[] = $tag; + } + + return $tags; + } +} + +new Visual_Portfolio_Get(); diff --git a/classes/class-gutenberg-saved.php b/classes/class-gutenberg-saved.php new file mode 100644 index 00000000..66e1bdbf --- /dev/null +++ b/classes/class-gutenberg-saved.php @@ -0,0 +1,126 @@ + array( + 'type' => 'string', + ), + 'align' => array( + 'type' => 'string', + ), + 'className' => array( + 'type' => 'string', + ), + 'anchor' => array( + 'type' => 'string', + ), + ); + + register_block_type( + visual_portfolio()->plugin_path . 'gutenberg/block-saved', + array( + 'render_callback' => array( $this, 'block_render' ), + 'attributes' => $attributes, + ) + ); + + // Fallback. + register_block_type( + 'nk/visual-portfolio', + array( + 'render_callback' => array( $this, 'block_render' ), + 'attributes' => $attributes, + ) + ); + } + + /** + * Block output + * + * @param array $attributes - block attributes. + * + * @return string + */ + public function block_render( $attributes ) { + ob_start(); + + $attributes = array_merge( + array( + 'id' => '', + 'align' => '', + 'className' => '', + ), + $attributes + ); + + if ( ! $attributes['id'] ) { + return ''; + } + + // WPML support. + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound + $attributes['id'] = apply_filters( 'wpml_object_id', $attributes['id'], 'vp_lists', true ); + + $class_name = 'wp-block-visual-portfolio'; + + if ( $attributes['align'] ) { + $class_name .= ' align' . $attributes['align']; + } + + if ( $attributes['className'] ) { + $class_name .= ' ' . $attributes['className']; + } + ?> +
+ > + $attributes['id'] ) ); + ?> +
+ cached_attributes ) ) { + return $this->cached_attributes; + } + + // Default attributes. + $attributes = array( + 'block_id' => array( + 'type' => 'string', + ), + 'align' => array( + 'type' => 'string', + ), + 'className' => array( + 'type' => 'string', + ), + 'anchor' => array( + 'type' => 'string', + ), + ); + + // Add dynamic attributes from registered controls. + $controls = Visual_Portfolio_Controls::get_registered_array(); + + foreach ( $controls as $control ) { + if ( isset( $attributes[ $control['name'] ] ) ) { + continue; + } + + if ( + 'html' === $control['type'] || + 'notice' === $control['type'] || + 'pro_note' === $control['type'] || + 'category_tabs' === $control['type'] || + 'category_toggle_group' === $control['type'] || + 'category_collapse' === $control['type'] || + 'category_navigator' === $control['type'] + ) { + continue; + } + + $attribute_data = apply_filters( + 'vpf_register_block_attribute_data', + array( + 'type' => 'string', + ), + $control + ); + + if ( ! $attribute_data ) { + continue; + } + + $attributes[ $control['name'] ] = $attribute_data; + + switch ( $control['type'] ) { + case 'checkbox': + case 'toggle': + $attributes[ $control['name'] ]['type'] = 'boolean'; + break; + case 'number': + case 'range': + $attributes[ $control['name'] ]['type'] = 'number'; + break; + case 'select': + case 'select2': + if ( $control['multiple'] ) { + $attributes[ $control['name'] ]['type'] = 'array'; + $attributes[ $control['name'] ]['items'] = array( + 'type' => 'string', + ); + } + break; + case 'sortable': + $attributes[ $control['name'] ]['type'] = 'array'; + $attributes[ $control['name'] ]['items'] = array( + 'type' => 'string', + ); + break; + case 'gallery': + $attributes[ $control['name'] ]['type'] = 'array'; + $attributes[ $control['name'] ]['items'] = array( + 'type' => 'object', + ); + break; + case 'elements_selector': + $attributes[ $control['name'] ]['type'] = 'object'; + $attributes[ $control['name'] ]['items'] = array( + 'type' => 'object', + ); + break; + } + + if ( isset( $control['default'] ) ) { + $attributes[ $control['name'] ]['default'] = $control['default']; + } + } + + $attributes = apply_filters( + 'vpf_register_block_attributes', + $attributes, + $controls + ); + + $this->cached_attributes = $attributes; + + return $this->cached_attributes; + } + + /** + * Register Block. + */ + public function register_block() { + if ( ! function_exists( 'register_block_type' ) ) { + return; + } + + $attributes = $this->get_block_attributes(); + + register_block_type( + visual_portfolio()->plugin_path . 'gutenberg/block', + array( + 'render_callback' => array( $this, 'block_render' ), + 'attributes' => $attributes, + ) + ); + } + + /** + * Block output + * + * @param array $attributes - block attributes. + * + * @return string + */ + public function block_render( $attributes ) { + $attributes = array_merge( + array( + 'align' => '', + 'className' => '', + ), + $attributes + ); + + $class_name = 'wp-block-visual-portfolio'; + + if ( $attributes['align'] ) { + $class_name .= ' align' . $attributes['align']; + } + + if ( $attributes['className'] ) { + $class_name .= ' ' . $attributes['className']; + } + + $result = '
'; + + $result .= Visual_Portfolio_Get::get( $attributes ); + + $result .= '
'; + + return $result; + } + + /** + * Enqueue script for Gutenberg editor + */ + public function enqueue_block_editor_assets() { + $attributes = $this->get_block_attributes(); + + // Block. + Visual_Portfolio_Assets::enqueue_script( + 'visual-portfolio-gutenberg', + 'build/gutenberg/index', + array( 'wp-i18n', 'wp-element', 'wp-components', 'jquery', 'lodash' ) + ); + Visual_Portfolio_Assets::enqueue_style( + 'visual-portfolio-gutenberg', + 'build/gutenberg/style' + ); + wp_style_add_data( 'visual-portfolio-gutenberg', 'rtl', 'replace' ); + wp_style_add_data( 'visual-portfolio-gutenberg', 'suffix', '.min' ); + + wp_localize_script( + 'visual-portfolio-gutenberg', + 'VPGutenbergVariables', + array( + 'nonce' => wp_create_nonce( 'vp-ajax-nonce' ), + 'plugin_version' => VISUAL_PORTFOLIO_VERSION, + 'plugin_name' => visual_portfolio()->plugin_name, + 'plugin_url' => visual_portfolio()->plugin_url, + 'admin_url' => get_admin_url(), + 'attributes' => $attributes, + 'controls' => Visual_Portfolio_Controls::get_registered_array(), + 'controls_categories' => Visual_Portfolio_Controls::get_registered_categories(), + 'items_count_notice' => get_option( 'visual_portfolio_items_count_notice_state', 'show' ), + 'items_count_notice_limit' => 40, + ) + ); + + // Meta. + Visual_Portfolio_Assets::enqueue_script( + 'visual-portfolio-gutenberg-custom-post-meta', + 'build/gutenberg/custom-post-meta', + array( 'wp-i18n', 'wp-element', 'wp-components', 'wp-plugins', 'jquery' ) + ); + + wp_localize_script( + 'visual-portfolio-gutenberg-custom-post-meta', + 'VPGutenbergMetaVariables', + array( + 'nonce' => wp_create_nonce( 'vp-ajax-nonce' ), + 'plugin_name' => visual_portfolio()->plugin_name, + ) + ); + } +} +new Visual_Portfolio_Gutenberg_Block(); diff --git a/classes/class-image-placeholder.php b/classes/class-image-placeholder.php new file mode 100644 index 00000000..a96f9904 --- /dev/null +++ b/classes/class-image-placeholder.php @@ -0,0 +1,93 @@ +plugin_path . '/assets/images/placeholder.png'; + $filename = $upload_dir['basedir'] . '/visual-portfolio/placeholder.png'; + + // try to move to /visual-portfolio/ directory. + if ( ! file_exists( $upload_dir['basedir'] . '/visual-portfolio' ) ) { + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + @mkdir( $upload_dir['basedir'] . '/visual-portfolio', 0755, true ); + } + if ( ! file_exists( $upload_dir['basedir'] . '/visual-portfolio' ) ) { + $filename = $upload_dir['basedir'] . '/visual-portfolio-placeholder.png'; + } + + if ( ! file_exists( $filename ) ) { + copy( $source, $filename ); + } + + if ( ! file_exists( $filename ) ) { + return; + } + + $filetype = wp_check_filetype( basename( $filename ), null ); + $attachment = array( + 'guid' => $upload_dir['url'] . '/' . basename( $filename ), + 'post_mime_type' => $filetype['type'], + 'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $filename ) ), + 'post_content' => '', + 'post_status' => 'inherit', + ); + $attach_id = wp_insert_attachment( $attachment, $filename ); + + // Update settings. + $general_settings['no_image'] = $attach_id; + update_option( 'vp_general', $general_settings ); + + // Make sure that this file is included, as wp_generate_attachment_metadata() depends on it. + require_once ABSPATH . 'wp-admin/includes/image.php'; + + // Generate the metadata for the attachment, and update the database record. + $attach_data = wp_generate_attachment_metadata( $attach_id, $filename ); + wp_update_attachment_metadata( $attach_id, $attach_data ); + } +} + +new Visual_Portfolio_Image_Placeholder(); diff --git a/classes/class-images.php b/classes/class-images.php new file mode 100644 index 00000000..ad40fa3a --- /dev/null +++ b/classes/class-images.php @@ -0,0 +1,641 @@ + esc_html__( 'Small (VP)', 'visual-portfolio' ), + 'vp_md' => esc_html__( 'Medium (VP)', 'visual-portfolio' ), + 'vp_lg' => esc_html__( 'Large (VP)', 'visual-portfolio' ), + 'vp_xl' => esc_html__( 'Extra Large (VP)', 'visual-portfolio' ), + ) + ); + } + + /** + * Get blocked attributes to prevent our images lazy loading. + */ + public static function get_image_blocked_attributes() { + $blocked_attributes = array( + 'data-skip-lazy', + 'data-no-lazy', + 'data-src', + 'data-srcset', + 'data-lazy-original', + 'data-lazy-src', + 'data-lazysrc', + 'data-lazyload', + 'data-bgposition', + 'data-envira-src', + 'fullurl', + 'lazy-slider-img', + ); + + /** + * Allow plugins and themes to tell lazy images to skip an image with a given attribute. + */ + return apply_filters( 'vpf_lazyload_images_blocked_attributes', $blocked_attributes ); + } + + /** + * Init Lazyload + */ + public static function init_lazyload() { + // Don't lazy load for feeds, previews and admin side. + if ( is_feed() || is_preview() || is_admin() ) { + return; + } + + // Don't add on AMP endpoint. + if ( function_exists( 'is_amp_endpoint' ) && is_amp_endpoint() ) { + return; + } + + // Disable using filter. + // Same filter used in `class-assets.php`. + if ( ! apply_filters( 'vpf_images_lazyload', true ) ) { + return; + } + + self::$allow_vp_lazyload = ! ! Visual_Portfolio_Settings::get_option( 'lazy_loading', 'vp_images' ); + self::$allow_wp_lazyload = 'full' === Visual_Portfolio_Settings::get_option( 'lazy_loading', 'vp_images' ); + + // Check for plugin settings. + if ( ! self::$allow_vp_lazyload && ! self::$allow_wp_lazyload ) { + return; + } + + $lazyload_exclusions = Visual_Portfolio_Settings::get_option( 'lazy_loading_excludes', 'vp_images' ); + if ( $lazyload_exclusions ) { + self::$lazyload_user_exclusions = explode( "\n", $lazyload_exclusions ); + + add_filter( 'vpf_lazyload_skip_image_with_attributes', 'Visual_Portfolio_Images::add_lazyload_exclusions', 10, 2 ); + } + + if ( self::$allow_wp_lazyload ) { + add_filter( 'the_content', 'Visual_Portfolio_Images::add_image_placeholders', 9999 ); + add_filter( 'post_thumbnail_html', 'Visual_Portfolio_Images::add_image_placeholders', 9999 ); + add_filter( 'get_avatar', 'Visual_Portfolio_Images::add_image_placeholders', 9999 ); + add_filter( 'widget_text', 'Visual_Portfolio_Images::add_image_placeholders', 9999 ); + add_filter( 'get_image_tag', 'Visual_Portfolio_Images::add_image_placeholders', 9999 ); + } + + add_action( 'wp_kses_allowed_html', 'Visual_Portfolio_Images::allow_lazy_attributes' ); + add_action( 'wp_head', 'Visual_Portfolio_Images::add_nojs_fallback' ); + } + + /** + * Get the URL of an image attachment. + * + * @param int $attachment_id Image attachment ID. + * @param string|array $size Optional. Image size to retrieve. Accepts any valid image size, or an array + * of width and height values in pixels (in that order). Default 'thumbnail'. + * @param bool $icon Optional. Whether the image should be treated as an icon. Default false. + * + * @return string|false Attachment URL or false if no image is available. + */ + public static function wp_get_attachment_image_url( $attachment_id, $size = 'thumbnail', $icon = false ) { + $mime_type = get_post_mime_type( $attachment_id ); + + // Prevent usage of resized GIFs, since GIFs animated only in full size. + if ( $mime_type && 'image/gif' === $mime_type ) { + $size = 'full'; + } + + return wp_get_attachment_image_url( $attachment_id, $size, $icon ); + } + + /** + * Allow attributes of Lazy Load for wp_kses. + * + * @param array $allowed_tags The allowed tags and their attributes. + * + * @return array + */ + public static function allow_lazy_attributes( $allowed_tags ) { + if ( ! isset( $allowed_tags['img'] ) ) { + return $allowed_tags; + } + + // But, if images are allowed, ensure that our attributes are allowed! + $img_attributes = array_merge( + $allowed_tags['img'], + array( + 'data-src' => 1, + 'data-sizes' => 1, + 'data-srcset' => 1, + 'data-no-lazy' => 1, + 'loading' => 1, + ) + ); + + $allowed_tags['img'] = $img_attributes; + + return $allowed_tags; + } + + /** + * Fix img src attribute correction in wp_kses. + * + * @param array $protocols protocols array. + * + * @return array + */ + public static function kses_allowed_protocols( $protocols ) { + $protocols[] = 'data'; + return $protocols; + } + + /** + * Add image placeholders. + * + * @param string $content Content. + * @return string + */ + public static function add_image_placeholders( $content ) { + // This is a pretty simple regex, but it works. + // + // 1. Find tags + // 2. Exclude tags, placed inside