This commit is contained in:
2024-05-20 15:37:46 +03:00
commit 00b7dbd0b7
10404 changed files with 3285853 additions and 0 deletions

View File

@ -0,0 +1,42 @@
<?php
/**
* A3 Lazy Load Plugin.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_3rd_A3_Lazy_Load
*/
class Visual_Portfolio_3rd_A3_Lazy_Load {
/**
* Visual_Portfolio_3rd_A3_Lazy_Load constructor.
*/
public function __construct() {
// Fix conflict with lazy loading.
add_filter( 'a3_lazy_load_skip_images_classes', array( $this, 'a3_lazy_load_skip_images_classes' ) );
}
/**
* Add lazyload class to skip.
*
* @param string $classes classes.
*
* @return string
*/
public function a3_lazy_load_skip_images_classes( $classes ) {
if ( '' !== $classes && ! empty( $classes ) ) {
$classes .= ',';
}
$classes .= 'vp-lazyload';
return $classes;
}
}
new Visual_Portfolio_3rd_A3_Lazy_Load();

View File

@ -0,0 +1,105 @@
<?php
/**
* All In One SEO Plugin.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_3rd_All_In_One_Seo
*/
class Visual_Portfolio_3rd_All_In_One_Seo {
/**
* Visual_Portfolio_3rd_All_In_One_Seo constructor.
*/
public function __construct() {
// Fixed canonical links.
add_filter( 'aioseo_canonical_url', array( 'Visual_Portfolio_Archive_Mapping', 'get_canonical' ) );
add_filter( 'aioseo_schema_output', array( $this, 'graph_schema_output' ) );
add_action( 'wp_print_footer_scripts', array( $this, 'set_query_as_archive' ), 8 ); // priority one level lower than what the plugin uses.
add_action( 'wp_print_footer_scripts', array( $this, 'remove_query_as_archive' ), 13 ); // priority one level higher than what the plugin uses.
add_action( 'wp', array( $this, 'set_query_as_archive' ), 9 );
add_action( 'wp', array( $this, 'remove_query_as_archive' ), 13 );
}
/**
* Allows changing graph output.
*
* @param array $graph - Graph output array.
* @return array
*/
public function graph_schema_output( $graph ) {
if ( isset( $graph ) && ! empty( $graph ) && is_array( $graph ) ) {
foreach ( $graph as $key => $graph_item ) {
if ( isset( $graph_item['@type'] ) ) {
switch ( $graph_item['@type'] ) {
case 'BreadcrumbList':
$graph[ $key ]['@id'] = Visual_Portfolio_Archive_Mapping::get_canonical_anchor( $graph_item['@id'] );
break;
case 'WebPage':
case 'CollectionPage':
$graph[ $key ]['@id'] = Visual_Portfolio_Archive_Mapping::get_canonical_anchor( $graph_item['@id'] );
$graph[ $key ]['url'] = Visual_Portfolio_Archive_Mapping::get_canonical( $graph_item['@id'] );
$graph[ $key ]['breadcrumb']['@id'] = Visual_Portfolio_Archive_Mapping::get_canonical_anchor( $graph_item['@id'] );
break;
}
}
}
}
return $graph;
}
/**
* Set query as archive temporary.
* This is necessary for the plugin to work correctly and set all the necessary settings in the page footer.
* Because our custom archive and taxonomy pages override the base query and interfere with the global object,
* Conflicts may occur with some SEO plugins that work this way.
* In this case, the search plugin is trying to place the assets needed for a regular page in the footer,
* While the page itself is defined as a taxonomy.
* In this case, we let the plugin know that this is not a page, but a category.
*
* @return void
*/
public function set_query_as_archive() {
if ( Visual_Portfolio_Archive_Mapping::is_category() ) {
global $wp_query;
$wp_query->is_archive = true;
$wp_query->is_single = false;
$wp_query->is_singular = false;
$wp_query->is_page = false;
$wp_query->is_post_type_archive = true;
$wp_query->is_category = true;
}
}
/**
* Set query as archive temporary.
* This is necessary for the plugin to work correctly and set all the necessary settings in the page footer.
* Because our custom archive and taxonomy pages override the base query and interfere with the global object,
* Conflicts may occur with some SEO plugins that work this way.
* In this case, the search plugin is trying to place the assets needed for a regular page in the footer,
* While the page itself is defined as a taxonomy.
* In this case, we let the plugin know that this is not a page, but a category.
* This function cancels previous settings so as not to interfere with further system operation.
*
* @return void
*/
public function remove_query_as_archive() {
if ( Visual_Portfolio_Archive_Mapping::is_category() ) {
global $wp_query;
$wp_query->is_archive = false;
$wp_query->is_single = false;
$wp_query->is_singular = true;
$wp_query->is_page = true;
$wp_query->is_post_type_archive = false;
$wp_query->is_category = false;
}
}
}
new Visual_Portfolio_3rd_All_In_One_Seo();

View File

@ -0,0 +1,43 @@
<?php
/**
* Divi Plugin.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_Divi
*/
class Visual_Portfolio_Divi {
/**
* Visual_Portfolio_Divi constructor.
*/
public function __construct() {
add_action( 'wp_head', array( $this, 'maybe_fix_images_width' ) );
}
/**
* Add CSS to fix lazy loaded Divi images widths.
* When the image has lazy loading attributes, width is set to 4px, not the actual image size.
*/
public function maybe_fix_images_width() {
if ( ! defined( 'ET_CORE' ) || 'full' !== Visual_Portfolio_Settings::get_option( 'lazy_loading', 'vp_images' ) ) {
return;
}
?>
<style type="text/css">
.et-db #et-boc .et-l .et_pb_module .et_pb_image_wrap,
.et-db #et-boc .et-l .et_pb_module .et_pb_image_wrap img[data-src] {
width: 100%;
}
</style>
<?php
}
}
new Visual_Portfolio_Divi();

View File

@ -0,0 +1,254 @@
<?php
/**
* Widget for Elementor
*
* @package visual-portfolio/elementor
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_3rd_Elementor_Widget
*/
class Visual_Portfolio_3rd_Elementor_Widget extends \Elementor\Widget_Base {
/**
* Constructor of Visual_Portfolio_3rd_Elementor_Widget class.
*
* @param array $data default widget data.
* @param null|array $args default widget args.
*/
public function __construct( $data = array(), $args = null ) {
// Migrate from old 'id' control to new 'saved_id'.
if ( isset( $data['settings']['id'] ) ) {
if ( $data['settings']['id'] && ( ! isset( $data['settings']['saved_id'] ) || ! $data['settings']['saved_id'] ) ) {
$data['settings']['saved_id'] = $data['settings']['id'];
}
unset( $data['settings']['id'] );
}
parent::__construct( $data, $args );
if ( $this->is_preview_mode() ) {
Visual_Portfolio_Assets::register_script( 'iframe-resizer', 'assets/vendor/iframe-resizer/js/iframeResizer.min', array(), '4.3.7' );
Visual_Portfolio_Assets::register_script( 'visual-portfolio-elementor', 'build/assets/admin/js/elementor', array( 'elementor-frontend', 'iframe-resizer' ) );
Visual_Portfolio_Assets::register_style( 'visual-portfolio-elementor', 'build/assets/admin/css/elementor' );
wp_style_add_data( 'visual-portfolio-elementor', 'rtl', 'replace' );
wp_style_add_data( 'visual-portfolio-elementor', 'suffix', '.min' );
}
}
/**
* Is edit mode check.
*
* @return boolean
*/
public function is_preview_mode() {
return \Elementor\Plugin::$instance->preview->is_preview_mode() || \Elementor\Plugin::$instance->editor->is_edit_mode();
}
/**
* Get widget name.
*
* @return string Widget name.
*/
public function get_name() {
return 'visual-portfolio';
}
/**
* Get widget title.
*
* @return string Widget title.
*/
public function get_title() {
return visual_portfolio()->plugin_name;
}
/**
* Get widget icon.
*
* @return string Widget icon.
*/
public function get_icon() {
return 'eicon-gallery-grid';
}
/**
* Get widget categories.
*
* @return array Widget categories.
*/
public function get_categories() {
return array( 'general' );
}
/**
* Get widget keywords.
*
* @return array Widget keywords.
*/
public function get_keywords() {
return array( 'portfolio', 'gallery', 'images', 'visual portfolio', 'vpf' );
}
/**
* Get script dependencies.
*
* @return array Widget script dependencies.
*/
public function get_script_depends() {
if ( $this->is_preview_mode() ) {
return array( 'visual-portfolio-elementor' );
}
return array();
}
/**
* We should add the prefix to all options, because Select2 conflicts with the Safari for some reason.
*
* @param String $option - option value.
*
* @return String
*/
public function set_option_prefix( $option ) {
return 'post-id-' . $option;
}
/**
* Remove prefix from the option.
*
* @param String $option - prefixed option value.
*
* @return String
*/
public function unset_option_prefix( $option ) {
return str_replace( 'post-id-', '', $option );
}
/**
* Get style dependencies.
*
* @return array Widget style dependencies.
*/
public function get_style_depends() {
if ( $this->is_preview_mode() ) {
return array( 'visual-portfolio-elementor' );
}
return array();
}
/**
* Adds different input fields to allow the user to change and customize the widget settings.
*
* @return void
*/
protected function register_controls() {
// 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,
)
);
$options = array(
'' => esc_html__( '-- Select Layout --', 'visual-portfolio' ),
);
foreach ( $vp_query as $post ) {
$options[ $this->set_option_prefix( $post->ID ) ] = '#' . $post->ID . ' - ' . $post->post_title;
}
$this->start_controls_section(
'content_section',
array(
'label' => __( 'General', 'visual-portfolio' ),
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
)
);
$this->add_control(
'saved_id',
array(
'label' => esc_html__( 'Saved Layout', 'visual-portfolio' ),
'type' => \Elementor\Controls_Manager::SELECT2,
'options' => $options,
'dynamic' => array(
'active' => true,
),
)
);
$this->end_controls_section();
}
/**
* Render widget output on the frontend.
*/
protected function render() {
$settings = array_merge(
array(
'saved_id' => '',
'class' => '',
),
$this->get_settings_for_display()
);
// No saved layout selected.
if ( ! $settings['saved_id'] ) {
return;
}
if ( $this->is_preview_mode() ) {
$this->add_render_attribute(
'wrapper',
array(
'class' => 'visual-portfolio-elementor-preview',
'data-id' => $this->unset_option_prefix( $settings['saved_id'] ),
)
);
}
?>
<div
<?php
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $this->get_render_attribute_string( 'wrapper' );
?>
>
<?php if ( $this->is_preview_mode() ) : ?>
<iframe allowtransparency="true"></iframe>
<?php else : ?>
<?php echo do_shortcode( '[visual_portfolio id="' . esc_attr( $this->unset_option_prefix( $settings['saved_id'] ) ) . '"]' ); ?>
<?php endif; ?>
</div>
<?php
}
/**
* Render shortcode widget output in the editor.
*
* Written as a Backbone JavaScript template and used to generate the live preview.
*
* @since 1.0.0
* @access protected
*/
protected function content_template() {}
/**
* Render Plain Content
*
* @param array $instance instance data.
*/
// phpcs:ignore
public function render_plain_content( $instance = array() ) {}
}

View File

@ -0,0 +1,160 @@
<?php
/**
* Class for Elementor
*
* @package visual-portfolio/elementor
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_3rd_Elementor
*/
class Visual_Portfolio_3rd_Elementor {
/**
* Lightbox fix added.
*
* @var boolean
*/
public $lightbox_fix_added = false;
/**
* Visual_Portfolio_3rd_Elementor constructor.
*/
public function __construct() {
add_action( 'elementor/widgets/register', array( $this, 'register_widget' ) );
// We should also try to include this script in the footer,
// since caching plugins place jQuery in the footer, and our script depends on it.
add_action( 'wp_body_open', array( $this, 'maybe_fix_elementor_lightbox_conflict' ) );
add_action( 'wp_footer', array( $this, 'maybe_fix_elementor_lightbox_conflict' ), 20 );
// Compatibility code for Swiper library.
add_action( 'wp_enqueue_scripts', array( $this, 'fix_elementor_swiper_assets' ), 101 );
}
/**
* Register widget
*/
public function register_widget() {
require_once visual_portfolio()->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;
?>
<script>
(function($) {
if (!$) {
return;
}
// Previously we added this code on Elementor pages only,
// but sometimes Lightbox enabled globally and it still conflicting with our galleries.
// if (!$('.elementor-page').length) {
// return;
// }
function addDataAttribute($items) {
$items.find('.vp-portfolio__item a:not([data-elementor-open-lightbox])').each(function () {
if (/\.(png|jpe?g|gif|svg|webp)(\?.*)?$/i.test(this.href)) {
this.dataset.elementorOpenLightbox = 'no';
}
});
}
$(document).on('init.vpf', function(event, vpObject) {
if ('vpf' !== event.namespace) {
return;
}
addDataAttribute(vpObject.$item);
});
$(document).on('addItems.vpf', function(event, vpObject, $items) {
if ('vpf' !== event.namespace) {
return;
}
addDataAttribute($items);
});
})(window.jQuery);
</script>
<?php
}
/**
* Add Swiper from the Elementor plugin to prevent conflicts.
*
* @link https://wordpress.org/support/topic/visual-portfolio-elementor-issue/ - Old Elementor (< v3.11.0) and their lightbox is not working properly if we include new Swiper library.
* @link https://wordpress.org/support/topic/elementor-image-carousel-navigation-is-affected-by-this-plugin/ - New Elementor (>= 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();

View File

@ -0,0 +1,30 @@
<?php
/**
* EWWW Image Optimizer Plugin.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_3rd_Ewww_Image_Optimizer
*/
class Visual_Portfolio_3rd_Ewww_Image_Optimizer {
/**
* Visual_Portfolio_3rd_Ewww_Image_Optimizer constructor.
*/
public function __construct() {
if ( ! function_exists( 'ewww_image_optimizer_get_option' ) || ! ewww_image_optimizer_get_option( 'ewww_image_optimizer_lazy_load' ) ) {
return;
}
// Disable our lazyload if EWWW lazyload used.
add_filter( 'vpf_images_lazyload', '__return_false' );
add_filter( 'vpf_enqueue_plugin_lazysizes', '__return_false' );
}
}
new Visual_Portfolio_3rd_Ewww_Image_Optimizer();

View File

@ -0,0 +1,67 @@
<?php
/**
* Imagify Plugin.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_3rd_Imagify
*/
class Visual_Portfolio_3rd_Imagify {
/**
* Visual_Portfolio_3rd_Imagify constructor.
*/
public function __construct() {
// Fix lazyload attributes.
//
// Thanks to:
// - https://wordpress.org/support/topic/all-images-are-grey/
// - https://wordpress.org/support/topic/all-images-are-grey-again/.
add_filter( 'imagify_picture_attributes', array( $this, 'imagify_picture_attributes' ) );
add_filter( 'imagify_picture_img_attributes', array( $this, 'imagify_picture_img_attributes' ), 10, 2 );
}
/**
* Remove lazyload class from the picture tag.
*
* @param array $attributes image tag attributes.
*
* @return array
*/
public function imagify_picture_attributes( $attributes ) {
if ( isset( $attributes['class'] ) && strpos( $attributes['class'], 'vp-lazyload' ) !== false ) {
$attributes['class'] = str_replace( 'vp-lazyload', '', $attributes['class'] );
}
return $attributes;
}
/**
* Restore lazyload class to the img tag.
*
* @param array $attributes image tag attributes.
* @param array $image image data.
*
* @return array
*/
public function imagify_picture_img_attributes( $attributes, $image ) {
if ( isset( $image['attributes']['class'] ) && strpos( $image['attributes']['class'], 'vp-lazyload' ) !== false ) {
if ( isset( $attributes['class'] ) ) {
$attributes['class'] .= ' ';
} else {
$attributes['class'] = '';
}
$attributes['class'] .= 'vp-lazyload';
}
return $attributes;
}
}
new Visual_Portfolio_3rd_Imagify();

View File

@ -0,0 +1,55 @@
<?php
/**
* Jetpack Plugin.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_3rd_Jetpack
*/
class Visual_Portfolio_3rd_Jetpack {
/**
* Visual_Portfolio_3rd_Jetpack constructor.
*/
public function __construct() {
// Fix conflict with lazy loading.
add_filter( 'jetpack_lazy_images_skip_image_with_attributes', array( $this, 'jetpack_lazy_images_skip_image_with_attributes' ), 15, 2 );
add_action( 'wp_enqueue_scripts', array( $this, 'wp_enqueue_scripts' ), 20 );
}
/**
* We need to init the Jetpack lazy loading manually after Visual Portfolio AJAX completed.
*/
public function wp_enqueue_scripts() {
$wp_scripts = wp_scripts();
$jetpack_ll_handler = 'jetpack-lazy-images';
if ( ! isset( $wp_scripts->registered[ $jetpack_ll_handler ] ) ) {
return;
}
Visual_Portfolio_Assets::register_script( 'visual-portfolio-3rd-jetpack', 'build/assets/js/3rd/plugin-jetpack' );
$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();

View File

@ -0,0 +1,30 @@
<?php
/**
* Lazy Loader Plugin.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_3rd_Lazy_Loader
*/
class Visual_Portfolio_3rd_Lazy_Loader {
/**
* Visual_Portfolio_3rd_Lazy_Loader constructor.
*/
public function __construct() {
if ( ! class_exists( 'FlorianBrinkmann\LazyLoadResponsiveImages\Plugin' ) ) {
return;
}
// Disable our lazyload if Lazy Loader plugin installed.
add_filter( 'vpf_images_lazyload', '__return_false' );
add_filter( 'vpf_enqueue_plugin_lazysizes', '__return_false' );
}
}
new Visual_Portfolio_3rd_Lazy_Loader();

View File

@ -0,0 +1,41 @@
<?php
/**
* Paid Memberships Pro Plugin.
*
* @package visual-portfolio/pmp
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_3rd_Paid_Memberships_Pro
*/
class Visual_Portfolio_3rd_Paid_Memberships_Pro {
/**
* Visual_Portfolio_3rd_Paid_Memberships_Pro constructor.
*/
public function __construct() {
add_action( 'init', array( $this, 'fix_pmpromh_redirect_in_preview' ) );
}
/**
* Remove redirect action from the preview frame.
* Because "Paid Memberships Pro - Member Homepages Add On" make their own redirect and our preview frame failed to load.
*/
public function fix_pmpromh_redirect_in_preview() {
if ( ! function_exists( 'pmpromh_template_redirect_homepage' ) ) {
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! isset( $_GET['vp_preview'] ) ) {
return;
}
remove_action( 'template_redirect', 'pmpromh_template_redirect_homepage' );
}
}
new Visual_Portfolio_3rd_Paid_Memberships_Pro();

View File

@ -0,0 +1,85 @@
<?php
/**
* Rank Math SEO Plugin.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_3rd_Rank_Math
*/
class Visual_Portfolio_3rd_Rank_Math {
/**
* Visual_Portfolio_3rd_Rank_Math constructor.
*/
public function __construct() {
// Fixed canonical links.
add_filter( 'rank_math/frontend/canonical', array( 'Visual_Portfolio_Archive_Mapping', 'get_canonical' ) );
add_filter( 'rank_math/frontend/title', array( $this, 'get_title' ) );
add_filter( 'rank_math/opengraph/facebook/og_title', array( $this, 'get_title' ) );
add_action( 'rank_math/head', array( $this, 'set_query_as_archive' ), 5 ); // priority one level lower than what the plugin uses.
add_action( 'rank_math/head', array( $this, 'remove_query_as_archive' ), 23 ); // priority one level higher than what the plugin uses.
}
/**
* Allow changing the Rank Math generated title.
*
* @param string $title - Current Page Title.
* @return string
*/
public function get_title( $title ) {
return Visual_Portfolio_Archive_Mapping::get_current_term_title() ?? $title;
}
/**
* Set query as archive temporary.
* This is necessary for the plugin to work correctly and set all the necessary settings in the page header.
* Because our custom archive and taxonomy pages override the base query and interfere with the global object,
* Conflicts may occur with some SEO plugins that work this way.
* In this case, the search plugin is trying to place the assets needed for a regular page in the header,
* While the page itself is defined as a taxonomy.
* In this case, we let the plugin know that this is not a page, but a category.
*
* @return void
*/
public function set_query_as_archive() {
if ( Visual_Portfolio_Archive_Mapping::is_category() ) {
global $wp_query;
$wp_query->is_archive = true;
$wp_query->is_single = false;
$wp_query->is_singular = false;
$wp_query->is_page = false;
$wp_query->is_post_type_archive = true;
}
}
/**
* Remove query as archive temporary.
* This is necessary for the plugin to work correctly and set all the necessary settings in the page header.
* Because our custom archive and taxonomy pages override the base query and interfere with the global object,
* Conflicts may occur with some SEO plugins that work this way.
* In this case, the search plugin is trying to place the assets needed for a regular page in the header,
* While the page itself is defined as a taxonomy.
* In this case, we let the plugin know that this is not a page, but a category.
* This function cancels previous settings so as not to interfere with further system operation.
*
* @return void
*/
public function remove_query_as_archive() {
if ( Visual_Portfolio_Archive_Mapping::is_category() ) {
global $wp_query;
$wp_query->is_archive = false;
$wp_query->is_single = true;
$wp_query->is_singular = true;
$wp_query->is_page = true;
$wp_query->is_post_type_archive = false;
}
}
}
new Visual_Portfolio_3rd_Rank_Math();

View File

@ -0,0 +1,87 @@
<?php
/**
* SiteGround Optimizer Plugin.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
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;
}
// 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';
if ( ! isset( $wp_scripts->registered[ $sg_ll_handler ] ) ) {
return;
}
$wp_scripts->registered[ $sg_ll_handler ]->deps[] = 'jquery';
wp_add_inline_script(
$sg_ll_handler,
'(function($){
if ( !$ ) {
return;
}
function maybeFixSGSrc( $images ) {
$images.find( "img[src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\"]" ).each( function() {
var $img = $( this );
var width = $img.attr( "width" );
var height = $img.attr( "height" );
if ( width && height ) {
var newSrc = "<svg width=\"" + width + "\" height=\"" + height + "\" viewBox=\"0 0 " + width + " " + height + "\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"></svg>";
newSrc = newSrc
.replace( /</g, "%3c" )
.replace( />/g, "%3e" )
.replace( /#/g, "%23" )
.replace( /"/g, "\'" );
$img.attr( "src", "data:image/svg+xml;utf8," + newSrc );
}
} );
}
// Fix starter images.
maybeFixSGSrc( $( ".vp-portfolio__items" ) );
// Fix AJAX loaded images.
$( document ).on( "addItems.vpf", function ( event, self, $items, removeExisting ) {
if ( "vpf" !== event.namespace ) {
return;
}
maybeFixSGSrc( $items );
} );
}(window.jQuery));',
'before'
);
}
}
new Visual_Portfolio_3rd_SG_Cachepress();

View File

@ -0,0 +1,107 @@
<?php
/**
* Extend TinyMCE toolbar
*
* @package visual-portfolio/tinymce
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_3rd_TinyMCE
*/
class Visual_Portfolio_3rd_TinyMCE {
/**
* Visual_Portfolio_3rd_TinyMCE constructor.
*/
public function __construct() {
$this->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();

View File

@ -0,0 +1,99 @@
<?php
/**
* Shortcode for Visual Composer
*
* @package visual-portfolio/vc
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_3rd_VC
*/
class Visual_Portfolio_3rd_VC {
/**
* Visual_Portfolio_3rd_VC constructor.
*/
public function __construct() {
$this->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' );
}
}
/**
* 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();

View File

@ -0,0 +1,40 @@
<?php
/**
* WP Rocket Plugin.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_3rd_WP_Rocket
*/
class Visual_Portfolio_3rd_WP_Rocket {
/**
* Visual_Portfolio_3rd_WP_Rocket constructor.
*/
public function __construct() {
// Fix conflict with lazy loading.
add_filter( 'rocket_delay_js_exclusions', array( $this, 'rocket_delay_js_exclusions' ) );
}
/**
* Exclude all our lazysizes scripts from delayed loading,
* because WP Rocket excluded lazysizes automatically, but not other assets,
* this causes problems with lazy loading.
*
* @param array $excluded excluded scripts.
*
* @return array
*/
public function rocket_delay_js_exclusions( $excluded ) {
$excluded[] = 'visual-portfolio/assets/js/lazysizes';
return $excluded;
}
}
new Visual_Portfolio_3rd_WP_Rocket();

View File

@ -0,0 +1,166 @@
<?php
/**
* Register some fields for WPML.
*
* @package visual-portfolio/preview
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase, WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
/**
* Class Visual_Portfolio_3rd_WPML
*/
class Visual_Portfolio_3rd_WPML {
/**
* Archive Page.
*
* @var int $archive_page
*/
private $archive_page = null;
/**
* Visual_Portfolio_3rd_WPML constructor.
*/
public function __construct() {
global $iclTranslationManagement;
if ( class_exists( 'SitePress' ) ) {
add_action( 'init', array( $this, 'init' ), 9 );
}
if ( ! isset( $iclTranslationManagement ) ) {
return;
}
add_filter( 'vpf_registered_controls', array( $this, 'make_control_translatable' ) );
add_filter( 'vpf_extend_options_before_query_args', array( $this, 'prepare_custom_query_taxonomies' ) );
}
/**
* Initialize archive.
*
* @see __construct
*/
public function init() {
$this->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();

View File

@ -0,0 +1,73 @@
<?php
/**
* Yoast SEO Plugin.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_3rd_Yoast
*/
class Visual_Portfolio_3rd_Yoast {
/**
* Visual_Portfolio_3rd_Yoast constructor.
*/
public function __construct() {
// Fixed canonical links.
add_filter( 'wpseo_canonical', array( 'Visual_Portfolio_Archive_Mapping', 'get_canonical' ), 12, 1 );
add_filter( 'wpseo_opengraph_url', array( 'Visual_Portfolio_Archive_Mapping', 'get_canonical' ), 12, 1 );
add_filter( 'wpseo_schema_webpage', array( $this, 'graph_schema_webpage' ), 12, 1 );
add_filter( 'wpseo_schema_breadcrumb', array( $this, 'graph_schema_breadcrumb' ), 12, 1 );
add_filter( 'wpseo_opengraph_title', array( $this, 'graph_title' ), 12, 1 );
}
/**
* Allows changing webpage graph piece output.
*
* @param array $webpage_graph_piece - The webpage graph piece to filter.
* @return array
*/
public function graph_schema_webpage( $webpage_graph_piece ) {
$webpage_graph_piece['@id'] = Visual_Portfolio_Archive_Mapping::get_canonical( $webpage_graph_piece['@id'] );
$webpage_graph_piece['url'] = Visual_Portfolio_Archive_Mapping::get_canonical( $webpage_graph_piece['url'] );
$webpage_graph_piece['breadcrumb']['@id'] = Visual_Portfolio_Archive_Mapping::get_canonical_anchor( $webpage_graph_piece['breadcrumb']['@id'] );
$webpage_graph_piece['name'] = Visual_Portfolio_Archive_Mapping::get_current_term_title() ?? $webpage_graph_piece['name'];
if ( ! empty( $webpage_graph_piece['potentialAction'] ) ) {
foreach ( $webpage_graph_piece['potentialAction'] as $key => $potential_action ) {
if ( isset( $potential_action['target'] ) && ! is_array( $potential_action['target'] ) && isset( $potential_action['@type'] ) && 'ReadAction' === $potential_action['@type'] ) {
$webpage_graph_piece['potentialAction'][ $key ]['target'] = Visual_Portfolio_Archive_Mapping::get_canonical( $potential_action['target'] );
}
}
}
return $webpage_graph_piece;
}
/**
* Allows changing breadcrumb graph piece output.
*
* @param array $breadcrumb_graph_piece - The breadcrumb graph piece to filter.
* @return array
*/
public function graph_schema_breadcrumb( $breadcrumb_graph_piece ) {
$breadcrumb_graph_piece['@id'] = Visual_Portfolio_Archive_Mapping::get_canonical_anchor( $breadcrumb_graph_piece['@id'] );
return $breadcrumb_graph_piece;
}
/**
* Allow changing the Yoast SEO generated title.
*
* @param string $title - Current Graph Title.
* @return string
*/
public function graph_title( $title ) {
return Visual_Portfolio_Archive_Mapping::get_current_term_title() ?? $title;
}
}
new Visual_Portfolio_3rd_Yoast();

View File

@ -0,0 +1,37 @@
<?php
/**
* Avada Theme.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_3rd_Avada
*/
class Visual_Portfolio_3rd_Avada {
/**
* Visual_Portfolio_3rd_Avada constructor.
*/
public function __construct() {
if ( is_admin() ) {
return;
}
$avada_options = get_option( 'fusion_options' );
// Important - Avada folder is in uppercase, this is not a mistake.
if ( 'Avada' !== get_template() || ! isset( $avada_options['lazy_load'] ) || 'avada' !== $avada_options['lazy_load'] ) {
return;
}
// Disable our lazyload if Avada's lazyload used.
add_filter( 'vpf_images_lazyload', '__return_false' );
add_filter( 'vpf_enqueue_plugin_lazysizes', '__return_false' );
}
}
new Visual_Portfolio_3rd_Avada();

View File

@ -0,0 +1,82 @@
<?php
/**
* Enfold Theme.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_3rd_Enfold
*/
class Visual_Portfolio_3rd_Enfold {
/**
* Cache for lazy loading option
*
* @var string
*/
public $lazy_loading_option_cache = null;
/**
* Visual_Portfolio_3rd_Enfold constructor.
*/
public function __construct() {
if ( is_admin() ) {
return;
}
if ( 'enfold' !== get_template() ) {
return;
}
// Disable Enfold lightbox by adding classname.
add_filter( 'vpf_extend_portfolio_class', array( $this, 'disable_lightbox_class' ) );
// Disable our lazyload if Enfold lazyload enabled.
add_filter( 'vpf_images_lazyload', array( $this, 'disable_lazy_load' ) );
}
/**
* Disable Enfold lightbox by adding classname.
*
* @param string $class - portfolio block classname.
*
* @return string
*/
public function disable_lightbox_class( $class ) {
$class .= ' noLightbox';
return $class;
}
/**
* Disable VPF lazy load if Enfold uses their own.
*
* @param boolean $return - portfolio block classname.
*
* @return boolean
*/
public function disable_lazy_load( $return ) {
$enfold_option = '';
if ( null !== $this->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();

View File

@ -0,0 +1,36 @@
<?php
/**
* Thrive Architect Theme Builder.
* This builder overrides page output and we can't enqueue inline dynamic styles using `wp_add_inline_style`
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_3rd_Thrive_Architect
*/
class Visual_Portfolio_3rd_Thrive_Architect {
/**
* Visual_Portfolio_3rd_Thrive_Architect constructor.
*/
public function __construct() {
add_action( 'init', array( $this, 'init' ) );
}
/**
* Init action.
*/
public function init() {
if ( ! function_exists( 'tcb_custom_editable_content' ) ) {
return;
}
add_filter( 'vpf_enqueue_dynamic_styles_inline_style', '__return_false' );
}
}
new Visual_Portfolio_3rd_Thrive_Architect();

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,137 @@
<?php
/**
* Ask Review Notice.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_Ask_Review_Notice
*/
class Visual_Portfolio_Ask_Review_Notice {
/**
* Option name.
*
* @var string
*/
public $option_name = 'vpf_ask_review_notice';
/**
* Visual_Portfolio_Ask_Review_Notice constructor.
*/
public function __construct() {
add_action( 'admin_notices', array( $this, 'admin_notices' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
add_action( 'wp_ajax_vpf_dismiss_ask_review_notice', array( $this, 'ajax_vpf_dismiss_ask_review_notice' ) );
}
/**
* Check if we can display notice.
*/
public function is_notice_allowed() {
$state = get_site_option( $this->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;
}
?>
<div class="notice notice-info vpf-admin-notice" id="vpf-review-plugin-notice">
<div class="vpf-admin-notice-icon">
<i class="dashicons-visual-portfolio"></i>
</div>
<div class="vpf-admin-notice-content">
<h3><?php esc_html_e( 'Satisfied using Visual Portfolio?', 'visual-portfolio' ); ?></h3>
<p>
<?php
// translators: %s - Plugin name.
echo wp_kses_post( sprintf( __( 'Hey, we noticed you\'ve been using %s for more than two weeks now that\'s awesome!', 'visual-portfolio' ), '<strong>' . _x( 'Visual Portfolio', 'plugin name inside the review notice', 'visual-portfolio' ) . '</strong>' ) );
?>
<br>
<?php esc_html_e( 'Could you please do us a BIG favor and give it a rating on WordPress.org to help us spread the word and boost our motivation?', 'visual-portfolio' ); ?>
</p>
<p>
<a href="https://wordpress.org/support/plugin/visual-portfolio/reviews/?filter=5#new-post" class="vpf-review-plugin-notice-dismiss" data-vpf-review-action="yes" target="_blank" rel="noopener noreferrer">
<strong>
<?php esc_html_e( 'Yes, you deserve it', 'visual-portfolio' ); ?>
</strong>
</a>
<br>
<a href="#" class="vpf-review-plugin-notice-dismiss" data-vpf-review-action="later">
<?php esc_html_e( 'No, maybe later', 'visual-portfolio' ); ?>
</a><br>
<a href="#" class="vpf-review-plugin-notice-dismiss" data-vpf-review-action="already">
<?php esc_html_e( 'I already did', 'visual-portfolio' ); ?>
</a>
</p>
</div>
</div>
<?php
}
/**
* Enqueue script.
*/
public function admin_enqueue_scripts() {
if ( is_customize_preview() ) {
return;
}
Visual_Portfolio_Assets::enqueue_script( 'visual-portfolio-ask-review-notice', 'build/assets/admin/js/ask-review-notice' );
wp_localize_script(
'visual-portfolio-ask-review-notice',
'VPAskReviewNotice',
array(
'nonce' => 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();

View File

@ -0,0 +1,945 @@
<?php
/**
* Assets static and dynamic.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_Assets
*/
class Visual_Portfolio_Assets {
/**
* List with stored assets.
*
* @var array
*/
private static $stored_assets = array(
'script' => 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.
* @param string $filetype asset file type [style|script].
*
* @return array
*/
public static function get_asset_file( $filepath, $filetype = '' ) {
$asset_path = visual_portfolio()->plugin_path . '/' . str_replace( '.min', '', $filepath ) . '.asset.php';
if ( file_exists( $asset_path ) ) {
// phpcs:ignore WPThemeReview.CoreFunctionality.FileInclude.FileIncludeFound
return include $asset_path;
} elseif ( ! empty( $filetype ) ) {
$file_ext = 'style' === $filetype ? 'css' : 'js';
$full_file_path = visual_portfolio()->plugin_path . '/' . $filepath . '.' . $file_ext;
$file_version = file_exists( $full_file_path ) ? filemtime( $full_file_path ) : null;
}
return array(
'dependencies' => array(),
'version' => $file_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, 'script' );
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, 'style' );
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( '&gt;', '>', $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 <body> 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( '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(
'isotope',
),
),
'visual-portfolio-plugin-fj-gallery' => array(
'build/assets/js/plugin-fj-gallery',
array(
'flickr-justified-gallery',
),
),
'visual-portfolio-plugin-swiper' => array(
'build/assets/js/plugin-swiper',
array(
'swiper',
),
),
'visual-portfolio-custom-scrollbar' => array(
'build/assets/js/custom-scrollbar',
array(
'simplebar',
),
),
'visual-portfolio-lazyload' => array(
'build/assets/js/lazyload',
array(
'lazysizes',
),
),
'visual-portfolio-popup-gallery' => array(
'build/assets/js/popup-gallery',
),
'visual-portfolio-plugin-photoswipe' => array(
'build/assets/js/plugin-photoswipe',
array(
'photoswipe-ui-default',
'visual-portfolio-popup-gallery',
),
),
'visual-portfolio-plugin-fancybox' => array(
'build/assets/js/plugin-fancybox',
array(
'fancybox',
'visual-portfolio-popup-gallery',
),
),
'visual-portfolio-layout-masonry' => array(
'build/assets/js/layout-masonry',
array(
'visual-portfolio-plugin-isotope',
),
),
'visual-portfolio-layout-grid' => array(
'build/assets/js/layout-grid',
array(
'visual-portfolio-plugin-isotope',
),
),
'visual-portfolio-layout-tiles' => array(
'build/assets/js/layout-tiles',
array(
'visual-portfolio-plugin-isotope',
),
),
'visual-portfolio-layout-justified' => array(
'build/assets/js/layout-justified',
array(
'visual-portfolio-plugin-fj-gallery',
),
),
'visual-portfolio-layout-slider' => array(
'build/assets/js/layout-slider',
array(
'visual-portfolio-plugin-swiper',
),
),
'visual-portfolio-items-style-fly' => array(
'build/assets/js/items-style-fly',
),
'visual-portfolio-pagination-infinite' => array(
'build/assets/js/pagination-infinite',
),
'visual-portfolio-pagination-minimal-paged' => array(
'build/assets/js/pagination-minimal-paged',
),
);
foreach ( $vp_scripts as $name => $data ) {
self::register_script( $name, $data[0], $data[1] ?? array() );
}
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. <br /> 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 "<script type='text/javascript'>\n";
echo "/* <![CDATA[ */\n";
echo 'var VPData = ' . wp_json_encode( $data ) . ';';
echo "\n/* ]]> */\n";
echo "</script>\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( '&gt;', '>', $styles );
}
if ( ! $styles ) {
return;
}
?>
<noscript>
<style type="text/css">
<?php echo wp_kses( $styles, array( '\'', '\"' ) ); ?>
</style>
</noscript>
<?php
}
/**
* Add fallback for lazyloading.
*/
public static function add_lazyload_fallback_script() {
$css_url = visual_portfolio()->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;
?>
<script>
(function(){
// Check if fallback is not necessary.
if ( CSS.supports('selector(:has(div))') ) {
return;
}
var linkTag = document.createElement("link");
linkTag.setAttribute('rel', 'stylesheet');
linkTag.setAttribute('href', '<?php echo esc_url( $css_url ); ?>');
document.head.appendChild(linkTag);
var scriptTag = document.createElement("script");
scriptTag.setAttribute('src', '<?php echo esc_url( $js_url ); ?>');
document.head.appendChild(scriptTag);
}());
</script>
<?php
}
/**
* Parse shortcodes from content.
*/
public function maybe_parse_shortcodes_from_content() {
global $wp_query;
if ( is_admin() || ! isset( $wp_query->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();

View File

@ -0,0 +1,183 @@
<?php
/**
* Breakpoints.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_Breakpoints
*/
class Visual_Portfolio_Breakpoints {
/**
* Extra Small Default Breakpoint.
*
* @var int
*/
private static $default_xs = 320;
/**
* Mobile Default Breakpoint.
*
* @var int
*/
private static $default_sm = 576;
/**
* Tablet Breakpoint.
*
* @var int
*/
private static $default_md = 768;
/**
* Desktop Breakpoint.
*
* @var int
*/
private static $default_lg = 992;
/**
* Large Desktop Breakpoint.
*
* @var int
*/
private static $default_xl = 1200;
/**
* Get Breakpoints.
*/
public static function get_breakpoints() {
$xs = self::get_breakpoint_xs();
$xs = ( ! empty( $xs ) && $xs ) ? $xs : self::$default_xs;
$sm = self::get_breakpoint_sm();
$sm = ( ! empty( $sm ) && $sm ) ? $sm : self::$default_sm;
$md = self::get_breakpoint_md();
$md = ( ! empty( $md ) && $md ) ? $md : self::$default_md;
$lg = self::get_breakpoint_lg();
$lg = ( ! empty( $lg ) && $lg ) ? $lg : self::$default_lg;
$xl = self::get_breakpoint_xl();
$xl = ( ! empty( $xl ) && $xl ) ? $xl : self::$default_xl;
return array(
$xs,
$sm,
$md,
$lg,
$xl,
);
}
/**
* Get default breakpoints.
*
* @return array
*/
public static function get_default_breakpoints() {
return array(
'xs' => 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() );
}
}

View File

@ -0,0 +1,356 @@
<?php
/**
* Controls
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_Controls
*/
class Visual_Portfolio_Controls {
/**
* Registered user categories to print it in the future.
*
* @var array
*/
private static $registered_categories = array();
/**
* Registered user fields to print it in the future.
*
* @var array
*/
private static $registered_fields = array();
/**
* Cached all registered controls.
*
* @var array
*/
private static $cached_all_registered_controls = array();
/**
* Cached saved layout meta.
*
* @var array
*/
private static $cached_saved_layout_meta = array();
/**
* Default control args.
*
* @var array
*/
private static $default_args = array(
// category for registered fields.
'category' => '',
'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( '&gt;', '>', $result );
}
return $result;
}
}
new Visual_Portfolio_Controls();

View File

@ -0,0 +1,419 @@
<?php
/**
* Add custom meta data to posts.
*
* @package visual-portfolio/admin
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_Custom_Post_Meta
*/
class Visual_Portfolio_Custom_Post_Meta {
/**
* Visual_Portfolio_Custom_Post_Meta constructor.
*/
public static function init() {
// add post formats.
add_action( 'after_setup_theme', array( __CLASS__, 'add_extra_post_format' ), 99 );
add_action( 'init', array( __CLASS__, 'register_post_meta' ) );
add_action( 'add_meta_boxes', array( __CLASS__, 'add_post_format_metaboxes' ), 1 );
add_action( 'save_post', array( __CLASS__, 'save_post_format_metaboxes' ) );
add_action( 'save_post', array( __CLASS__, 'update_words_count' ) );
add_action( 'wp_head', array( __CLASS__, 'update_views_count' ) );
}
/**
* Check if current page is gutenberg.
*
* @return boolean
*/
public static function is_gutenberg() {
$current_screen = get_current_screen();
if ( method_exists( $current_screen, 'is_block_editor' ) && $current_screen->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'];
}
}
?>
<p></p>
<input class="vp-input" name="_vp_format_video_url" type="url" id="_vp_format_video_url" value="<?php echo esc_attr( $video_url ); ?>" placeholder="<?php echo esc_attr__( 'https://', 'visual-portfolio' ); ?>">
<div class="vp-oembed-preview">
<?php
if ( $oembed_html ) {
echo wp_kses( $oembed_html, $wpkses_iframe );
}
?>
</div>
<style>
#vp_format_video {
display: <?php echo has_post_format( 'video' ) ? 'block' : 'none'; ?>;
}
</style>
<?php
}
/**
* Save Format metabox
*
* @param int $post_id The post ID.
*/
public static function save_post_format_metaboxes( $post_id ) {
if ( ! isset( $_POST['vp_format_video_nonce'] ) ) {
return;
}
if ( ! wp_verify_nonce( sanitize_key( $_POST['vp_format_video_nonce'] ), basename( __FILE__ ) ) ) {
return;
}
$meta = array(
'_vp_format_video_url',
);
foreach ( $meta as $item ) {
if ( isset( $_POST[ $item ] ) ) {
if ( is_array( $_POST[ $item ] ) ) {
$result = array_map( 'sanitize_text_field', wp_unslash( $_POST[ $item ] ) );
} else {
$result = sanitize_text_field( wp_unslash( $_POST[ $item ] ) );
}
update_post_meta( $post_id, $item, $result );
// remove old video meta.
if ( '_vp_format_video_url' === $item && get_post_meta( $post_id, 'video_url', true ) ) {
delete_post_meta( $post_id, 'video_url' );
}
} else {
update_post_meta( $post_id, $item, false );
}
}
}
/**
* Get video format URL.
*
* @param int $post_id The post ID.
*/
public static function get_video_format_url( $post_id ) {
if ( ! $post_id ) {
$post_id = get_the_ID();
}
$video_url = get_post_meta( $post_id, '_vp_format_video_url', true );
// fallback.
if ( ! $video_url ) {
$video_url = get_post_meta( $post_id, 'video_url', true );
}
return $video_url;
}
/**
* Get featured image focal point.
*
* @param int $post_id The post ID.
*/
public static function get_featured_image_focal_point( $post_id ) {
if ( ! $post_id ) {
$post_id = get_the_ID();
}
$focal_point = get_post_meta( $post_id, '_vp_image_focal_point', true );
if (
! isset( $focal_point ) ||
empty( $focal_point ) ||
! isset( $focal_point['x'] ) || ! isset( $focal_point['y'] ) ||
( '0.5' === $focal_point['x'] && '0.5' === $focal_point['y'] )
) {
return null;
}
return $focal_point;
}
/**
* Update views count.
*
* @param int $post_id The post ID.
*/
public static function update_views_count( $post_id ) {
if ( ! is_single() ) {
return;
}
if ( ! $post_id ) {
$post_id = get_the_ID();
}
$current_views = self::get_views_count( $post_id );
update_post_meta( $post_id, '_vp_views_count', $current_views + 1 );
// Support for https://wordpress.org/plugins/post-views-counter/ .
if ( function_exists( 'pvc_view_post' ) ) {
pvc_view_post( $post_id );
}
}
/**
* Get views count.
*
* @param int $post_id The post ID.
*/
public static function get_views_count( $post_id ) {
if ( ! $post_id ) {
$post_id = get_the_ID();
}
// Support for https://wordpress.org/plugins/post-views-counter/ .
if ( function_exists( 'pvc_get_post_views' ) ) {
return pvc_get_post_views( $post_id );
}
$current_views = get_post_meta( $post_id, '_vp_views_count', true );
if ( ! $current_views ) {
$current_views = 0;
}
return $current_views;
}
/**
* Update reading time.
*
* @param int $post_id The post ID.
*/
public static function update_words_count( $post_id ) {
if ( ! $post_id ) {
$post_id = get_the_ID();
}
$current_reading_time = self::calculate_words_count( $post_id );
update_post_meta( $post_id, '_vp_words_count', $current_reading_time );
}
/**
* Get reading time.
*
* Read time is based on the average reading speed of an adult (roughly 265 WPM).
* We take the total word count of a post and translate it into minutes.
* For posts in Chinese, Japanese and Korean, it's a function of
* number of characters (500 characters/min).
*
* @thanks https://help.medium.com/hc/en-us/articles/214991667-Read-time
*
* @param int $post_id The post ID.
*/
public static function get_reading_time( $post_id ) {
if ( ! $post_id ) {
$post_id = get_the_ID();
}
$post_words_count = get_post_meta( $post_id, '_vp_words_count', true );
if ( ! $post_words_count ) {
$post_words_count = self::calculate_words_count( $post_id );
}
$locale = get_locale();
switch ( $locale ) {
// zh_CN - Chinese (China)
// zh_HK - Chinese (Hong Kong SAR China)
// zh_SG - Chinese (Singapore)
// zh_TW - Chinese (Taiwan)
// ja_JP - Japanese (Japan)
// ko_KR - Korean (South Korea).
case 'zh_CN':
case 'zh_HK':
case 'zh_SG':
case 'zh_TW':
case 'ja_JP':
case 'ko_KR':
$reading_time = $post_words_count / 500;
break;
default:
$reading_time = $post_words_count / 265;
break;
}
// When reading time is 0, return it as `< 1` instead of `0`.
if ( 1 > $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();

View File

@ -0,0 +1,869 @@
<?php
/**
* Register Custom Post Types.
*
* @package visual-portfolio/admin
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_Custom_Post_Type
*/
class Visual_Portfolio_Custom_Post_Type {
/**
* Option of register portfolio post type.
*
* @deprecated deprecated since version 2.17.1
* @var string
*/
public static $register_portfolio_post_type = true;
/**
* Menu slug.
*
* @deprecated deprecated since version 2.17.1
* @var string
*/
public static $menu_slug = 'edit.php?post_type=portfolio';
/**
* Visual_Portfolio_Custom_Post_Type constructor.
*/
public function __construct() {
// custom post types.
// We have to use priority < 10 to add compatibility for 'post-terms' block.
// @link https://github.com/WordPress/gutenberg/blob/7880f08f57ac9275c21129d1f7e1f56af40bea87/packages/block-library/src/post-terms/index.php#L61-L110.
add_action( 'init', array( $this, 'add_custom_post_type' ), 9 );
add_action( 'restrict_manage_posts', array( $this, 'filter_custom_post_by_taxonomies' ), 10 );
// custom post roles.
add_action( 'init', array( $this, 'add_role_caps' ) );
// remove screen options from portfolio list page.
add_action( 'screen_options_show_screen', array( $this, 'remove_screen_options' ), 10, 2 );
// show thumbnail in portfolio list table.
add_filter( 'manage_portfolio_posts_columns', array( $this, 'add_portfolio_img_column' ) );
add_filter( 'manage_portfolio_posts_custom_column', array( $this, 'manage_portfolio_img_column' ), 10, 2 );
// show notice in vp_lists admin list page.
add_filter( 'admin_notices', array( $this, 'add_vp_lists_notice' ) );
// show icon and shortcode columns in vp_lists table.
add_filter( 'manage_vp_lists_posts_columns', array( $this, 'add_vp_lists_custom_columns' ) );
add_action( 'manage_vp_lists_posts_custom_column', array( $this, 'manage_vp_lists_custom_columns' ), 10, 2 );
add_action( 'restrict_manage_posts', array( $this, 'restrict_manage_posts_vp_lists' ) );
add_action( 'parse_query', array( $this, 'parse_query_vp_lists' ) );
// change allowed blocks for vp_lists post type.
add_filter( 'allowed_block_types_all', array( $this, 'vp_lists_allowed_block_types_all' ), 10, 2 );
// force enable Gutenberg editor in 'vp_lists' for Classic Editor plugin.
add_action( 'classic_editor_enabled_editors_for_post_type', array( $this, 'vp_lists_classic_plugin_force_gutenberg' ), 150, 2 );
add_action( 'use_block_editor_for_post_type', array( $this, 'vp_lists_classic_plugin_force_gutenberg_2' ), 150, 2 );
add_action( 'use_block_editor_for_post', array( $this, 'vp_lists_classic_plugin_force_gutenberg_3' ), 150, 2 );
// force enable Gutenberg in 'vp_lists' for users with disabled option "Visual Editor".
add_filter( 'user_can_richedit', array( $this, 'vp_lists_user_can_richedit_force' ) );
// highlight admin menu items.
add_action( 'admin_menu', array( $this, 'add_proofing_admin_menu' ), 10 );
add_action( 'admin_menu', array( $this, 'admin_menu' ), 12 );
// show admin menu dropdown with available portfolios on the current page.
add_action( 'wp_before_admin_bar_render', array( $this, 'wp_before_admin_bar_render' ) );
// add backward compatibility with old Pro version.
add_action( 'init', array( __CLASS__, 'set_settings_of_register_portfolio_post_type' ) );
}
/**
* Set settings of register portfolio post type.
*
* @deprecated deprecated since version 2.17.1
* @return void
*/
public static function set_settings_of_register_portfolio_post_type() {
self::$register_portfolio_post_type = Visual_Portfolio_Settings::get_option( 'register_portfolio_post_type', 'vp_general' );
self::$menu_slug = self::$register_portfolio_post_type ? 'edit.php?post_type=portfolio' : 'visual-portfolio-settings';
}
/**
* Get menu slug.
*
* @return string
*/
public static function get_menu_slug() {
return self::portfolio_post_type_is_registered() ? 'edit.php?post_type=portfolio' : 'visual-portfolio-settings';
}
/**
* Return true if portfolio post type is registered.
*
* @return bool
*/
public static function portfolio_post_type_is_registered() {
return Visual_Portfolio_Settings::get_option( 'register_portfolio_post_type', 'vp_general' );
}
/**
* Add custom post type
*/
public function add_custom_post_type() {
$archive_page = Visual_Portfolio_Settings::get_option( 'portfolio_archive_page', 'vp_general' );
$custom_slug = (int) get_option( 'page_on_front' ) === (int) $archive_page ? '/' : Visual_Portfolio_Archive_Mapping::get_portfolio_slug();
$custom_label = Visual_Portfolio_Archive_Mapping::get_portfolio_label();
$permalinks = Visual_Portfolio_Archive_Mapping::get_permalink_structure( true );
// portfolio post type / project post type.
if ( self::portfolio_post_type_is_registered() ) {
register_post_type(
'portfolio',
array(
'labels' => 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 '<select name="' . esc_attr( $taxonomy_slug ) . '" id="' . esc_attr( $taxonomy_slug ) . '" class="postform">';
// translators: %s - taxonomy name.
echo '<option value="">' . sprintf( esc_html__( 'Show All %s', 'visual-portfolio' ), esc_html( $taxonomy_name ) ) . '</option>';
foreach ( $terms as $term ) {
printf(
'<option value="%1$s" %2$s>%3$s (%4$s)</option>',
esc_attr( $term->slug ),
// phpcs:ignore WordPress.Security.NonceVerification
isset( $_GET[ $taxonomy_slug ] ) && $_GET[ $taxonomy_slug ] === $term->slug ? ' selected="selected"' : '',
esc_html( $term->name ),
esc_html( $term->count )
);
}
echo '</select>';
}
}
/**
* 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 '<a href="' . esc_url( get_edit_post_link() ) . '" class="vp-portfolio__thumbnail">';
if ( has_post_thumbnail() ) {
the_post_thumbnail( 'thumbnail' );
}
echo '</a>';
}
}
/**
* 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;
}
?>
<div class="notice notice-info vpf-admin-notice">
<div class="vpf-admin-notice-icon">
<i class="dashicons-visual-portfolio"></i>
</div>
<div class="vpf-admin-notice-content">
<h3>
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
<?php echo esc_html__( 'When to use Saved Layouts', 'visual-portfolio' ); ?>
</h3>
<p>
<?php
echo wp_kses_post(
sprintf(
// translators: %1$s - url to documentation.
// translators: %2$s - plugin name.
__( 'If you are using the Gutenberg page builder for your pages and posts, you should <strong>avoid using Saved Layouts</strong>. See here more info about <a href="%1$s" target="_blank">%2$s Blocks</a>.', 'visual-portfolio' ),
'https://visualportfolio.co/docs/portfolio-blocks/',
visual_portfolio()->plugin_name
)
);
?>
</p>
<p>
<?php
// translators: %s - url to documentation.
echo wp_kses_post( sprintf( __( 'To reuse blocks, you can use the built-in Gutenberg feature - <a href="%s" target="_blank">Reusable Blocks</a>.', 'visual-portfolio' ), 'https://www.wpbeginner.com/beginners-guide/how-to-create-a-reusable-block-in-wordpress/' ) );
?>
</p>
<p>
<?php
// translators: %s - url to documentation.
echo wp_kses_post( sprintf( __( 'Saved Layouts may be only used for 3rd-party builders (such as Elementor, WPBakery Page Builder, etc.), <a href="%s" target="_blank">read more info in documentation</a>. Since WordPress moved from Shortcodes to Blocks system, we prepared for you advanced blocks.', 'visual-portfolio' ), 'https://visualportfolio.co/docs/saved-layouts-and-shortcodes/' ) );
?>
</p>
</div>
</div>
<?php
}
/**
* Add icons and shortcode columns in vp_lists admin.
*
* @param array $columns columns of the table.
*
* @return array
*/
public function add_vp_lists_custom_columns( $columns = array() ) {
// Icon column.
$column_icon = array(
'vp_lists_post_icon' => 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 '<a href="' . esc_url( get_edit_post_link() ) . '" class="vp-portfolio-list__icon">';
echo wp_kses( $icon, 'vp_svg' );
echo '</a>';
}
}
if ( 'vp_lists_post_shortcode' === $column_name ) {
echo '<code class="vp-onclick-selection" role="button" tabIndex="0" aria-hidden="true">';
echo '[visual_portfolio id="' . get_the_ID() . '"]';
echo '</code>';
}
}
/**
* 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'] ) ) : '';
?>
<select name="vp_layout" id="filter-by-vp_layout">
<option value="0"><?php echo esc_html__( 'All layouts', 'visual-portfolio' ); ?></option>
<?php
foreach ( $all_layouts as $name => $data ) {
?>
<option value="<?php echo esc_attr( $name ); ?>" <?php echo $name === $selected_layout ? 'selected="selected"' : ''; ?>><?php echo esc_html( $data['title'] ); ?></option>
<?php
}
?>
</select>
<select name="vp_items_style" id="filter-by-vp_items_style">
<option value="0"><?php echo esc_html__( 'All styles', 'visual-portfolio' ); ?></option>
<?php
foreach ( $all_items_styles as $name => $data ) {
?>
<option value="<?php echo esc_attr( $name ); ?>" <?php echo $name === $selected_items_style ? 'selected="selected"' : ''; ?>><?php echo esc_html( $data['title'] ); ?></option>
<?php
}
?>
</select>
<select name="vp_content_source" id="filter-by-vp_content_source">
<option value="0"><?php echo esc_html__( 'All sources', 'visual-portfolio' ); ?></option>
<?php
foreach ( $all_content_sources as $name => $title ) {
?>
<option value="<?php echo esc_attr( $name ); ?>" <?php echo $name === $selected_content_source ? 'selected="selected"' : ''; ?>><?php echo esc_html( $title ); ?></option>
<?php
}
?>
</select>
<?php
};
}
/**
* Custom filtering for vp_lists admin screen.
*
* @param object $query - current query data.
*/
public function parse_query_vp_lists( $query ) {
global $pagenow;
$q_vars = &$query->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',
)
);
?>
<table class="form-table" role="presentation">
<tbody>
<tr class="pro_info vpf-setting-type-html">
<td>
<div class="vpf-pro-note vpf-settings-info-pro">
<h3>
<?php echo esc_html__( 'Premium Only', 'visual-portfolio' ); ?>
</h3>
<div>
<p class="vpf-pro-note-description"><?php echo esc_html__( 'Send a collection of photographs to your client for approval.', 'visual-portfolio' ); ?></p>
<a class="vpf-pro-note-button" target="_blank" rel="noopener noreferrer" href="<?php echo esc_url( $pro_url ); ?>">
<?php echo esc_html__( 'Go Pro', 'visual-portfolio' ); ?>
</a>
</div>
</div>
</td>
</tr>
</tbody>
</table>
<?php
}
}
}
new Visual_Portfolio_Custom_Post_Type();

View File

@ -0,0 +1,84 @@
<?php
/**
* Checks if another version of Visual Portfolio/Visual Portfolio Pro is active and deactivates it.
*
* @package visual-portfolio/deactivate-duplicate-plugin
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_Deactivate_Duplicate_Plugin
*/
class Visual_Portfolio_Deactivate_Duplicate_Plugin {
/**
* Visual_Portfolio_Deactivate_Duplicate_Plugin constructor.
*/
public function __construct() {
add_action( 'activated_plugin', array( $this, 'deactivate_other_instances' ) );
add_action( 'pre_current_active_plugins', array( $this, 'plugin_deactivated_notice' ) );
}
/**
* Checks if another version of Visual Portfolio/Visual Portfolio Pro is active and deactivates it.
* Hooked on `activated_plugin` so other plugin is deactivated when current plugin is activated.
*
* @param string $plugin The plugin being activated.
*/
public function deactivate_other_instances( $plugin ) {
if ( ! in_array( $plugin, array( 'visual-portfolio/class-visual-portfolio.php', 'visual-portfolio-pro/class-visual-portfolio-pro.php' ), true ) ) {
return;
}
$plugin_to_deactivate = 'visual-portfolio/class-visual-portfolio.php';
$deactivated_notice_id = 1;
// If we just activated the free version, deactivate the Pro version.
if ( $plugin === $plugin_to_deactivate ) {
$plugin_to_deactivate = 'visual-portfolio-pro/class-visual-portfolio-pro.php';
$deactivated_notice_id = 2;
}
if ( is_multisite() && is_network_admin() ) {
$active_plugins = (array) get_site_option( 'active_sitewide_plugins', array() );
$active_plugins = array_keys( $active_plugins );
} else {
$active_plugins = (array) get_option( 'active_plugins', array() );
}
foreach ( $active_plugins as $plugin_basename ) {
if ( $plugin_to_deactivate === $plugin_basename ) {
set_transient( 'vp_deactivated_notice_id', $deactivated_notice_id, 1 * HOUR_IN_SECONDS );
deactivate_plugins( $plugin_basename );
return;
}
}
}
/**
* Displays a notice when either Visual Portfolio or Visual Portfolio Pro is automatically deactivated.
*/
public function plugin_deactivated_notice() {
$deactivated_notice_id = (int) get_transient( 'vp_deactivated_notice_id' );
if ( ! in_array( $deactivated_notice_id, array( 1, 2 ), true ) ) {
return;
}
$message = __( "Visual Portfolio and Visual Portfolio Pro should not be active at the same time. We've automatically deactivated Visual Portfolio.", 'visual-portfolio' );
if ( 2 === $deactivated_notice_id ) {
$message = __( "Visual Portfolio and Visual Portfolio Pro should not be active at the same time. We've automatically deactivated Visual Portfolio Pro.", 'visual-portfolio' );
}
?>
<div class="notice notice-warning">
<p><?php echo esc_html( $message ); ?></p>
</div>
<?php
delete_transient( 'vp_deactivated_notice_id' );
}
}
new Visual_Portfolio_Deactivate_Duplicate_Plugin();

View File

@ -0,0 +1,418 @@
<?php
/**
* Deprecations.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_Deprecations
*/
class Visual_Portfolio_Deprecations {
/**
* The list of all deprecated hooks.
*
* @var array
*/
public $hooks = array();
/**
* Visual_Portfolio_Deprecations constructor.
*/
public function __construct() {
// Deprecated filters since v2.9.0.
$this->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();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,121 @@
<?php
/**
* Gutenberg block.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_Gutenberg_Saved_Block
*/
class Visual_Portfolio_Gutenberg_Saved_Block {
/**
* Registered controls, that will be used in Gutenberg block.
*
* @var array
*/
public $registered_controls = array();
/**
* Visual_Portfolio_Gutenberg_Saved_Block constructor.
*/
public function __construct() {
add_action( 'init', array( $this, 'register_block' ), 11 );
add_action( 'admin_init', array( $this, 'register_block_layouts_editor' ), 11 );
}
/**
* Register Block.
*/
public function register_block() {
if ( ! function_exists( 'register_block_type_from_metadata' ) ) {
return;
}
register_block_type_from_metadata(
visual_portfolio()->plugin_path . 'gutenberg/block-saved',
array(
'render_callback' => array( $this, 'block_render' ),
)
);
// Fallback.
register_block_type_from_metadata(
'nk/visual-portfolio',
array(
'render_callback' => array( $this, 'block_render' ),
'attributes' => array(
'id' => array(
'type' => 'string',
),
'align' => array(
'type' => 'string',
),
'className' => array(
'type' => 'string',
),
'anchor' => array(
'type' => 'string',
),
),
)
);
}
/**
* Register Block for Layouts Editor.
*/
public function register_block_layouts_editor() {
global $pagenow;
if (
'post.php' === $pagenow && isset( $_GET['post'] ) && 'vp_lists' === get_post_type( $_GET['post'] ) ||
'post-new.php' === $pagenow && isset( $_GET['post_type'] ) && 'vp_lists' === $_GET['post_type']
) {
register_block_type_from_metadata(
visual_portfolio()->plugin_path . 'gutenberg/layouts-editor/block'
);
}
}
/**
* Block output
*
* @param array $attributes - block attributes.
*
* @return string
*/
public function block_render( $attributes ) {
$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';
$wrapper_attributes = get_block_wrapper_attributes(
array(
'class' => $class_name,
)
);
return sprintf( '<div %1$s>%2$s</div>', $wrapper_attributes, Visual_Portfolio_Get::get( array( 'id' => $attributes['id'] ) ) );
}
}
new Visual_Portfolio_Gutenberg_Saved_Block();

View File

@ -0,0 +1,254 @@
<?php
/**
* Gutenberg block.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_Gutenberg_Block
*/
class Visual_Portfolio_Gutenberg_Block {
/**
* Cached block attributes, we will use it when register block in PHP and in JS.
*
* @var array
*/
public $cached_attributes = array();
/**
* Registered controls, that will be used in Gutenberg block.
*
* @var array
*/
public $registered_controls = array();
/**
* Visual_Portfolio_Gutenberg_Block constructor.
*/
public function __construct() {
add_action( 'init', array( $this, 'register_block' ), 11 );
add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_editor_assets' ) );
}
/**
* Get block attributes.
*
* @return array
*/
public function get_block_attributes() {
if ( ! empty( $this->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_from_metadata' ) ) {
return;
}
$attributes = $this->get_block_attributes();
register_block_type_from_metadata(
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';
$wrapper_attributes = get_block_wrapper_attributes(
array(
'class' => $class_name,
)
);
return sprintf( '<div %1$s>%2$s</div>', $wrapper_attributes, Visual_Portfolio_Get::get( $attributes ) );
}
/**
* Enqueue script for Gutenberg editor
*/
public function enqueue_block_editor_assets() {
if ( ! is_admin() ) {
return;
}
$attributes = $this->get_block_attributes();
// Block.
Visual_Portfolio_Assets::enqueue_script(
'visual-portfolio-gutenberg',
'build/gutenberg/index',
array( 'masonry' )
);
Visual_Portfolio_Assets::enqueue_style(
'visual-portfolio-gutenberg',
'build/gutenberg/index'
);
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'
);
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();

View File

@ -0,0 +1,93 @@
<?php
/**
* Image placeholder.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_Image_Placeholder
*/
class Visual_Portfolio_Image_Placeholder {
/**
* Visual_Portfolio_Image_Placeholder constructor.
*/
public function __construct() {
if ( is_admin() ) {
add_action( 'admin_init', array( $this, 'create_placeholder_image' ), 3 );
} else {
add_action( 'wp', array( $this, 'create_placeholder_image' ), 3 );
}
}
/**
* Create a placeholder image in the media library.
* For code thanks to WooCommerce.
*/
public function create_placeholder_image() {
// Run only on first plugin install.
// This option added in Migration class.
if ( get_option( 'vpf_db_version' ) ) {
return;
}
$general_settings = get_option( 'vp_general' );
if ( ! is_array( $general_settings ) ) {
$general_settings = array();
}
// Don't run when already added placeholder.
if ( isset( $general_settings['no_image'] ) && $general_settings['no_image'] ) {
return;
}
// Upload placeholder image to Media Library.
$upload_dir = wp_upload_dir();
$source = visual_portfolio()->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();

View File

@ -0,0 +1,641 @@
<?php
/**
* Prepare placeholder and lazy load.
*
* @package visual-portfolio/images
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_Images
*/
class Visual_Portfolio_Images {
/**
* When image process in progress with method get_attachment_image, this variable will be 'true'.
*
* @var bool
*/
public static $image_processing = false;
/**
* Allow Visual Portfolio images to use lazyload.
*
* @var bool
*/
public static $allow_vp_lazyload = false;
/**
* Allow WordPress images to use lazyload.
*
* @var bool
*/
public static $allow_wp_lazyload = false;
/**
* The list of exclusions from the plugin settings.
*
* @var array
*/
public static $lazyload_user_exclusions = array();
/**
* Visual_Portfolio_Images constructor.
*/
public static function construct() {
add_action( 'wp', 'Visual_Portfolio_Images::init_lazyload' );
add_action( 'after_setup_theme', 'Visual_Portfolio_Images::add_image_sizes' );
add_filter( 'image_size_names_choose', 'Visual_Portfolio_Images::image_size_names_choose' );
/**
* Allow `data:` inside image src attribute.
* Don't place this hook inside the `wp` hook.
*
* @link https://wordpress.org/support/topic/lazy-load-404-error-with-image-like-png/#post-16439422
*/
add_filter( 'kses_allowed_protocols', 'Visual_Portfolio_Images::kses_allowed_protocols', 15 );
}
/**
* Add image sizes.
*/
public static function add_image_sizes() {
$sm = Visual_Portfolio_Settings::get_option( 'sm', 'vp_images' );
$md = Visual_Portfolio_Settings::get_option( 'md', 'vp_images' );
$lg = Visual_Portfolio_Settings::get_option( 'lg', 'vp_images' );
$xl = Visual_Portfolio_Settings::get_option( 'xl', 'vp_images' );
$sm_popup = Visual_Portfolio_Settings::get_option( 'sm_popup', 'vp_images' );
$md_popup = Visual_Portfolio_Settings::get_option( 'md_popup', 'vp_images' );
$xl_popup = Visual_Portfolio_Settings::get_option( 'xl_popup', 'vp_images' );
// custom image sizes.
add_image_size( 'vp_sm', $sm, 9999 );
add_image_size( 'vp_md', $md, 9999 );
add_image_size( 'vp_lg', $lg, 9999 );
add_image_size( 'vp_xl', $xl, 9999 );
add_image_size( 'vp_sm_popup', $sm_popup, 9999 );
add_image_size( 'vp_md_popup', $md_popup, 9999 );
add_image_size( 'vp_xl_popup', $xl_popup, 9999 );
}
/**
* Custom image sizes
*
* @param array $sizes - registered image sizes.
*
* @return array
*/
public static function image_size_names_choose( $sizes ) {
return array_merge(
$sizes,
array(
'vp_sm' => 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 <img> tags
// 2. Exclude tags, placed inside <noscript>.
$content = preg_replace_callback( '#(?<!noscript\>)<(img)([^>]+?)(>(.*?)</\\1>|[\/]?>)#si', 'Visual_Portfolio_Images::process_image', $content );
return $content;
}
/**
* Returns true when a given array of attributes contains attributes or class signifying lazy images.
* should not process the image.
*
* @param array $attributes all available image attributes.
*
* @return bool
*/
public static function should_skip_image_with_blocked_attributes( $attributes ) {
// Check for blocked classes.
if ( ! empty( $attributes['class'] ) ) {
$blocked_classes = array(
'lazy',
'lazyload',
'lazy-load',
'skip-lazy',
'no-lazy',
'gazette-featured-content-thumbnail',
);
/**
* Allow plugins and themes to tell lazy images to skip an image with a given class.
*/
$blocked_classes = apply_filters( 'vpf_lazyload_images_blocked_classes', $blocked_classes );
if ( is_array( $blocked_classes ) && ! empty( $blocked_classes ) ) {
foreach ( $blocked_classes as $class ) {
if ( false !== strpos( $attributes['class'], $class ) ) {
return true;
}
}
}
}
// Check for blocked src.
if ( ! empty( $attributes['src'] ) ) {
$blocked_src = array(
'/wpcf7_captcha/',
'timthumb.php?src',
);
/**
* Allow plugins and themes to tell lazy images to skip an image with a given class.
*/
$blocked_src = apply_filters( 'vpf_lazyload_images_blocked_src', $blocked_src );
if ( is_array( $blocked_src ) && ! empty( $blocked_src ) ) {
foreach ( $blocked_src as $src ) {
if ( false !== strpos( $attributes['src'], $src ) ) {
return true;
}
}
}
}
$blocked_attributes = self::get_image_blocked_attributes();
foreach ( $blocked_attributes as $attr ) {
if ( isset( $attributes[ $attr ] ) ) {
return true;
}
}
/**
* Allow plugins and themes to conditionally skip processing an image via its attributes.
*/
if ( apply_filters( 'vpf_lazyload_skip_image_with_attributes', false, $attributes ) ) {
return true;
}
return false;
}
/**
* Skip lazy loading using exclusion settings.
*
* @param boolean $return - default return value.
* @param array $attributes - image attributes.
*
* @return boolean
*/
public static function add_lazyload_exclusions( $return, $attributes ) {
if ( ! empty( self::$lazyload_user_exclusions ) && ! empty( $attributes ) ) {
$full_attributes_string = '';
foreach ( $attributes as $k => $attr ) {
$full_attributes_string .= ' ' . $k . '="' . $attr . '"';
}
foreach ( self::$lazyload_user_exclusions as $exclusion ) {
if ( $exclusion && false !== strpos( $full_attributes_string, $exclusion ) ) {
// `true` means - exclude this image from lazy loading.
return true;
}
}
}
return $return;
}
/**
* Processes images in content by acting as the preg_replace_callback.
*
* @param array $matches Matches.
*
* @return string The image with updated lazy attributes.
*/
public static function process_image( $matches ) {
$old_attributes_str = $matches[2];
$old_attributes_kses_hair = wp_kses_hair( $old_attributes_str, wp_allowed_protocols() );
$fallback = $matches[0];
if ( empty( $old_attributes_kses_hair['src'] ) ) {
return $fallback;
}
$old_attributes = self::flatten_kses_hair_data( $old_attributes_kses_hair );
// Return original image if image is already lazy loaded.
if ( ! empty( $old_attributes['class'] ) && false !== strpos( $old_attributes['class'], 'vp-lazyload' ) ) {
return $fallback;
}
$new_attributes = self::process_image_attributes( $old_attributes );
// Return original image if new attributes does not contains the lazyload class.
if ( empty( $new_attributes['class'] ) || false === strpos( $new_attributes['class'], 'vp-lazyload' ) ) {
return $fallback;
}
// Skip 3rd-party lazy loading from noscript img tag.
$fallback = str_replace( ' src="', ' data-skip-lazy src="', $fallback );
$new_attributes_str = self::build_attributes_string( $new_attributes );
return sprintf( '<noscript>%1$s</noscript><img %2$s>', $fallback, $new_attributes_str );
}
/**
* Given an array of image attributes, updates the `src`, `srcset`, and `sizes` attributes so
* that they load lazily.
*
* @param array $attributes Attributes.
*
* @return array The updated image attributes array with lazy load attributes.
*/
public static function process_image_attributes( $attributes ) {
// Skip lazy load from VPF images if option disabled.
if ( ! self::$allow_vp_lazyload && self::$image_processing ) {
return $attributes;
}
// Skip lazy load from WordPress images if option disabled.
if ( ! self::$allow_wp_lazyload && ! self::$image_processing ) {
return $attributes;
}
if ( empty( $attributes['src'] ) ) {
return $attributes;
}
if ( self::should_skip_image_with_blocked_attributes( $attributes ) ) {
return $attributes;
}
// Default Placeholder.
$placeholder = false;
$placeholder_w = isset( $attributes['width'] ) ? $attributes['width'] : false;
$placeholder_h = isset( $attributes['height'] ) ? $attributes['height'] : false;
// Trying to get image size from metadata.
if ( ! $placeholder_w || ! $placeholder_h ) {
$image_id = self::attributes_to_image_id( $attributes );
$metadata = get_post_meta( $image_id, '_wp_attachment_metadata', true );
if ( isset( $metadata['width'] ) && isset( $metadata['height'] ) ) {
$placeholder_w = $metadata['width'];
$placeholder_h = $metadata['height'];
}
}
if ( $placeholder_w && $placeholder_h ) {
$placeholder = self::get_image_placeholder( $placeholder_w, $placeholder_h );
}
$attributes['data-src'] = $attributes['src'];
if ( ! empty( $attributes['srcset'] ) ) {
$attributes['data-srcset'] = $attributes['srcset'];
if ( $placeholder ) {
$attributes['srcset'] = $placeholder;
} else {
unset( $attributes['srcset'] );
}
// In case if the image doesn't have `srcset`, we need to add placeholder to `src`.
} elseif ( $placeholder ) {
$attributes['src'] = $placeholder;
}
if ( isset( $attributes['sizes'] ) ) {
unset( $attributes['sizes'] );
}
$attributes['data-sizes'] = 'auto';
// Prevent Native lazy loading.
$attributes['loading'] = 'eager';
// Add custom classname.
$attributes['class'] = empty( $attributes['class'] ) ? '' : ( $attributes['class'] . ' ' );
$attributes['class'] .= 'vp-lazyload';
/**
* Allow plugins and themes to override the attributes on the image before the content is updated.
*
* One potential use of this filter is for themes that set `height:auto` on the `img` tag.
* With this filter, the theme could get the width and height attributes from the
* $attributes array and then add a style tag that sets those values as well, which could
* minimize reflow as images load.
*/
return apply_filters( 'vpf_lazyload_images_new_attributes', $attributes );
}
/**
* Get attachment image wrapper.
*
* @param string|int $attachment_id attachment image id.
* @param string|array $size image size.
* @param bool $icon icon.
* @param string|array $attr image attributes.
* @param bool $lazyload DEPRECATED use lazyload tags.
*
* @return string
*/
public static function get_attachment_image( $attachment_id, $size = 'thumbnail', $icon = false, $attr = '', $lazyload = true ) {
$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';
}
self::$image_processing = true;
$image = apply_filters( 'vpf_wp_get_attachment_image', false, $attachment_id, $size, $attr, $lazyload );
if ( ! $image && false === strripos( $attachment_id, 'vpf_pro_social' ) ) {
if ( ! is_array( $attr ) ) {
$attr = array();
}
if ( ! isset( $attr['class'] ) ) {
$attr['class'] = '';
}
// Add class `wp-image-ID` to allow parsers to get current image ID and make manipulations.
// For example, this class is used in our lazyloading script to determine the image ID.
$attr['class'] .= ( $attr['class'] ? ' ' : '' ) . 'wp-image-' . $attachment_id;
$image = wp_get_attachment_image( $attachment_id, $size, $icon, $attr );
}
// Maybe prepare lazy load output.
if ( self::$allow_vp_lazyload ) {
$image = self::add_image_placeholders( $image );
}
self::$image_processing = false;
return $image;
}
/**
* Generation placeholder.
*
* @param int $width Width of image.
* @param int $height Height of image.
*
* @return string
*/
public static function get_image_placeholder( $width = 1, $height = 1 ) {
if ( ! (int) $width || ! (int) $height ) {
return false;
}
// We need to use base64 to prevent rare cases when users use plugins
// that replaces http to https in xmlns attribute.
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
$placeholder = base64_encode( '<svg width="' . $width . '" height="' . $height . '" viewBox="0 0 ' . $width . ' ' . $height . '" fill="none" xmlns="http://www.w3.org/2000/svg"></svg>' );
$escape_search = array( '<', '>', '#', '"' );
$escape_replace = array( '%3c', '%3e', '%23', '\'' );
return apply_filters(
'vpf_lazyload_image_placeholder',
'data:image/svg+xml;base64,' . str_replace( $escape_search, $escape_replace, $placeholder )
);
}
/**
* Flatter KSES hair data.
*
* @param array $attributes Attributes.
*
* @return array
*/
private static function flatten_kses_hair_data( $attributes ) {
$flattened_attributes = array();
foreach ( $attributes as $name => $attribute ) {
$flattened_attributes[ $name ] = $attribute['value'];
}
return $flattened_attributes;
}
/**
* Build attributes string.
*
* @param array $attributes Attributes.
*
* @return string
*/
public static function build_attributes_string( $attributes ) {
$string = array();
foreach ( $attributes as $name => $value ) {
if ( '' === $value ) {
$string[] = sprintf( '%s', $name );
} else {
$string[] = sprintf( '%s="%s"', $name, esc_attr( $value ) );
}
}
return implode( ' ', $string );
}
/**
* Tries to convert an attachment IMG attr into a post object.
*
* @param array $attributes image attributes.
*
* @return int|bool
*/
private static function attributes_to_image_id( $attributes ) {
$img_id = false;
// Get ID from class.
if ( isset( $attributes['class'] ) && preg_match( '/wp-image-(\d*)/i', $attributes['class'], $match ) ) {
$img_id = $match[1];
}
if ( ! $img_id && isset( $attributes['src'] ) ) {
// Remove the thumbnail size.
$src = preg_replace( '~-[0-9]+x[0-9]+(?=\..{2,6})~', '', $attributes['src'] );
$img_id = attachment_url_to_postid( $src );
// Sometimes, when the uploaded image larger than max-size, this image scaled and filename changed to `NAME-scaled.EXT`.
if ( ! $img_id ) {
$src = preg_replace( '~-[0-9]+x[0-9]+(?=\..{2,6})~', '-scaled', $attributes['src'] );
$img_id = attachment_url_to_postid( $src );
}
}
return $img_id;
}
/**
* Adds JavaScript to check if the current browser supports JavaScript as well as some styles to hide lazy
* images when the browser does not support JavaScript.
*/
public static function add_nojs_fallback() {
?>
<style type="text/css">
/* If html does not have either class, do not show lazy loaded images. */
html:not(.vp-lazyload-enabled):not(.js) .vp-lazyload {
display: none;
}
</style>
<script>
document.documentElement.classList.add(
'vp-lazyload-enabled'
);
</script>
<?php
}
}
Visual_Portfolio_Images::construct();

View File

@ -0,0 +1,354 @@
<?php
/**
* Migrations
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_Migrations
*/
class Visual_Portfolio_Migrations {
/**
* The version.
*
* @var string
*/
protected $version = VISUAL_PORTFOLIO_VERSION;
/**
* Initial version.
*
* @var string
*/
protected $initial_version = '1.16.2';
/**
* Visual_Portfolio_Migrations constructor.
*/
public function __construct() {
if ( is_admin() ) {
add_action( 'admin_init', array( $this, 'init' ), 3 );
} else {
add_action( 'wp', array( $this, 'init' ), 3 );
}
}
/**
* Init.
*/
public function init() {
// Migration code added after `$this->initial_version` plugin version.
$saved_version = get_option( 'vpf_db_version', $this->initial_version );
$current_version = $this->version;
foreach ( $this->get_migrations() as $migration ) {
if ( version_compare( $saved_version, $migration['version'], '<' ) ) {
call_user_func( $migration['cb'] );
}
}
if ( version_compare( $saved_version, $current_version, '<' ) ) {
update_option( 'vpf_db_version', $current_version );
}
}
/**
* Get all available migrations.
*
* @return array
*/
public function get_migrations() {
return array(
array(
'version' => '3.0.0',
'cb' => array( $this, 'v_3_0_0' ),
),
array(
'version' => '2.15.0',
'cb' => array( $this, 'v_2_15_0' ),
),
array(
'version' => '2.10.0',
'cb' => array( $this, 'v_2_10_0' ),
),
array(
'version' => '2.0.0',
'cb' => array( $this, 'v_2_0_0' ),
),
array(
'version' => '1.11.0',
'cb' => array( $this, 'v_1_11_0' ),
),
);
}
/**
* Add new attributes and values from old attributes.
*/
public function v_3_0_0() {
// Get all available Layouts.
// Don't use WP_Query on the admin side https://core.trac.wordpress.org/ticket/18408.
$layouts_query = get_posts(
array(
'post_type' => 'vp_lists',
'posts_per_page' => -1,
'paged' => -1,
)
);
$attributes_to_change = array(
// Align.
'items_style_default__align' => 'items_style_default__caption_text_align',
'items_style_fade__align' => 'items_style_fade__overlay_text_align',
'items_style_fly__align' => 'items_style_fly__overlay_text_align',
'items_style_emerge__align' => 'items_style_emerge__caption_text_align',
'items_style_caption_move__align' => 'items_style_caption_move__caption_text_align',
// Color.
'items_style_default__bg_color' => 'items_style_default__overlay_bg_color',
'items_style_default__text_color' => 'items_style_default__overlay_text_color',
'items_style_default__meta_text_color' => 'items_style_default__caption_text_color',
'items_style_default__meta_links_color' => 'items_style_default__caption_links_color',
'items_style_default__meta_links_hover_color' => 'items_style_default__caption_links_hover_color',
'items_style_fade__bg_color' => 'items_style_fade__overlay_bg_color',
'items_style_fade__text_color' => 'items_style_fade__overlay_text_color',
'items_style_fly__bg_color' => 'items_style_fly__overlay_bg_color',
'items_style_fly__text_color' => 'items_style_fly__overlay_text_color',
'items_style_emerge__bg_color' => 'items_style_emerge__caption_bg_color',
'items_style_emerge__text_color' => 'items_style_emerge__caption_text_color',
'items_style_emerge__links_color' => 'items_style_emerge__caption_links_color',
'items_style_emerge__links_hover_color' => 'items_style_emerge__caption_links_hover_color',
'items_style_emerge__img_overlay_bg_color' => 'items_style_emerge__overlay_bg_color',
'items_style_caption-move__bg_color' => 'items_style_caption-move__caption_bg_color',
'items_style_caption-move__text_color' => 'items_style_caption-move__caption_text_color',
'items_style_caption-move__img_overlay_bg_color' => 'items_style_caption-move__overlay_bg_color',
'items_style_caption-move__overlay_text_color' => 'items_style_caption-move__overlay_text_color',
// Move Under Image.
'items_style_fade__move_overlay_under_image' => 'items_style_fade__overlay_under_image',
'items_style_fly__move_overlay_under_image' => 'items_style_fly__overlay_under_image',
'items_style_emerge__move_overlay_under_image' => 'items_style_emerge__caption_under_image',
'items_style_caption-move__move_overlay_under_image' => 'items_style_caption-move__caption_under_image',
);
$attributes_border_radius = array(
'items_style_default__images_rounded_corners',
'items_style_fade__images_rounded_corners',
'items_style_fly__images_rounded_corners',
'items_style_emerge__images_rounded_corners',
'items_style_caption_move__images_rounded_corners',
);
if ( $layouts_query ) {
foreach ( $layouts_query as $post ) {
// Change Portfolio content source to Post with custom post type Portfolio.
if ( 'portfolio' === get_post_meta( $post->ID, 'vp_content_source', true ) ) {
update_post_meta( $post->ID, 'vp_content_source', 'post-based' );
update_post_meta( $post->ID, 'vp_posts_source', 'portfolio' );
}
foreach ( $attributes_to_change as $old_attr => $new_attr ) {
$old_val = get_post_meta( $post->ID, 'vp_' . $old_attr, true );
$new_val = get_post_meta( $post->ID, 'vp_' . $new_attr, true );
if ( $old_val && ! $new_val ) {
update_post_meta( $post->ID, 'vp_' . $new_attr, $old_val );
}
}
foreach ( $attributes_border_radius as $attr_name ) {
$attr_val = get_post_meta( $post->ID, 'vp_' . $attr_name, true );
if ( is_numeric( $attr_val ) ) {
update_post_meta( $post->ID, 'vp_' . $attr_name, $attr_val . 'px' );
}
}
}
wp_reset_postdata();
}
}
/**
* Check the old portfolio slug option and create a page with archive page based on it.
*/
public function v_2_15_0() {
// Backward compatible with old slug option.
$settings_section = 'vp_general';
$option_name = 'portfolio_slug';
$options = get_option( $settings_section );
if ( isset( $options[ $option_name ] ) ) {
$custom_slug = $options[ $option_name ];
$archive_id = get_option( '_vp_add_archive_page' );
if ( $archive_id ) {
// Update archive slug on page.
wp_update_post(
array(
'ID' => $archive_id,
'post_name' => wp_slash( $custom_slug ),
)
);
// Rewrite flush rules.
visual_portfolio()->defer_flush_rewrite_rules();
} else {
// Create archive page.
Visual_Portfolio_Archive_Mapping::create_archive_page( $custom_slug );
}
// Delete old option.
unset( $options[ $option_name ] );
update_option( $settings_section, $options );
}
}
/**
* Move popup title and description settings to single Layouts.
*/
public function v_2_10_0() {
$options = get_option( 'vp_images' );
if ( ! isset( $options['lazy_loading'] ) ) {
return;
}
if ( 'off' === $options['lazy_loading'] || ! $options['lazy_loading'] ) {
$options['lazy_loading'] = '';
} else {
$options['lazy_loading'] = 'vp';
}
update_option( 'vp_images', $options );
}
/**
* 1. Change Portfolio content source to Post with custom post type Portfolio
* 2. Change filters, sort and pagination to layout-elements.
*/
public function v_2_0_0() {
// Get all available Layouts.
// Don't use WP_Query on the admin side https://core.trac.wordpress.org/ticket/18408.
$layouts_query = get_posts(
array(
'post_type' => 'vp_lists',
'posts_per_page' => -1,
'paged' => -1,
)
);
if ( $layouts_query ) {
foreach ( $layouts_query as $post ) {
// Change Portfolio content source to Post with custom post type Portfolio.
if ( 'portfolio' === get_post_meta( $post->ID, 'vp_content_source', true ) ) {
update_post_meta( $post->ID, 'vp_content_source', 'post-based' );
update_post_meta( $post->ID, 'vp_posts_source', 'portfolio' );
}
// Change filters, sort and pagination to layout-elements.
if ( ! get_post_meta( $post->ID, 'vp_layout_elements', true ) ) {
$top = array();
$bottom = array();
$filter = get_post_meta( $post->ID, 'vp_filter', true );
$sort = get_post_meta( $post->ID, 'vp_sort', true );
$pagination = get_post_meta( $post->ID, 'vp_pagination_style', true );
// Filter.
if ( $filter && 'false' !== $filter && false !== $filter ) {
$top[] = 'filter';
} else {
update_post_meta( $post->ID, 'vp_filter', 'minimal' );
}
// Sort.
if ( $sort && 'false' !== $sort && false !== $sort ) {
$top[] = 'sort';
} else {
update_post_meta( $post->ID, 'vp_sort', 'dropdown' );
}
// Pagination.
if ( $pagination && 'false' !== $pagination && false !== $pagination ) {
$bottom[] = 'pagination';
} else {
update_post_meta( $post->ID, 'vp_pagination_style', 'minimal' );
}
// Layout Elements.
if ( ! empty( $top ) || ! empty( $bottom ) ) {
update_post_meta(
$post->ID,
'vp_layout_elements',
array(
'top' => array(
'elements' => $top,
'align' => 'center',
),
'items' => array(
'elements' => array( 'items' ),
),
'bottom' => array(
'elements' => $bottom,
'align' => 'center',
),
)
);
}
}
}
wp_reset_postdata();
}
}
/**
* Move popup title and description settings to single Layouts.
*/
public function v_1_11_0() {
$options = get_option( 'vp_popup_gallery' );
if ( ! isset( $options['show_caption'] ) && ! isset( $options['caption_title'] ) && ! isset( $options['caption_description'] ) ) {
return;
}
$new_show_caption = isset( $options['show_caption'] ) ? 'on' === $options['show_caption'] : true;
$new_title_source = $new_show_caption && isset( $options['caption_title'] ) ? $options['caption_title'] : 'none';
$new_description_source = $new_show_caption && isset( $options['caption_description'] ) ? $options['caption_description'] : 'none';
// Get all available Layouts.
// Don't use WP_Query on the admin side https://core.trac.wordpress.org/ticket/18408.
$layouts_query = get_posts(
array(
'post_type' => 'vp_lists',
'posts_per_page' => -1,
'paged' => -1,
)
);
if ( $layouts_query ) {
foreach ( $layouts_query as $post ) {
update_post_meta( $post->ID, 'vp_items_click_action_popup_title_source', $new_title_source );
update_post_meta( $post->ID, 'vp_items_click_action_popup_description_source', $new_description_source );
}
wp_reset_postdata();
}
// remove saved old options.
if ( isset( $options['show_caption'] ) ) {
unset( $options['show_caption'] );
}
if ( isset( $options['caption_title'] ) ) {
unset( $options['caption_title'] );
}
if ( isset( $options['caption_description'] ) ) {
unset( $options['caption_description'] );
}
update_option( 'vp_popup_gallery', $options );
}
}
new Visual_Portfolio_Migrations();

View File

@ -0,0 +1,180 @@
<?php
/**
* Parse blocks from content and widgets
*
* @package visual-portfolio
*/
/**
* Visual_Portfolio_Parse_Blocks
*/
class Visual_Portfolio_Parse_Blocks {
/**
* Array of content, that already parsed.
*
* @var array
*/
public static $parsed_content = array();
/**
* Array of reusable block IDs, that already parsed.
*
* @var array
*/
public static $parsed_reusable_blocks = array();
/**
* Init.
*/
public static function init() {
add_action(
'wp',
function() {
// Simple use `render_block` in FSE themes to enqueue assets.
if ( current_theme_supports( 'block-templates' ) ) {
// Parse all blocks.
add_action( 'render_block', 'Visual_Portfolio_Parse_Blocks::render_block', 11, 2 );
// Parse blocks manually from content and custom locations in Classic themes.
} else {
// parse blocks from post content.
Visual_Portfolio_Parse_Blocks::maybe_parse_blocks_from_content();
// parse blocks from custom locations, that uses 'the_content' filter.
add_filter( 'the_content', 'Visual_Portfolio_Parse_Blocks::maybe_parse_blocks_from_custom_location', 8 );
add_filter( 'widget_block_content', 'Visual_Portfolio_Parse_Blocks::maybe_parse_blocks_from_custom_location', 8 );
}
}
);
}
/**
* Standard callback to parse blocks (mostly solves problem with FSE themes and blocks inside templates).
*
* @param string $block_content - block content.
* @param array $block - block data.
*
* @return string
*/
public static function render_block( $block_content, $block ) {
// We don't need to parse inner blocks manually, because `render_block` filter will make it for us.
$block['innerBlocks'] = false;
self::parse_blocks( array( $block ), 'general' );
return $block_content;
}
/**
* Parse blocks from custom locations.
*
* @param string $content - custom content.
*/
public static function maybe_parse_blocks_from_custom_location( $content ) {
if ( is_admin() ) {
return $content;
}
if ( isset( $content ) ) {
self::maybe_parse_blocks( $content, 'content' );
}
return $content;
}
/**
* Maybe parse blocks.
*
* @param string $content - content.
* @param string $location - blocks location [content,widget].
*/
public static function maybe_parse_blocks( $content, $location = 'content' ) {
if (
isset( $content ) &&
function_exists( 'has_blocks' ) &&
function_exists( 'parse_blocks' ) &&
$content &&
has_blocks( $content )
) {
$is_parsed = false;
// check if this content is already parsed.
foreach ( self::$parsed_content as $parsed ) {
$is_parsed = $is_parsed || $parsed === $content;
}
if ( ! $is_parsed ) {
$blocks = parse_blocks( $content );
self::parse_blocks( $blocks, $location );
self::$parsed_content[] = $content;
}
}
}
/**
* Parse blocks from content.
*/
public static function maybe_parse_blocks_from_content() {
global $wp_query;
if ( is_admin() || ! isset( $wp_query->posts ) ) {
return;
}
// parse all posts content.
foreach ( $wp_query->posts as $post ) {
if (
isset( $post->post_content ) &&
function_exists( 'has_blocks' ) &&
function_exists( 'parse_blocks' ) &&
has_blocks( $post )
) {
$blocks = parse_blocks( $post->post_content );
self::parse_blocks( $blocks, 'content' );
}
}
}
/**
* Parse blocks including reusable and InnerBlocks and call action `vpf_parse_blocks`.
*
* @param array $blocks - blocks array.
* @param string $location - blocks location [content,widget].
* @param boolean $is_reusable - is from reusable block.
* @param boolean $is_inner_blocks - is from inner blocks.
*/
public static function parse_blocks( $blocks, $location = 'content', $is_reusable = false, $is_inner_blocks = false ) {
if ( ! is_array( $blocks ) || empty( $blocks ) ) {
return;
}
do_action( 'vpf_parse_blocks', $blocks, $location, $is_reusable, $is_inner_blocks );
foreach ( $blocks as $block ) {
// Reusable Blocks.
if ( isset( $block['blockName'] ) && 'core/block' === $block['blockName'] && isset( $block['attrs']['ref'] ) ) {
// Check if this reusable block already parsed.
// Fixes possible error with nested reusable blocks.
if ( ! in_array( $block['attrs']['ref'], self::$parsed_reusable_blocks, true ) ) {
self::$parsed_reusable_blocks[] = $block['attrs']['ref'];
$reusable_block = get_post( $block['attrs']['ref'] );
if ( has_blocks( $reusable_block ) && isset( $reusable_block->post_content ) ) {
$post_blocks = parse_blocks( $reusable_block->post_content );
self::parse_blocks( $post_blocks, $location, true );
}
}
}
// Inner blocks.
if ( isset( $block['innerBlocks'] ) ) {
self::parse_blocks( $block['innerBlocks'], $location, $is_reusable, true );
}
}
}
}
Visual_Portfolio_Parse_Blocks::init();

View File

@ -0,0 +1,324 @@
<?php
/**
* Register fake page for portfolio preview.
*
* @package visual-portfolio/preview
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_Preview
*/
class Visual_Portfolio_Preview {
/**
* Preview enabled.
*
* @var bool
*/
public $preview_enabled = false;
/**
* Visual_Portfolio_Preview constructor.
*/
public function __construct() {
$this->init_hooks();
}
/**
* Hooks.
*/
public function init_hooks() {
add_action( 'init', array( $this, 'is_preview_check' ) );
add_filter( 'pre_handle_404', array( $this, 'pre_handle_404' ) );
add_filter( 'vpf_get_options', array( $this, 'filter_preview_option' ) );
add_action( 'template_redirect', array( $this, 'template_redirect' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'wp_enqueue_scripts' ), 11 );
// Localize script in editor, FSE, and Elementor.
add_action( 'enqueue_block_assets', array( $this, 'localize_scripts' ) );
add_action( 'wp_print_scripts', array( $this, 'localize_elementor_scripts' ), 9 );
}
/**
* Prepare preview URL to use from our preview iframe.
*
* @return string
*/
public function get_preview_url() {
add_filter( 'pre_option_permalink_structure', '__return_empty_string' );
$preview_url = set_url_scheme(
add_query_arg(
array(
'vp_preview' => 'vp_preview',
'vp_preview_time' => time(),
),
// We used `get_site_url()` before, but it does not work on some hosts,
// that have custom URL structure in admin area.
//
// For example:
// * Admin URL: https://mysite.com/wp/
// * Front URL: https://mysite.com/
//
// Use `trailingslashit` to fix some rare servers problems
// when preview is not working if no trailing slash in URL.
//
// For example:
// * Error: https://mysite.com/wp
// * Success: https://mysite.com/wp/
// .
trailingslashit( home_url( '/' ) )
)
);
remove_filter( 'pre_option_permalink_structure', '__return_empty_string' );
return $preview_url;
}
/**
* Localize scripts with preview URL.
*/
public function localize_scripts() {
if ( ! is_admin() ) {
return;
}
$preview_url = $this->get_preview_url();
// Localize scripts.
wp_localize_script(
'visual-portfolio-gutenberg',
'VPAdminGutenbergVariables',
array(
'preview_url' => $preview_url,
'nonce' => wp_create_nonce( 'vp-ajax-nonce' ),
)
);
}
/**
* Localize scripts with preview URL for Elementor.
*/
public function localize_elementor_scripts() {
if ( ! wp_script_is( 'visual-portfolio-elementor', 'registered' ) ) {
return;
}
$preview_url = $this->get_preview_url();
// Localize scripts.
wp_localize_script(
'visual-portfolio-elementor',
'VPAdminElementorVariables',
array(
'preview_url' => $preview_url,
'nonce' => wp_create_nonce( 'vp-ajax-nonce' ),
)
);
}
/**
* Check if the page is preview.
*/
public function is_preview_check() {
$frame = false;
if ( isset( $_GET['vp_preview'] ) && isset( $_REQUEST['vp_preview_nonce'] ) && wp_verify_nonce( sanitize_key( $_REQUEST['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;
}
}
$this->preview_enabled = $frame;
}
/**
* Prevent 404 headers if it is vp_preview page.
*
* @param bool $val - handle 404 headers.
*
* @return bool
*/
public function pre_handle_404( $val ) {
return $this->preview_enabled ? true : $val;
}
/**
* Disable infinite loading in preview.
*
* @param array $options - options.
*
* @return array
*/
public function filter_preview_option( $options ) {
if ( $this->preview_enabled && isset( $options['pagination'] ) && 'infinite' === $options['pagination'] ) {
$options['pagination'] = 'load-more';
}
return $options;
}
/**
* Display preview frame
* Available by requesting:
* SITE/?vp_preview=vp_preview with POST data: `vp_preview_frame=true&vp_preview_frame_id=10`
*/
public function template_redirect() {
if ( is_admin() || ! $this->preview_enabled ) {
return;
}
$this->print_template();
exit;
}
/**
* Enqueue scripts action.
*/
public function wp_enqueue_scripts() {
// Dequeue WooCommerce Geolocation script, since it reloads our preview iframe.
// Thanks to https://wordpress.org/support/topic/in-editor-the-normal-preview-replaced-with-a-smaller-website-load/ .
if ( $this->preview_enabled && wp_script_is( 'wc-geolocation', 'enqueued' ) ) {
wp_dequeue_script( 'wc-geolocation' );
}
}
/**
* Do not cache.
*
* Tell WordPress cache plugins not to cache this request.
*/
public function do_not_cache() {
// Disable cache plugins.
if ( ! defined( 'DONOTCACHEPAGE' ) ) {
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
define( 'DONOTCACHEPAGE', true );
}
if ( ! defined( 'DONOTCACHEDB' ) ) {
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
define( 'DONOTCACHEDB', true );
}
if ( ! defined( 'DONOTMINIFY' ) ) {
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
define( 'DONOTMINIFY', true );
}
if ( ! defined( 'DONOTCDN' ) ) {
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
define( 'DONOTCDN', true );
}
if ( ! defined( 'DONOTCACHEOBJECT' ) ) {
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
define( 'DONOTCACHEOBJECT', true );
}
// Set the headers to prevent caching for the different browsers.
nocache_headers();
}
/**
* Template of preview page.
*/
public function print_template() {
do_action( 'vpf_preview_template' );
// Tell to WP Cache plugins do not cache this request.
$this->do_not_cache();
// Don't redirect to permalink.
remove_action( 'template_redirect', 'redirect_canonical' );
// Compatibility with Yoast SEO plugin when 'Removes unneeded query variables from the URL' enabled.
if ( class_exists( 'WPSEO_Frontend' ) ) {
remove_action( 'template_redirect', array( \WPSEO_Frontend::get_instance(), 'clean_permalink' ), 1 );
}
// Disable the WP admin bar.
add_filter( 'show_admin_bar', '__return_false' );
// Avoid Cloudflare's Rocket Loader lazy load the editor iframe.
// TODO: we also need an additional filter, which is not available in the WP yet:
// https://github.com/WordPress/wordpress-develop/pull/1759 .
add_filter( 'script_loader_tag', array( $this, 'rocket_loader_filter' ) );
// Enqueue assets.
Visual_Portfolio_Assets::enqueue_script( 'iframe-resizer-content', 'assets/vendor/iframe-resizer/js/iframeResizer.contentWindow.min', array(), '4.3.7' );
Visual_Portfolio_Assets::enqueue_script( 'visual-portfolio-preview', 'build/assets/js/preview', array( 'iframe-resizer-content' ) );
// Post data for script.
wp_localize_script(
'visual-portfolio-preview',
'vp_preview_post_data',
// phpcs:disable WordPress.Security.NonceVerification.Missing
isset( $_POST ) && ! empty( $_POST ) ? Visual_Portfolio_Security::sanitize_attributes( $_POST ) : array()
);
$class_name = 'vp-preview-wrapper';
// preview type.
$type = isset( $_POST['vp_preview_type'] ) ? sanitize_text_field( wp_unslash( $_POST['vp_preview_type'] ) ) : false;
if ( $type ) {
$class_name .= ' vp-preview-type-' . $type;
}
// Prepare portfolio post options.
$options = array();
if ( isset( $_POST ) && ! empty( $_POST ) ) {
foreach ( $_POST as $name => $val ) {
if ( strpos( $name, 'vp_' ) === 0 ) {
$options[ preg_replace( '/^vp_/', '', $name ) ] = $val;
}
}
}
// Elementor widget preview.
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_REQUEST['vp_preview_type'] ) && 'elementor' === $_REQUEST['vp_preview_type'] && isset( $_REQUEST['vp_preview_frame_id'] ) ) {
$options['id'] = sanitize_text_field( wp_unslash( $_REQUEST['vp_preview_frame_id'] ) );
}
// phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended
$options = Visual_Portfolio_Security::sanitize_attributes( $options );
// Register assets.
Visual_Portfolio_Assets::enqueue( $options );
// Custom styles.
visual_portfolio()->include_template_style( 'visual-portfolio-preview', 'preview/style', array(), VISUAL_PORTFOLIO_VERSION );
// Output template.
visual_portfolio()->include_template(
'preview/preview',
array(
'options' => $options,
'class_name' => $class_name,
)
);
}
/**
* Disable rocket loader in preview.
*
* @param String $tag - tag string.
*
* @return String
*/
public function rocket_loader_filter( $tag ) {
return str_replace( '<script', '<script data-cfasync="false"', $tag );
}
}
new Visual_Portfolio_Preview();

View File

@ -0,0 +1,264 @@
<?php
/**
* Rest API functions
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_Rest
*/
class Visual_Portfolio_Rest extends WP_REST_Controller {
/**
* Namespace.
*
* @var string
*/
protected $namespace = 'visual-portfolio/v';
/**
* Version.
*
* @var string
*/
protected $version = '1';
/**
* Visual_Portfolio_Rest constructor.
*/
public function __construct() {
add_action( 'rest_api_init', array( $this, 'register_routes' ) );
}
/**
* Register rest routes.
*/
public function register_routes() {
$namespace = $this->namespace . $this->version;
// Get layouts list.
register_rest_route(
$namespace,
'/get_layouts/',
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_layouts' ),
'permission_callback' => array( $this, 'get_layouts_permission' ),
)
);
// Update layout data.
register_rest_route(
$namespace,
'/update_layout/',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_layout' ),
'permission_callback' => array( $this, 'update_layout_permission' ),
)
);
// Update gallery items count notice state.
register_rest_route(
$namespace,
'/update_gallery_items_count_notice_state/',
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_gallery_items_count_notice_state' ),
'permission_callback' => array( $this, 'update_gallery_items_count_notice_state_permission' ),
)
);
}
/**
* Get layout data permission.
*
* @return mixed
*/
public function get_layouts_permission() {
if ( current_user_can( 'edit_posts' ) ) {
return true;
}
foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
if ( current_user_can( $post_type->cap->edit_posts ) ) {
return true;
}
}
return $this->error( 'not_allowed', esc_html__( 'Sorry, you are not allowed to get list of saved layouts.', 'visual-portfolio' ), true );
}
/**
* Get layout data.
*
* @return mixed
*/
public function get_layouts() {
// get all visual-portfolio post types.
// Don't use WP_Query on the admin side https://core.trac.wordpress.org/ticket/18408 .
$layouts = array();
$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 ) {
$layouts[] = array(
'id' => $post->ID,
'title' => $post->post_title,
'edit_url' => admin_url( 'post.php?post=' . $post->ID ) . '&action=edit',
);
}
if ( ! empty( $layouts ) ) {
return $this->success( $layouts );
} else {
return $this->error( 'no_layouts_found', __( 'Layouts not found.', 'visual-portfolio' ) );
}
}
/**
* Update layout data permission.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return true|WP_Error
*/
public function update_layout_permission( $request ) {
$post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0;
if ( ! $post_id ) {
return $this->error( 'post_id_required', esc_html__( 'Post ID is required for this request.', 'visual-portfolio' ), true );
}
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return $this->error( 'not_allowed', esc_html__( 'Sorry, you are not allowed to edit saved layouts data.', 'visual-portfolio' ), true );
}
return true;
}
/**
* Update layout data.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function update_layout( $request ) {
$post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0;
$data = isset( $request['data'] ) ? $request['data'] : false;
if ( $post_id && $data ) {
$meta = array_keys( Visual_Portfolio_Get::get_options( array( 'id' => $post_id ) ) );
foreach ( $meta as $name ) {
// Save with prefix.
$prefixed_name = 'vp_' . $name;
if ( isset( $data[ $prefixed_name ] ) ) {
if (
'vp_images' === $prefixed_name ||
'vp_layout_elements' === $prefixed_name ||
'vp_custom_css' === $prefixed_name
) {
$result = $data[ $prefixed_name ];
} elseif ( is_array( $data[ $prefixed_name ] ) ) {
$result = array_map( 'sanitize_text_field', wp_unslash( $data[ $prefixed_name ] ) );
} else {
$result = sanitize_text_field( wp_unslash( $data[ $prefixed_name ] ) );
}
update_post_meta( $post_id, $prefixed_name, $result );
}
}
}
return $this->success( true );
}
/**
* Update gallery items count notice state permission.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return true|WP_Error
*/
public function update_gallery_items_count_notice_state_permission( $request ) {
$post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0;
if ( ! $post_id || ! current_user_can( 'manage_options' ) ) {
return $this->error( 'user_dont_have_permission', esc_html__( 'User don\'t have permissions to change options.', 'visual-portfolio' ), true );
}
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return $this->error( 'user_dont_have_permission', esc_html__( 'User don\'t have permissions to change options.', 'visual-portfolio' ), true );
}
return true;
}
/**
* Update layout data.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function update_gallery_items_count_notice_state( $request ) {
update_option( 'visual_portfolio_items_count_notice_state', $request->get_param( 'notice_state' ) );
return $this->success( true );
}
/**
* Success rest.
*
* @param mixed $response response data.
* @return mixed
*/
public function success( $response ) {
return new WP_REST_Response(
array(
'success' => true,
'response' => $response,
),
200
);
}
/**
* Error rest.
*
* @param mixed $code error code.
* @param mixed $response response data.
* @param boolean $true_error use true error response to stop the code processing.
* @return mixed
*/
public function error( $code, $response, $true_error = false ) {
if ( $true_error ) {
return new WP_Error( $code, $response, array( 'status' => 401 ) );
}
return new WP_REST_Response(
array(
'error' => true,
'success' => false,
'error_code' => $code,
'response' => $response,
),
401
);
}
}
new Visual_Portfolio_Rest();

View File

@ -0,0 +1,499 @@
<?php
/**
* Security sanitization and validation of data
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_Security
*/
class Visual_Portfolio_Security {
/**
* Visual_Portfolio_Security constructor.
*/
public function __construct() {
add_filter( 'wp_kses_allowed_html', array( $this, 'wp_kses_vp_popup' ), 9, 2 );
add_filter( 'wp_kses_allowed_html', array( $this, 'wp_kses_vp_svg' ), 9, 2 );
add_filter( 'wp_kses_allowed_html', array( $this, 'wp_kses_vp_image' ), 9, 2 );
}
/**
* Returns allowed HTML tags and attributes for Popup.
*
* @param string|array $allowedtags - Allow Tags for current context.
* @param string|array $context - tags context.
*
* @return array
*/
public function wp_kses_vp_popup( $allowedtags, $context ) {
if ( 'vp_popup' === $context ) {
$kses_defaults = wp_kses_allowed_html( 'post' );
$kses_popup = array(
'template' => array(
'class' => true,
'style' => true,
// Most data for the popup is saved in the data attributes.
'data-*' => true,
),
'div' => array(
'class' => true,
'style' => true,
// Most data for the popup is saved in the data attributes.
'data-*' => true,
),
);
return array_merge( $allowedtags, $kses_defaults, $kses_popup );
}
return $allowedtags;
}
/**
* Returns allowed HTML tags and attributes for Svg.
*
* @param string|array $allowedtags - Allow Tags for current context.
* @param string|array $context - tags context.
*
* @return array
*/
public function wp_kses_vp_svg( $allowedtags, $context ) {
if ( 'vp_svg' === $context ) {
$kses_svg_attrs = array(
'x' => true,
'y' => true,
'd' => true,
'r' => true,
'width' => true,
'height' => true,
'rx' => true,
'cx' => true,
'cy' => true,
'stroke' => true,
'stroke-width' => true,
'fill' => true,
'transform' => true,
'stroke-linecap' => true,
'stroke-linejoin' => true,
);
$kses_svg = array(
'svg' => array(
'class' => true,
'aria-hidden' => true,
'aria-labelledby' => true,
'role' => true,
'xmlns' => true,
'width' => true,
'height' => true,
'viewbox' => true,
),
'g' => $kses_svg_attrs,
'path' => $kses_svg_attrs,
'rect' => $kses_svg_attrs,
'circle' => $kses_svg_attrs,
'title' => array(
'title' => true,
),
);
return $kses_svg;
}
return $allowedtags;
}
/**
* Returns allowed HTML tags and attributes for Svg.
*
* @param string|array $allowedtags - Allow Tags for current context.
* @param string|array $context - tags context.
*
* @return array
*/
public function wp_kses_vp_image( $allowedtags, $context ) {
if ( 'vp_image' === $context ) {
$kses_image = array(
'noscript' => true,
'img' => array(
'alt' => true,
'align' => true,
'border' => true,
'class' => true,
'height' => true,
'hspace' => true,
'loading' => true,
'longdesc' => true,
'vspace' => true,
'src' => true,
'srcset' => true,
'sizes' => true,
'usemap' => true,
'width' => true,
// Lazy loading data is saved in the data attributes.
'data-*' => true,
),
'figure' => array(
'align' => true,
),
'figcaption' => array(
'align' => true,
),
);
return $kses_image;
}
return $allowedtags;
}
/**
* Sanitize hidden attribute.
*
* @param string|bool $attribute - Unclear Hidden Attribute.
* @return string|bool
*/
public static function sanitize_hidden( $attribute ) {
$value = false;
if ( is_bool( $attribute ) ) {
$value = (bool) $attribute;
} elseif ( is_string( $attribute ) ) {
$value = strtolower( $attribute );
if ( in_array( $value, array( 'false', '0' ), true ) ) {
$value = false;
}
if ( in_array( $value, array( 'true', '1' ), true ) ) {
$value = true;
}
$value = is_bool( $value ) ? (bool) $value : sanitize_text_field( wp_unslash( $attribute ) );
}
return $value;
}
/**
* Sanitize Boolean.
*
* @param string|bool $attribute - Unclear Boolean Attribute.
* @return bool
*/
public static function sanitize_boolean( $attribute ) {
$value = false;
if ( is_bool( $attribute ) ) {
$value = (bool) $attribute;
} elseif ( is_string( $attribute ) ) {
$value = strtolower( $attribute );
if ( in_array( $value, array( 'false', '0' ), true ) ) {
$value = false;
}
if ( in_array( $value, array( 'true', '1' ), true ) ) {
$value = true;
}
$value = (bool) wp_unslash( $value );
}
return $value;
}
/**
* Sanitize selector attribute.
*
* @param int|float|string $attribute - Unclear Selector Attribute.
* @return int|float|string
*/
public static function sanitize_selector( $attribute ) {
if ( is_numeric( $attribute ) ) {
if ( false === strpos( $attribute, '.' ) ) {
$attribute = intval( $attribute );
} else {
$attribute = (float) $attribute;
}
} else {
$attribute = sanitize_text_field( wp_unslash( $attribute ) );
}
return $attribute;
}
/**
* Sanitize number attribute.
*
* @param string|int|float $attribute - Unclear Number Attribute.
* @return int|float
*/
public static function sanitize_number( $attribute ) {
$attribute = preg_replace( '/[^0-9.-]/', '', wp_unslash( $attribute ) );
// We should keep an empty string, because we allow resetting certain attributes.
if ( '' === $attribute ) {
$attribute = '';
} elseif ( false === strpos( $attribute, '.' ) ) {
$attribute = intval( $attribute );
} else {
$attribute = (float) $attribute;
}
return $attribute;
}
/**
* Sanitize Element Selector.
*
* @param array $attribute - Unclear Element Selector Attribute.
* @return array
*/
public static function sanitize_elements_selector( $attribute ) {
$key = 'layout_elements';
if ( ! empty( $attribute ) && is_array( $attribute ) ) {
$controls = Visual_Portfolio_Controls::get_registered_array();
$hight_level_allowed_locations = array_keys( $controls[ $key ]['locations'] );
foreach ( $attribute as $locations_key => $locations ) {
if (
false !== array_search( $locations_key, $hight_level_allowed_locations, true ) &&
! empty( $locations ) &&
is_array( $locations )
) {
$low_level_allowed_locations = array( 'elements' );
if (
isset( $controls[ $key ]['locations'][ $locations_key ]['align'] ) &&
! empty( $controls[ $key ]['locations'][ $locations_key ]['align'] )
) {
$low_level_allowed_locations = array_merge(
$low_level_allowed_locations,
array( 'align' )
);
$allowed_align_protocol = $controls[ $key ]['locations'][ $locations_key ]['align'];
}
foreach ( $locations as $location_key => $location ) {
if (
false !== array_search( $location_key, $low_level_allowed_locations, true )
) {
if (
'align' === $location_key &&
isset( $allowed_align_protocol ) &&
false !== array_search( $location, $allowed_align_protocol, true )
) {
$attribute[ $locations_key ][ $location_key ] = sanitize_text_field( wp_unslash( $location ) );
} elseif (
'elements' === $location_key &&
is_array( $location )
) {
foreach ( $location as $item_key => $item ) {
if (
false !== array_search( $locations_key, $controls[ $key ]['options'][ $item ]['allowed_locations'], true ) &&
isset( $controls[ $key ]['options'][ $item ]['allowed_locations'] )
) {
$attribute[ $locations_key ][ $location_key ][ $item_key ] = sanitize_text_field( wp_unslash( $item ) );
} else {
unset( $attribute[ $locations_key ][ $location_key ][ $item_key ] );
}
}
} else {
unset( $attribute[ $locations_key ][ $location_key ] );
}
} else {
unset( $attribute[ $locations_key ][ $location_key ] );
}
}
} else {
unset( $attribute[ $locations_key ] );
}
}
} else {
$attribute = array();
}
return $attribute;
}
/**
* Sanitize Gallery.
*
* @param array $attribute - Unclear Gallery Attribute.
* @return array
*/
public static function sanitize_gallery( $attribute ) {
if ( isset( $attribute ) && ! empty( $attribute ) ) {
foreach ( $attribute as $key => $gallery_item ) {
if ( isset( $gallery_item ) && ! empty( $gallery_item ) ) {
foreach ( $gallery_item as $attribute_key => $media_attribute ) {
switch ( $attribute_key ) {
case 'imgUrl':
case 'imgThumbnailUrl':
case 'video_url':
case 'author_url':
case 'post_url':
case 'url':
$attribute[ $key ][ $attribute_key ] = esc_url_raw( wp_unslash( $media_attribute ) );
break;
case 'hover_image_focal_point':
case 'focalPoint':
if ( isset( $media_attribute ) && ! empty( $media_attribute ) ) {
foreach ( $media_attribute as $focal_key => $focal_point ) {
$attribute[ $key ][ $attribute_key ][ $focal_key ] = self::sanitize_number( $focal_point );
}
} else {
$attribute[ $key ][ $attribute_key ] = null;
}
break;
case 'custom_popup_image':
case 'hover_image':
$attribute[ $key ][ $attribute_key ] = self::sanitize_number( $media_attribute );
break;
case 'categories':
if ( isset( $media_attribute ) && ! empty( $media_attribute ) ) {
foreach ( $media_attribute as $category_key => $category ) {
$attribute[ $key ][ $attribute_key ][ $category_key ] = sanitize_text_field( wp_unslash( $category ) );
}
} else {
$attribute[ $key ][ $attribute_key ] = null;
}
break;
case 'title':
case 'description':
$attribute[ $key ][ $attribute_key ] = wp_kses_post( wp_unslash( $media_attribute ) );
break;
case 'id':
case 'author':
case 'format':
case 'deep_link_pid':
default:
$attribute[ $key ][ $attribute_key ] = sanitize_text_field( wp_unslash( $media_attribute ) );
break;
}
}
}
}
} else {
$attribute = array();
}
return $attribute;
}
/**
* Sanitize Attributes.
*
* @param array $attributes - Attributes.
* @return array
*/
public static function sanitize_attributes( $attributes = array() ) {
if ( ! empty( $attributes ) ) {
$controls = Visual_Portfolio_Controls::get_registered_array();
foreach ( $attributes as $key => $attribute ) {
if ( 0 === strpos( $key, 'vp_' ) ) {
$key = preg_replace( '/^vp_/', '', $key );
$mode = 'preview';
$attributes[ $key ] = $attribute;
unset( $attributes[ 'vp_' . $key ] );
}
$sanitize_callback = $controls[ $key ]['sanitize_callback'] ?? false;
if ( $sanitize_callback ) {
$finding_class = is_string( $sanitize_callback ) && strripos( $sanitize_callback, '::' );
if ( false === $finding_class && is_callable( array( __CLASS__, $sanitize_callback ) ) ) {
$attributes[ $key ] = call_user_func( array( __CLASS__, $sanitize_callback ), $attribute );
} else {
$attributes[ $key ] = call_user_func( $sanitize_callback, $attribute );
}
} else {
$type = $controls[ $key ]['type'] ?? false;
switch ( $type ) {
case 'hidden':
case 'checkbox':
$attributes[ $key ] = self::sanitize_hidden( $attribute );
break;
case 'icons_selector':
case 'text':
case 'radio':
case 'align':
case 'buttons':
$attributes[ $key ] = sanitize_text_field( wp_unslash( $attribute ) );
break;
case 'textarea':
$attributes[ $key ] = sanitize_textarea_field( wp_unslash( $attribute ) );
break;
case 'aspect_ratio':
$attributes[ $key ] = preg_replace( '/[^0-9:]/', '', wp_unslash( $attribute ) );
break;
case 'number':
case 'range':
$attributes[ $key ] = self::sanitize_number( $attribute );
break;
case 'tiles_selector':
$attributes[ $key ] = preg_replace( '/[^0-9.,|]/', '', wp_unslash( $attribute ) );
break;
case 'select':
$multiple = isset( $controls[ $key ]['multiple'] ) && ! empty( $controls[ $key ]['multiple'] ) ? $controls[ $key ]['multiple'] : false;
if ( $multiple ) {
$attributes[ $key ] = $attributes[ $key ] ?? $controls[ $key ]['default'] ?? array();
if ( is_array( $attributes[ $key ] ) && ! empty( $attributes[ $key ] ) ) {
foreach ( $attributes[ $key ] as $attribute_key => $value ) {
$attributes[ $key ][ $attribute_key ] = self::sanitize_selector( $value );
}
}
} else {
$attributes[ $key ] = self::sanitize_selector( $attributes[ $key ] );
}
break;
case 'elements_selector':
$attributes[ $key ] = self::sanitize_elements_selector( $attribute );
break;
case 'code_editor':
// Clear CSS. Maybe there will be an SVG validation error.
$attributes[ $key ] = preg_replace( '#</?\w+#', '', wp_unslash( $attribute ) );
break;
case 'sortable':
if ( is_array( $attribute ) && ! empty( $attribute ) ) {
foreach ( $attribute as $attribute_key => $value ) {
$attributes[ $key ][ $attribute_key ] = sanitize_text_field( wp_unslash( $value ) );
}
} else {
$attributes[ $key ] = array();
}
break;
case 'gallery':
$attributes[ $key ] = self::sanitize_gallery( $attribute );
break;
case false:
if ( 'block_id' === $key ) {
$attributes[ $key ] = preg_replace( '/[^a-zA-Z0-9_-]/', '', wp_unslash( $attribute ) );
} else {
$attributes[ $key ] = sanitize_text_field( wp_unslash( $attribute ) );
}
break;
default:
$attributes[ $key ] = sanitize_text_field( wp_unslash( $attribute ) );
break;
}
}
}
if ( isset( $mode ) && 'preview' === $mode ) {
foreach ( $attributes as $key => $attribute ) {
$attributes[ 'vp_' . $key ] = $attribute;
unset( $attributes[ $key ] );
}
}
}
return $attributes;
}
}
new Visual_Portfolio_Security();

View File

@ -0,0 +1,87 @@
<?php
/**
* Seo Optimization.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_SEO_Optimization
*/
class Visual_Portfolio_SEO_Optimization {
/**
* Visual_Portfolio_SEO_Optimization constructor.
*/
public function __construct() {
add_action( 'init', array( $this, 'init' ), 9 );
}
/**
* Initialize archive.
*
* @see __construct
*/
public function init() {
add_filter( 'get_canonical_url', array( $this, 'optimize_canonical_url' ), 10, 2 );
add_filter( 'get_shortlink', array( $this, 'optimize_shortlink' ), 10, 3 );
}
/**
* Optimize canonical URL.
*
* @param string $canonical_url - Canonical URL.
* @param object $post - Current Post Object.
* @return string
*/
public function optimize_canonical_url( $canonical_url, $post ) {
return $this->optimize_url( $canonical_url, $post->ID );
}
/**
* Optimize shortlink.
*
* @param string $shortlink - Shortlink URL.
* @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 string
*/
public function optimize_shortlink( $shortlink, $id, $context ) {
return 0 === $id && 'query' === $context ? $this->optimize_url( $shortlink, get_queried_object_id() ) : $shortlink;
}
/**
* Optimize url by supported GET variables: vp_page, vp_filter, vp_sort and vp_search.
*
* @param string $url - Not optimized URL.
* @param int $post_id - Post ID.
* @return string
*/
public function optimize_url( $url, $post_id ) {
if (
! Visual_Portfolio_Archive_Mapping::is_archive(
array(
'content_source' => 'post-based',
'posts_source' => 'current_query',
),
$post_id
) &&
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
isset( $_GET ) && ! empty( $_GET )
) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
foreach ( $_GET as $key => $value ) {
if ( 'vp_page' === $key || 'vp_filter' === $key || 'vp_sort' === $key || 'vp_search' === $key ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$url = add_query_arg( array_map( 'sanitize_text_field', wp_unslash( array( $key => $value ) ) ), $url );
}
}
}
return $url;
}
}
new Visual_Portfolio_SEO_Optimization();

View File

@ -0,0 +1,914 @@
<?php
/**
* Plugin Settings
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
require_once visual_portfolio()->plugin_path . 'vendors/class-settings-api.php';
/**
* Visual Portfolio Settings Class
*/
class Visual_Portfolio_Settings {
/**
* Settings API instance
*
* @var object
*/
public static $settings_api;
/**
* Cached settings fields. We call settings fields method a lot of times to get default values.
* So, for performance reasons we need to cache the output.
*
* @var object
*/
public static $cached_settings_fields;
/**
* Visual_Portfolio_Settings constructor.
*/
public function __construct() {
self::init_actions();
}
/**
* Get Option Value
*
* @param string $option - option name.
* @param string $section - section name.
* @param string $deprecated_default - default option value.
*
* @return bool|string
*/
// phpcs:ignore
public static function get_option( $option, $section, $deprecated_default = '' ) {
$options = get_option( $section );
$result = '';
if ( isset( $options[ $option ] ) ) {
$result = $options[ $option ];
} else {
// find default.
$fields = self::get_settings_fields();
if ( isset( $fields[ $section ] ) && is_array( $fields[ $section ] ) ) {
foreach ( $fields[ $section ] as $field_data ) {
if ( $option === $field_data['name'] && isset( $field_data['default'] ) ) {
$result = $field_data['default'];
}
}
}
}
return 'off' === $result ? false : ( 'on' === $result ? true : $result );
}
/**
* Update Option Value
*
* @param string $option - option name.
* @param string $section - section name.
* @param string $value - new option value.
*/
public static function update_option( $option, $section, $value ) {
$options = get_option( $section );
if ( ! is_array( $options ) ) {
$options = array();
}
$options[ $option ] = $value;
update_option( $section, $options );
}
/**
* Init actions
*/
public static function init_actions() {
self::$settings_api = new Visual_Portfolio_Settings_API();
add_action( 'admin_init', array( __CLASS__, 'admin_init' ) );
add_action( 'admin_menu', array( __CLASS__, 'admin_menu' ), 11 );
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'admin_enqueue_scripts' ) );
add_action( 'wp_ajax_vp_get_pages_list', array( __CLASS__, 'get_posts_ajax_callback' ) );
}
/**
* Initialize the settings
*
* @return void
*/
public static function admin_init() {
self::redirect_if_toggle_unregistered_portfolio_post_type();
// set the settings.
self::$settings_api->set_sections( self::get_settings_sections() );
self::$settings_api->set_fields( self::get_settings_fields() );
// initialize settings.
self::$settings_api->admin_init();
}
/**
* Register the admin settings menu
*
* @return void
*/
public static function admin_menu() {
remove_submenu_page( 'visual-portfolio-settings', 'visual-portfolio-settings' );
add_submenu_page(
Visual_Portfolio_Custom_Post_Type::get_menu_slug(),
esc_html__( 'Settings', 'visual-portfolio' ),
esc_html__( 'Settings', 'visual-portfolio' ),
'manage_options',
'visual-portfolio-settings',
array( __CLASS__, 'print_settings_page' )
);
}
/**
* Redirect to actual admin page if unregistered portfolio post type.
*
* @return void
*/
public static function redirect_if_toggle_unregistered_portfolio_post_type() {
global $pagenow;
$register_portfolio_post_type = Visual_Portfolio_Custom_Post_Type::portfolio_post_type_is_registered();
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if (
$register_portfolio_post_type &&
'admin.php' === $pagenow &&
isset( $_GET['page'] ) &&
'visual-portfolio-settings' === $_GET['page']
) {
wp_safe_redirect( admin_url( '/edit.php?post_type=portfolio&page=visual-portfolio-settings' ) );
exit;
}
if (
! $register_portfolio_post_type &&
'edit.php' === $pagenow &&
isset( $_GET['page'] ) &&
'visual-portfolio-settings' === $_GET['page'] &&
isset( $_GET['post_type'] ) &&
'portfolio' === $_GET['post_type']
) {
wp_safe_redirect( admin_url( '/admin.php?page=visual-portfolio-settings' ) );
exit;
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
}
/**
* Enqueue archive select2 ajax script.
*
* @param string $page - Current admin page.
* @return void
*/
public static function admin_enqueue_scripts( $page ) {
if ( 'portfolio_page_visual-portfolio-settings' === $page || 'toplevel_page_visual-portfolio-settings' === $page ) {
$data_init = array(
'nonce' => wp_create_nonce( 'vp-ajax-nonce' ),
);
Visual_Portfolio_Assets::enqueue_script( 'visual-portfolio-archive-page-selector', 'build/assets/admin/js/archive-page-selector', array( 'select2' ) );
wp_localize_script( 'visual-portfolio-archive-page-selector', 'VPAdminVariables', $data_init );
}
}
/**
* Plugin settings sections
*
* @return array
*/
public static function get_settings_sections() {
$sections = array(
array(
'id' => 'vp_general',
'title' => esc_html__( 'General', 'visual-portfolio' ),
'icon' => '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" /></svg>',
),
array(
'id' => 'vp_images',
'title' => esc_html__( 'Images', 'visual-portfolio' ),
'icon' => '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>',
),
array(
'id' => 'vp_popup_gallery',
'title' => esc_html__( 'Popup & Lightbox', 'visual-portfolio' ),
'icon' => '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path></svg>',
),
array(
'id' => 'vp_watermarks',
'title' => esc_html__( 'Watermarks', 'visual-portfolio' ),
'icon' => '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /></svg>',
),
array(
'id' => 'vp_social_integrations',
'title' => esc_html__( 'Social Feeds', 'visual-portfolio' ),
'icon' => '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" /></svg>',
),
array(
'id' => 'vp_white_label',
'title' => esc_html__( 'White Label', 'visual-portfolio' ),
'icon' => '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z" /></svg>',
),
);
return apply_filters( 'vpf_settings_sections', $sections );
}
/**
* Returns all the settings fields
*
* @return array settings fields
*/
public static function get_settings_fields() {
if ( ! empty( self::$cached_settings_fields ) ) {
return self::$cached_settings_fields;
}
$default_breakpoints = Visual_Portfolio_Breakpoints::get_default_breakpoints();
$go_pro_links = array(
'watermarks' => Visual_Portfolio_Admin::get_plugin_site_url(
array(
'utm_medium' => 'settings_page',
'utm_campaign' => 'watermarks',
)
),
'social' => Visual_Portfolio_Admin::get_plugin_site_url(
array(
'utm_medium' => 'settings_page',
'utm_campaign' => 'social_feeds',
)
),
'white_label' => Visual_Portfolio_Admin::get_plugin_site_url(
array(
'utm_medium' => 'settings_page',
'utm_campaign' => 'white_label',
)
),
);
$settings_fields = array(
'vp_general' => array(
array(
'name' => 'register_portfolio_post_type',
'label' => esc_html__( 'Register Portfolio Post Type', 'visual-portfolio' ),
'desc' => esc_html__( 'Add custom post type `portfolio` to showcase your works.', 'visual-portfolio' ),
'type' => 'toggle',
'default' => 'on',
),
array(
'name' => 'portfolio_archive_page',
'label' => esc_html__( 'Archive Page', 'visual-portfolio' ),
'desc' => esc_html__( 'Base page of your portfolio, where will be placed your works archive.', 'visual-portfolio' ),
'type' => 'select',
'options' => self::get_pages_list(),
'sanitize_callback' => array( 'Visual_Portfolio_Archive_Mapping', 'save_archive_page_option' ),
'condition' => array(
array(
'control' => '[type="checkbox"][name="vp_general[register_portfolio_post_type]"]',
),
),
),
array(
'name' => 'archive_page_items_per_page',
'label' => esc_html__( 'Archive Page Items Per Page', 'visual-portfolio' ),
'type' => 'number',
'min' => -1,
'max' => 9999,
'default' => 6,
'condition' => array(
array(
'control' => '[type="checkbox"][name="vp_general[register_portfolio_post_type]"]',
),
),
),
array(
'name' => 'filter_taxonomies',
'label' => esc_html__( 'Filter Taxonomies', 'visual-portfolio' ),
'desc' => esc_html__( 'You can show custom taxonomies in the portfolio Filter. Enter some taxonomies by "," separating values. Example: "product_cat,product_tag"', 'visual-portfolio' ),
'type' => 'text',
'default' => '',
),
array(
'name' => 'no_image',
'label' => esc_html__( 'Placeholder Image', 'visual-portfolio' ),
'desc' => esc_html__( 'This image used on items in layouts where image is not specified.', 'visual-portfolio' ),
'type' => 'image',
'default' => '',
'options' => array(
'button_label' => esc_html__( 'Choose image', 'visual-portfolio' ),
),
),
// AJAX Caching and Preloading.
array(
'name' => 'ajax_caching',
'label' => esc_html__( 'AJAX Cache and Preload', 'visual-portfolio' ),
'desc' => esc_html__( 'Reduce AJAX calls request time.', 'visual-portfolio' ),
'type' => 'toggle',
'default' => class_exists( 'Visual_Portfolio_Pro' ) ? 'on' : 'off',
'is_pro' => true,
),
// Breakpoints.
array(
'name' => 'breakpoints_title',
'label' => esc_html__( 'Responsive Breakpoints', 'visual-portfolio' ),
'type' => 'section_title',
'is_pro' => true,
),
array(
'name' => 'breakpoint_xl',
'label' => esc_html__( 'Extra Large', 'visual-portfolio' ),
'type' => 'number',
'min' => (float) $default_breakpoints['lg'] + 1,
'max' => 3840,
'placeholder' => (string) $default_breakpoints['xl'],
'default' => (float) $default_breakpoints['xl'],
// translators: %1$s - default breakpoint.
'desc' => sprintf( esc_html__( 'Sets the breakpoint on extra large screen sizes (Default: %1$spx).', 'visual-portfolio' ), $default_breakpoints['xl'] ),
'is_pro' => true,
),
array(
'name' => 'breakpoint_lg',
'label' => esc_html__( 'Large', 'visual-portfolio' ),
'type' => 'number',
'min' => (float) $default_breakpoints['md'] + 1,
'max' => (float) $default_breakpoints['xl'] - 1,
'placeholder' => (string) $default_breakpoints['lg'],
'default' => (float) $default_breakpoints['lg'],
// translators: %1$s - default breakpoint.
'desc' => sprintf( esc_html__( 'Sets the breakpoint on large screen sizes (Default: %1$spx).', 'visual-portfolio' ), $default_breakpoints['lg'] ),
'is_pro' => true,
),
array(
'name' => 'breakpoint_md',
'label' => esc_html__( 'Medium', 'visual-portfolio' ),
'type' => 'number',
'min' => (float) $default_breakpoints['sm'] + 1,
'max' => (float) $default_breakpoints['lg'] - 1,
'placeholder' => (string) $default_breakpoints['md'],
'default' => (float) $default_breakpoints['md'],
// translators: %1$s - default breakpoint.
'desc' => sprintf( esc_html__( 'Sets the breakpoint on medium screen sizes (Default: %1$spx).', 'visual-portfolio' ), $default_breakpoints['md'] ),
'is_pro' => true,
),
array(
'name' => 'breakpoint_sm',
'label' => esc_html__( 'Small', 'visual-portfolio' ),
'type' => 'number',
'min' => (float) $default_breakpoints['xs'] + 1,
'max' => (float) $default_breakpoints['md'] - 1,
'placeholder' => (string) $default_breakpoints['sm'],
'default' => (float) $default_breakpoints['sm'],
// translators: %1$s - default breakpoint.
'desc' => sprintf( esc_html__( 'Sets the breakpoint on small screen sizes (Default: %1$spx).', 'visual-portfolio' ), $default_breakpoints['sm'] ),
'is_pro' => true,
),
array(
'name' => 'breakpoint_xs',
'label' => esc_html__( 'Extra Small', 'visual-portfolio' ),
'type' => 'number',
'min' => 1,
'max' => (float) $default_breakpoints['sm'] - 1,
'placeholder' => (string) $default_breakpoints['xs'],
'default' => (float) $default_breakpoints['xs'],
// translators: %1$s - default breakpoint.
'desc' => sprintf( esc_html__( 'Sets the breakpoint on extra small screen sizes (Default: %1$spx).', 'visual-portfolio' ), $default_breakpoints['xs'] ),
'is_pro' => true,
),
),
'vp_images' => array(
array(
'name' => 'lazy_loading',
'label' => esc_html__( 'Lazy Loading', 'visual-portfolio' ),
// translators: %s - plugin brand name.
'desc' => sprintf( esc_html__( 'Enable lazy loading for %s layouts only or for the whole website.', 'visual-portfolio' ), visual_portfolio()->plugin_name ),
'type' => 'select',
'default' => 'vp',
'options' => array(
'' => esc_html__( 'Disabled', 'visual-portfolio' ),
// translators: %s - plugin brand name.
'vp' => sprintf( esc_html__( '%s Only', 'visual-portfolio' ), visual_portfolio()->plugin_name ),
'full' => esc_html__( 'All images', 'visual-portfolio' ),
),
),
array(
'name' => 'lazy_loading_excludes',
'label' => esc_html__( 'Lazy Loading Excludes', 'visual-portfolio' ),
// translators: %s - doc url.
// translators: %s - link text.
'desc' => sprintf( __( 'Listed images will not be lazy loaded. Both full URLs and partial strings can be used. One per line. <a href="%1$s">%2$s</a>', 'visual-portfolio' ), 'https://visualportfolio.co/docs/settings/images/', esc_html__( 'More info', 'visual-portfolio' ) ),
'type' => 'textarea',
'placeholder' => "image-example.webp\nslider-image-classname",
'condition' => array(
array(
'control' => '[name="vp_images[lazy_loading]"]',
),
),
),
array(
'name' => 'images_layouts_title',
'label' => esc_html__( 'Layouts Image Sizes', 'visual-portfolio' ),
'desc' => __( 'Image sizes used in portfolio layouts.', 'visual-portfolio' ),
'type' => 'section_title',
),
array(
'name' => 'sm',
'label' => esc_html__( 'Small', 'visual-portfolio' ),
'type' => 'number',
'placeholder' => '500',
'default' => 500,
),
array(
'name' => 'md',
'label' => esc_html__( 'Medium', 'visual-portfolio' ),
'type' => 'number',
'placeholder' => '800',
'default' => 800,
),
array(
'name' => 'lg',
'label' => esc_html__( 'Large', 'visual-portfolio' ),
'type' => 'number',
'placeholder' => '1280',
'default' => 1280,
),
array(
'name' => 'xl',
'label' => esc_html__( 'Extra Large', 'visual-portfolio' ),
'type' => 'number',
'placeholder' => '1920',
'default' => 1920,
),
array(
'name' => 'images_popup_title',
'label' => esc_html__( 'Lightbox Image Sizes', 'visual-portfolio' ),
'desc' => __( 'Image sizes used in lightbox images.', 'visual-portfolio' ),
'type' => 'section_title',
),
array(
'name' => 'sm_popup',
'label' => esc_html__( 'Small', 'visual-portfolio' ),
'type' => 'number',
'placeholder' => '500',
'default' => 500,
),
array(
'name' => 'md_popup',
'label' => esc_html__( 'Medium', 'visual-portfolio' ),
'type' => 'number',
'placeholder' => '800',
'default' => 800,
),
array(
'name' => 'xl_popup',
'label' => esc_html__( 'Large', 'visual-portfolio' ),
'type' => 'number',
'placeholder' => '1920',
'default' => 1920,
),
array(
'name' => 'images_sizes_note',
// translators: %s: regenerate thumbnails url.
'desc' => sprintf( __( 'After publishing your changes, new image sizes may not be shown until you <a href="%s" target="_blank">Regenerate Thumbnails</a>.', 'visual-portfolio' ), 'https://wordpress.org/plugins/regenerate-thumbnails/' ),
'type' => 'html',
),
),
'vp_popup_gallery' => array(
// Vendor.
array(
'name' => 'vendor',
'label' => esc_html__( 'Vendor Script', 'visual-portfolio' ),
'type' => 'select',
'options' => array(
'fancybox' => esc_html__( 'Fancybox', 'visual-portfolio' ),
'photoswipe' => esc_html__( 'PhotoSwipe', 'visual-portfolio' ),
),
'default' => 'fancybox',
),
// Default WordPress Images.
array(
'name' => 'enable_on_wordpress_images',
'label' => esc_html__( 'Lightbox for WordPress Images', 'visual-portfolio' ),
'desc' => esc_html__( 'Enable lightbox for WordPress native galleries and images.', 'visual-portfolio' ),
'type' => 'toggle',
'default' => 'off',
),
// Section divider.
array(
'name' => 'popup_general_divider_title',
'type' => 'section_title',
),
// Deeplinking.
array(
'name' => 'deep_linking',
'label' => esc_html__( 'Deep Linking', 'visual-portfolio' ),
'desc' => esc_html__( 'Makes URL automatically change to reflect the current opened popup, and you can easily link directly to that image or video.', 'visual-portfolio' ),
'type' => 'toggle',
'default' => class_exists( 'Visual_Portfolio_Pro' ) ? 'on' : 'off',
'is_pro' => true,
),
array(
'name' => 'deep_linking_url_to_share_images',
'label' => esc_html__( 'Use Deep Linking URL to Share Images', 'visual-portfolio' ),
'desc' => esc_html__( 'Check to share Deep Linking URLs when sharing images. When disabled, all galleries will share direct links to image files.', 'visual-portfolio' ),
'type' => 'toggle',
'default' => class_exists( 'Visual_Portfolio_Pro' ) ? 'on' : 'off',
'is_pro' => true,
),
// Loop.
array(
'name' => 'loop',
'label' => esc_html__( 'Loop', 'visual-portfolio' ),
'type' => 'toggle',
'default' => 'on',
'is_pro' => true,
),
// Click to Zoom.
array(
'name' => 'click_to_zoom',
'label' => esc_html__( 'Click to Zoom', 'visual-portfolio' ),
'type' => 'toggle',
'default' => 'on',
),
// Restore Focus.
array(
'name' => 'restore_focus',
'label' => esc_html__( 'Restore Focus', 'visual-portfolio' ),
'desc' => esc_html__( 'Restore focus on the last active item after Popup is closed.', 'visual-portfolio' ),
'type' => 'toggle',
'default' => 'on',
),
// UI Elements.
array(
'name' => 'popup_ui_elements_title',
'label' => esc_html__( 'UI Elements', 'visual-portfolio' ),
'type' => 'section_title',
),
array(
'name' => 'show_arrows',
'label' => esc_html__( 'Display Arrows', 'visual-portfolio' ),
'desc' => esc_html__( 'Arrows to navigate between images.', 'visual-portfolio' ),
'type' => 'toggle',
'default' => 'on',
),
array(
'name' => 'show_counter',
'label' => esc_html__( 'Display Images Counter', 'visual-portfolio' ),
'desc' => esc_html__( 'On the top left corner will be showed images counter.', 'visual-portfolio' ),
'type' => 'toggle',
'default' => 'on',
),
array(
'name' => 'show_zoom_button',
'label' => esc_html__( 'Display Zoom Button', 'visual-portfolio' ),
'type' => 'toggle',
'default' => 'on',
),
array(
'name' => 'show_fullscreen_button',
'label' => esc_html__( 'Display Fullscreen Button', 'visual-portfolio' ),
'type' => 'toggle',
'default' => 'on',
),
array(
'name' => 'show_share_button',
'label' => esc_html__( 'Display Share Button', 'visual-portfolio' ),
'type' => 'toggle',
'default' => 'on',
),
array(
'name' => 'show_close_button',
'label' => esc_html__( 'Display Close Button', 'visual-portfolio' ),
'type' => 'toggle',
'default' => 'on',
),
// Fancybox Popup Settings.
array(
'name' => 'show_thumbs',
'label' => esc_html__( 'Display Thumbnails', 'visual-portfolio' ),
'type' => 'toggle',
'default' => 'on',
'condition' => array(
array(
'control' => '[name="vp_popup_gallery[vendor]"]',
'operator' => '===',
'value' => 'fancybox',
),
),
),
array(
'name' => 'thumbs_auto_start',
'label' => esc_html__( 'Thumbnails Opened At Startup', 'visual-portfolio' ),
'type' => 'toggle',
'default' => 'off',
'is_pro' => true,
'condition' => array(
array(
'control' => '[type="checkbox"][name="vp_popup_gallery[show_thumbs]"]',
),
array(
'control' => '[name="vp_popup_gallery[vendor]"]',
'operator' => '===',
'value' => 'fancybox',
),
),
),
array(
'name' => 'thumbs_position',
'label' => esc_html__( 'Thumbnails Position', 'visual-portfolio' ),
'type' => 'select',
'default' => 'vertical',
'options' => array(
'vertical' => esc_html__( 'Vertical', 'visual-portfolio' ),
'horizontal' => esc_html__( 'Horizontal', 'visual-portfolio' ),
),
'is_pro' => true,
'condition' => array(
array(
'control' => '[type="checkbox"][name="vp_popup_gallery[show_thumbs]"]',
),
array(
'control' => '[name="vp_popup_gallery[vendor]"]',
'operator' => '===',
'value' => 'fancybox',
),
),
),
array(
'name' => 'show_download_button',
'label' => esc_html__( 'Display Download Button', 'visual-portfolio' ),
'type' => 'toggle',
'default' => 'off',
'condition' => array(
array(
'control' => '[name="vp_popup_gallery[vendor]"]',
'operator' => '===',
'value' => 'fancybox',
),
),
),
array(
'name' => 'show_slideshow',
'label' => esc_html__( 'Display Slideshow', 'visual-portfolio' ),
'type' => 'toggle',
'default' => 'off',
'condition' => array(
array(
'control' => '[name="vp_popup_gallery[vendor]"]',
'operator' => '===',
'value' => 'fancybox',
),
),
),
// Quick View settings.
array(
'name' => 'popup_quick_view_title',
'label' => esc_html__( 'Quick View', 'visual-portfolio' ),
'type' => 'section_title',
),
array(
'name' => 'popup_quick_view_show_url_button',
'label' => esc_html__( 'Display URL Button', 'visual-portfolio' ),
'desc' => esc_html__( 'Button with page URL will be placed in the popup toolbar.', 'visual-portfolio' ),
'type' => 'toggle',
'default' => 'on',
'is_pro' => true,
),
array(
'name' => 'popup_quick_view_internal_links_target',
'label' => esc_html__( 'Internal Links', 'visual-portfolio' ),
'type' => 'select',
'default' => '_blank',
'options' => array(
'_blank' => esc_html__( 'Open in New Tab', 'visual-portfolio' ),
'_top' => esc_html__( 'Open in Current Tab', 'visual-portfolio' ),
'_self' => esc_html__( 'Open in Frame (not recommended)', 'visual-portfolio' ),
'prevent-click' => esc_html__( 'Prevent Click', 'visual-portfolio' ),
),
'is_pro' => true,
),
array(
'name' => 'popup_quick_view_external_links_target',
'label' => esc_html__( 'External Links', 'visual-portfolio' ),
'type' => 'select',
'default' => '_blank',
'options' => array(
'_blank' => esc_html__( 'Open in New Tab', 'visual-portfolio' ),
'_top' => esc_html__( 'Open in Current Tab', 'visual-portfolio' ),
'_self' => esc_html__( 'Open in Frame (not recommended)', 'visual-portfolio' ),
'prevent-click' => esc_html__( 'Prevent Click', 'visual-portfolio' ),
),
'is_pro' => true,
),
array(
'name' => 'pages_iframe_custom_css',
'label' => esc_html__( 'Custom CSS', 'visual-portfolio' ),
'desc' => esc_html__( 'When you display posts and pages in popup iframe, you may not need some page elements like header and footer. Hide it using custom CSS with classname `.vp-popup-iframe`.', 'visual-portfolio' ),
'type' => 'textarea',
'default' => ! class_exists( 'Visual_Portfolio_Pro' ) ? '' : '
/* Hide header and footer in standard themes */
.vp-popup-iframe #site-header,
.vp-popup-iframe #masthead,
.vp-popup-iframe #site-footer,
.vp-popup-iframe #colophon {
display: none;
}
/* Hide header and footer in Twenty Twenty-Two theme (Full Site Editing) */
.vp-popup-iframe .wp-site-blocks > header.wp-block-template-part,
.vp-popup-iframe .wp-site-blocks > footer.wp-block-template-part {
display: none;
}',
'is_pro' => true,
),
// Misc settings.
array(
'name' => 'popup_misc_title',
'label' => esc_html__( 'Misc', 'visual-portfolio' ),
'type' => 'section_title',
),
array(
'name' => 'background_color',
'label' => esc_html__( 'Background Color', 'visual-portfolio' ),
'type' => 'color',
'default' => '#1e1e1e',
),
),
'vp_watermarks' => array(
array(
'name' => 'pro_info',
'desc' => '
<div class="vpf-pro-note vpf-settings-info-pro">
<h3>' . esc_html__( 'Premium Only', 'visual-portfolio' ) . '</h3>
<div>
<p class="vpf-pro-note-description">' . esc_html__( 'Protect your works using watermarks', 'visual-portfolio' ) . '</p>
<a class="vpf-pro-note-button" target="_blank" rel="noopener noreferrer" href="' . esc_url( $go_pro_links['watermarks'] ) . '">' . esc_html__( 'Go Pro', 'visual-portfolio' ) . '</a>
</div>
</div>
',
'type' => 'html',
),
),
'vp_social_integrations' => array(
array(
'name' => 'pro_info',
'desc' => '
<div class="vpf-pro-note vpf-settings-info-pro">
<h3>' . esc_html__( 'Premium Only', 'visual-portfolio' ) . '</h3>
<div>
<p class="vpf-pro-note-description">' . esc_html__( 'Social feeds such as Instagram, Youtube, Flickr, Twitter, etc...', 'visual-portfolio' ) . '</p>
<a class="vpf-pro-note-button" target="_blank" rel="noopener noreferrer" href="' . esc_url( $go_pro_links['social'] ) . '">' . esc_html__( 'Go Pro', 'visual-portfolio' ) . '</a>
</div>
</div>
',
'type' => 'html',
),
),
'vp_white_label' => array(
array(
'name' => 'pro_info',
'desc' => '
<div class="vpf-pro-note">
<h3>' . esc_html__( 'Premium Only', 'visual-portfolio' ) . '</h3>
<div>
<p class="vpf-pro-note-description">' . esc_html__( 'Remove our plugin brand and logos from Front and Admin areas', 'visual-portfolio' ) . '</p>
<a class="vpf-pro-note-button" target="_blank" rel="noopener noreferrer" href="' . esc_url( $go_pro_links['white_label'] ) . '">' . esc_html__( 'Go Pro', 'visual-portfolio' ) . '</a>
</div>
</div>
',
'type' => 'html',
),
),
);
self::$cached_settings_fields = apply_filters( 'vpf_settings_fields', $settings_fields );
return self::$cached_settings_fields;
}
/**
* The plugin page handler
*
* @return void
*/
public static function print_settings_page() {
self::$settings_api->admin_enqueue_scripts();
echo '<div class="wrap">';
echo '<h2>' . esc_html__( 'Settings', 'visual-portfolio' ) . '</h2>';
self::$settings_api->show_navigation();
self::$settings_api->show_forms();
echo '</div>';
?>
<script>
(function( $ ) {
// Don't allow adding input number values that > then max attribute and < min attribute.
$('form').on('input', '[type="number"]', function(e) {
var current = parseFloat( this.value );
var min = parseFloat(this.min);
var max = parseFloat(this.max);
if ('' !== this.value) {
if (!Number.isNaN(min) && current < min) {
this.value = min;
}
if (!Number.isNaN(max) && current > max) {
this.value = max;
}
}
});
<?php if ( ! class_exists( 'Visual_Portfolio_Pro' ) ) : ?>
// disable pro inputs.
$('.vpf-settings-control-pro').find('input, textarea').attr('disabled', 'disabled');
<?php endif; ?>
})(jQuery);
</script>
<?php
}
/**
* Get Pages List.
*
* @return array
*/
public static function get_pages_list() {
$options = get_option( 'vp_general' );
$archive_page = $options['portfolio_archive_page'] ?? false;
$pages_list = array(
'' => esc_html__( '-- Select Page --', 'visual-portfolio' ),
);
if ( $archive_page ) {
$archive_title = get_post_field( 'post_title', $archive_page );
$pages_list[ $archive_page ] = $archive_title;
}
return $pages_list;
}
/**
* Get Posts for Select2 archive page field by Ajax.
*
* @return void
*/
public static function get_posts_ajax_callback() {
if ( isset( $_REQUEST['nonce'] ) && wp_verify_nonce( sanitize_key( $_REQUEST['nonce'] ), 'vp-ajax-nonce' ) ) {
$return = array();
$query_opts = array(
'post_status' => 'publish',
'ignore_sticky_posts' => 1,
'posts_per_page' => 50,
'post_type' => 'page',
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
);
if ( isset( $_GET['q'] ) && ! empty( $_GET['q'] ) ) {
$query_opts['s'] = sanitize_text_field( wp_unslash( $_GET['q'] ) );
}
$search_results = new WP_Query( $query_opts );
if ( $search_results->have_posts() ) {
while ( $search_results->have_posts() ) {
$search_results->the_post();
$title = ( mb_strlen( $search_results->post->post_title ) > 50 ) ? mb_substr( $search_results->post->post_title, 0, 49 ) . '...' : $search_results->post->post_title;
$return[] = array( $search_results->post->ID, $title );
}
}
echo wp_json_encode( $return );
}
die;
}
}
new Visual_Portfolio_Settings();

View File

@ -0,0 +1,88 @@
<?php
/**
* Shortcode
*
* @package visual-portfolio/shortcode
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_Shortcode
*/
class Visual_Portfolio_Shortcode {
/**
* Visual_Portfolio_Shortcode constructor.
*/
public function __construct() {
// add shortcode.
add_shortcode( 'visual_portfolio', array( $this, 'get_shortcode_out' ) );
add_shortcode( 'visual_portfolio_filter', array( $this, 'get_shortcode_filter_out' ) );
add_shortcode( 'visual_portfolio_sort', array( $this, 'get_shortcode_sort_out' ) );
}
/**
* Shortcode Output
*
* @param array $atts shortcode attributes.
* @return string
*/
public function get_shortcode_out( $atts = array() ) {
$atts = shortcode_atts(
array(
'id' => '',
'class' => '',
'vc_css' => '',
),
$atts
);
return Visual_Portfolio_Get::get( $atts );
}
/**
* Shortcode Filter Output
*
* @param array $atts shortcode attributes.
* @return string
*/
public function get_shortcode_filter_out( $atts = array() ) {
$atts = shortcode_atts(
array(
'id' => '',
'type' => 'default',
'align' => 'center',
'show_count' => false,
'text_all' => esc_attr__( 'All', 'visual-portfolio' ),
'class' => '',
),
$atts
);
return Visual_Portfolio_Get::get_filter( $atts );
}
/**
* Shortcode Sort Output
*
* @param array $atts shortcode attributes.
* @return string
*/
public function get_shortcode_sort_out( $atts = array() ) {
$atts = shortcode_atts(
array(
'id' => '',
'type' => 'default',
'align' => 'center',
'class' => '',
),
$atts
);
return Visual_Portfolio_Get::get_sort( $atts );
}
}
new Visual_Portfolio_Shortcode();

View File

@ -0,0 +1,155 @@
<?php
/**
* Supported Images in Sitemap (SEO).
*
* @package visual-portfolio/sitemap
*/
/**
* Visual_Portfolio_Sitemap class
*/
class Visual_Portfolio_Sitemap {
/**
* Visual_Portfolio_Sitemap constructor.
*/
public function __construct() {
add_filter( 'aioseo_sitemap_posts', array( $this, 'add_images_to_aioseo_sitemap' ), 10, 1 );
add_filter( 'rank_math/sitemap/urlimages', array( $this, 'add_images_to_sitemap' ), 10, 2 );
add_filter( 'wpseo_sitemap_urlimages', array( $this, 'add_images_to_sitemap' ), 10, 2 );
}
/**
* Add sitemap entries for All In One SEO.
*
* @param array $entries - Sitemap entries.
*
* @return array
*/
public function add_images_to_aioseo_sitemap( $entries ) {
if ( is_array( $entries ) ) {
foreach ( $entries as &$entry ) {
$post_permalink = $entry['loc'];
$post_id = url_to_postid( $post_permalink );
$images = isset( $entry['images'] ) ? $entry['images'] : array();
if ( 0 === $post_id ) {
$archive_page = Visual_Portfolio_Settings::get_option( 'portfolio_archive_page', 'vp_general' );
if ( get_permalink( $archive_page ) === $post_permalink ) {
$post_id = $archive_page;
}
}
$block_images = $this->parse_images_from_blocks( $post_id );
if ( ! empty( $block_images ) ) {
foreach ( $block_images as $image ) {
$images[] = (object) array(
'image:loc' => $image['src'],
'image:caption' => $image['alt'],
'image:title' => $image['title'],
);
}
}
$entry['images'] = $images;
}
}
return $entries;
}
/**
* Add sitemap images for Rank Math and Yoast SEO.
*
* @param array $images - Sitemap Images for current Post.
* @param int $post_id - Post ID.
* @return array
*/
public function add_images_to_sitemap( $images, $post_id ) {
$block_images = $this->parse_images_from_blocks( $post_id );
if ( ! empty( $block_images ) ) {
$images = array_merge( $images, $block_images );
}
return $images;
}
/**
* Parse block images.
*
* @param int $post_id - Post ID.
* @return array
*/
private function parse_images_from_blocks( $post_id ) {
$block_images = array();
if ( $post_id > 0 ) {
$post = get_post( $post_id );
$content_post = $post->post_content;
$parse_blocks = parse_blocks( $content_post );
if ( ! empty( $parse_blocks ) && is_array( $parse_blocks ) ) {
foreach ( $parse_blocks as $block ) {
if (
'visual-portfolio/block' === $block['blockName'] ||
'visual-portfolio/saved' === $block['blockName'] ||
'nk/visual-portfolio' === $block['blockName']
) {
$options = Visual_Portfolio_Get::get_options( $block['attrs'] );
switch ( $options['content_source'] ) {
case 'post-based':
if ( isset( $options['posts_source'] ) ) {
$query_opts = Visual_Portfolio_Get::get_query_params( $options, false, $options['id'] );
$portfolio_query = new WP_Query( $query_opts );
if ( isset( $portfolio_query ) ) {
while ( $portfolio_query->have_posts() ) {
$portfolio_query->the_post();
$image_id = apply_filters( 'vpf_parse_sitemap_image_id_from_blocks', 'attachment' === get_post_type() ? get_the_ID() : get_post_thumbnail_id( get_the_ID() ), get_the_ID() );
$image_alt = get_post_meta( $image_id, '_wp_attachment_image_alt', true );
$block_images[] = array(
'src' => wp_get_attachment_image_url( $image_id, 'full' ),
'alt' => $image_alt,
'title' => get_the_title( $image_id ),
);
}
$portfolio_query->reset_postdata();
// Sometimes, when we use WPBakery Page Builder, without this reset output is wrong.
wp_reset_postdata();
}
}
break;
case 'images':
if ( isset( $options['images'] ) ) {
foreach ( $options['images'] as $image ) {
$image_id = $image['id'];
$image_alt = $image['description'] ?? get_post_meta( $image_id, '_wp_attachment_image_alt', true ) ?? '';
$image_title = $image['title'] ?? get_the_title( $image_id );
$image_url = $image['imgUrl'] ?? wp_get_attachment_image_url( $image_id, 'full' );
$block_images[] = array(
'src' => $image_url,
'alt' => $image_alt,
'title' => $image_title,
);
}
}
break;
}
}
}
}
}
return apply_filters( 'vpf_parse_sitemap_images_from_blocks', $block_images, $post_id );
}
}
new Visual_Portfolio_Sitemap();

View File

@ -0,0 +1,102 @@
<?php
/**
* Supported themes.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_Supported_Themes
*/
class Visual_Portfolio_Supported_Themes {
/**
* Visual_Portfolio_Supported_Themes constructor.
*/
public function __construct() {
add_action( 'wp_enqueue_scripts', array( $this, 'wp_enqueue_scripts' ) );
}
/**
* Get Theme Compatibility Style
*/
public function get_theme_compatibility_style() {
$result = false;
switch ( get_template() ) {
case 'twentytwentytwo':
$result = array(
'name' => 'vpf-twentytwentytwo',
'url' => 'build/assets/css/theme-twentytwentytwo',
);
break;
case 'twentytwentyone':
$result = array(
'name' => 'vpf-twentytwentyone',
'url' => 'build/assets/css/theme-twentytwentyone',
);
break;
case 'twentytwenty':
$result = array(
'name' => 'vpf-twentytwenty',
'url' => 'build/assets/css/theme-twentytwenty',
);
break;
case 'twentynineteen':
$result = array(
'name' => 'vpf-twentynineteen',
'url' => 'build/assets/css/theme-twentynineteen',
);
break;
case 'twentyseventeen':
$result = array(
'name' => 'vpf-twentyseventeen',
'url' => 'build/assets/css/theme-twentyseventeen',
);
break;
case 'twentysixteen':
$result = array(
'name' => 'vpf-twentysixteen',
'url' => 'build/assets/css/theme-twentysixteen',
);
break;
case 'twentyfifteen':
$result = array(
'name' => 'vpf-twentyfifteen',
'url' => 'assets/css/theme-twentyfifteen',
);
break;
case 'airtifact':
$result = array(
'name' => 'vpf-airtifact',
'url' => 'assets/css/theme-airtifact',
);
break;
case 'betheme':
$result = array(
'name' => 'vpf-betheme',
'url' => 'assets/css/theme-betheme',
);
break;
}
return $result;
}
/**
* Enqueue styles
*/
public function wp_enqueue_scripts() {
$theme_compat = $this->get_theme_compatibility_style();
if ( $theme_compat ) {
Visual_Portfolio_Assets::enqueue_style( $theme_compat['name'], $theme_compat['url'] );
wp_style_add_data( $theme_compat['name'], 'rtl', 'replace' );
wp_style_add_data( $theme_compat['name'], 'suffix', '.min' );
}
}
}
new Visual_Portfolio_Supported_Themes();

View File

@ -0,0 +1,121 @@
<?php
/**
* Methods to work with templates.
*
* @package visual-portfolio
*/
/**
* Visual_Portfolio_Templates
*/
class Visual_Portfolio_Templates {
/**
* Include template
*
* @param string $template_name file name.
* @param array $args args for template.
*/
public static function include_template( $template_name, $args = array() ) {
// Allow 3rd party plugin filter template args from their plugin.
$args = apply_filters( 'vpf_include_template_args', $args, $template_name );
if ( ! empty( $args ) && is_array( $args ) ) {
// phpcs:ignore WordPress.PHP.DontExtract.extract_extract
extract( $args );
}
// template in theme folder.
$template = locate_template( array( '/visual-portfolio/' . $template_name . '.php' ) );
// pro plugin template.
if ( ! $template && visual_portfolio()->pro_plugin_path && file_exists( visual_portfolio()->pro_plugin_path . 'templates/' . $template_name . '.php' ) ) {
$template = visual_portfolio()->pro_plugin_path . 'templates/' . $template_name . '.php';
}
// default template.
if ( ! $template ) {
$template = visual_portfolio()->plugin_path . 'templates/' . $template_name . '.php';
}
// Allow 3rd party plugin filter template file from their plugin.
$template = apply_filters( 'vpf_include_template', $template, $template_name, $args );
if ( file_exists( $template ) ) {
include $template;
}
}
/**
* Find css template file
*
* @param string $template_name file name.
* @return string
*/
public static function find_template_styles( $template_name ) {
$template = '';
$template_version = '';
if ( file_exists( get_stylesheet_directory() . '/visual-portfolio/' . $template_name . '.css' ) ) {
// Child Theme (or just theme).
$template = trailingslashit( get_stylesheet_directory_uri() ) . 'visual-portfolio/' . $template_name . '.css';
$template_version = filemtime( get_stylesheet_directory() . '/visual-portfolio/' . $template_name . '.css' );
} elseif ( file_exists( get_template_directory() . '/visual-portfolio/' . $template_name . '.css' ) ) {
// Parent Theme (when parent exists).
$template = trailingslashit( get_template_directory_uri() ) . 'visual-portfolio/' . $template_name . '.css';
$template_version = filemtime( get_template_directory() . '/visual-portfolio/' . $template_name . '.css' );
} elseif ( visual_portfolio()->pro_plugin_path && file_exists( visual_portfolio()->pro_plugin_path . 'templates/' . $template_name . '.css' ) ) {
// PRO plugin folder.
$template = visual_portfolio()->pro_plugin_url . 'templates/' . $template_name . '.css';
$template_version = filemtime( visual_portfolio()->pro_plugin_path . 'templates/' . $template_name . '.css' );
} elseif ( file_exists( visual_portfolio()->plugin_path . 'templates/' . $template_name . '.css' ) ) {
// Default file in plugin folder.
$template = visual_portfolio()->plugin_url . 'templates/' . $template_name . '.css';
$template_version = filemtime( visual_portfolio()->plugin_path . 'templates/' . $template_name . '.css' );
}
return array(
'path' => $template,
'version' => $template_version,
);
}
/**
* Include template style
*
* @param string $handle style handle name.
* @param string $template_name file name.
* @param array $deps dependencies array.
* @param string|bool|null $ver version string.
* @param string $media media string.
*/
public static function include_template_style( $handle, $template_name, $deps = array(), $ver = false, $media = 'all' ) {
$template = visual_portfolio()->find_template_styles( $template_name );
$is_min = false;
// maybe find minified style.
if ( ! $template['path'] ) {
$template = visual_portfolio()->find_template_styles( $template_name . '.min' );
$is_min = true;
}
// Get dynamic version.
if ( ! $ver && $template['version'] ) {
$ver = $template['version'];
}
if ( ! $ver ) {
$ver = VISUAL_PORTFOLIO_VERSION;
}
// Allow 3rd party plugin filter template file from their plugin.
$template['path'] = apply_filters( 'vpf_include_template_style', $template['path'], $template_name, $deps, $ver, $media );
if ( $template['path'] ) {
wp_enqueue_style( $handle, $template['path'], $deps, $ver, $media );
wp_style_add_data( $handle, 'rtl', 'replace' );
if ( $is_min ) {
wp_style_add_data( $handle, 'suffix', '.min' );
}
}
}
}

View File

@ -0,0 +1,206 @@
<?php
/**
* Welcome Screen.
*
* @package visual-portfolio
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Visual_Portfolio_Welcome_Screen
*/
class Visual_Portfolio_Welcome_Screen {
/**
* Visual_Portfolio_Welcome_Screen constructor.
*/
public function __construct() {
add_action( 'admin_init', array( $this, 'redirect_to_welcome_screen' ) );
add_action( 'admin_menu', array( $this, 'welcome_screen_page' ) );
add_action( 'admin_head', array( $this, 'welcome_screen_remove_page' ) );
}
/**
* Redirect to Welcome page after activation.
*/
public function redirect_to_welcome_screen() {
// Bail if no activation redirect.
if ( ! get_transient( '_visual_portfolio_welcome_screen_activation_redirect' ) ) {
return;
}
// Delete the redirect transient.
delete_transient( '_visual_portfolio_welcome_screen_activation_redirect' );
// Bail if activating from network, or bulk.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( is_network_admin() || isset( $_GET['activate-multi'] ) ) {
return;
}
// Redirect to welcome page.
wp_safe_redirect( add_query_arg( array( 'page' => 'visual-portfolio-welcome' ), admin_url( Visual_Portfolio_Custom_Post_Type::get_menu_slug() ) ) );
}
/**
* Add welcome screen page.
*/
public function welcome_screen_page() {
add_submenu_page(
Visual_Portfolio_Custom_Post_Type::get_menu_slug(),
esc_html__( 'Visual Portfolio Welcome Screen', 'visual-portfolio' ),
esc_html__( 'Visual Portfolio Welcome Screen', 'visual-portfolio' ),
'manage_options',
'visual-portfolio-welcome',
array( $this, 'welcome_screen_page_content' )
);
}
/**
* Remove welcome screen page from admin menu.
*/
public function welcome_screen_remove_page() {
remove_submenu_page( Visual_Portfolio_Custom_Post_Type::get_menu_slug(), 'visual-portfolio-welcome' );
}
/**
* Add welcome screen page content.
*/
public function welcome_screen_page_content() {
if ( function_exists( 'print_emoji_detection_script' ) ) {
print_emoji_detection_script();
}
$go_pro_links = array(
'head' => Visual_Portfolio_Admin::get_plugin_site_url(
array(
'utm_medium' => 'welcome_page',
'utm_campaign' => 'go_pro_head',
)
),
'more_features' => Visual_Portfolio_Admin::get_plugin_site_url(
array(
'sub_path' => '',
'utm_medium' => 'welcome_page',
'utm_campaign' => 'more_features',
)
),
'docs' => Visual_Portfolio_Admin::get_plugin_site_url(
array(
'sub_path' => 'docs/getting-started',
'utm_medium' => 'welcome_page',
'utm_campaign' => 'docs',
)
),
'foot' => Visual_Portfolio_Admin::get_plugin_site_url(
array(
'utm_medium' => 'settings_page',
'utm_campaign' => 'go_pro_foot',
)
),
);
?>
<div class="vpf-welcome-screen">
<div class="vpf-welcome-head">
<img class="vpf-welcome-head-background" src="<?php echo esc_url( visual_portfolio()->plugin_url . 'assets/admin/images/admin-welcome-background.jpg' ); ?>" alt="<?php echo esc_attr__( 'Visual Portfolio', 'visual-portfolio' ); ?>">
<h2 class="vpf-welcome-head-logo">
<i class="dashicons-visual-portfolio"></i>
<?php echo esc_html__( 'Visual Portfolio', 'visual-portfolio' ); ?>
</h2>
<div class="vpf-welcome-subtitle"><?php echo esc_html__( 'Thank you for choosing Visual Portfolio - The Modern Gallery, Posts Grid and Portfolio Plugin for WordPress.', 'visual-portfolio' ); ?></div>
<div class="vpf-welcome-head-pro-info">
<div><strong><?php echo esc_html__( 'You\'re using free Visual Portfolio plugin. Enjoy! 🙂', 'visual-portfolio' ); ?></strong></div>
<div>
<?php
// translators: %s - pro link.
echo sprintf( esc_html__( 'Want to get more power with Pro? Visit %s', 'visual-portfolio' ), '<a target="_blank" rel="noopener noreferrer" href="' . esc_url( $go_pro_links['head'] ) . '">visualportfolio.co/pricing</a>' );
?>
</div>
</div>
</div>
<div class="vpf-welcome-content">
<h2 class="vpf-welcome-content-title"><?php echo esc_html__( 'Main Features & Solutions', 'visual-portfolio' ); ?></h2>
<ul class="vpf-welcome-content-features">
<li>
<span>🏆</span>
<strong><?php echo esc_html__( 'Visual Gallery Builder', 'visual-portfolio' ); ?></strong>
<p><?php echo esc_html__( 'Build your portfolio and gallery blocks with no coding knowledge. Thanks to Gutenberg page builder you are able to create and customize galleries visually.', 'visual-portfolio' ); ?></p>
</li>
<li>
<span>🚀</span>
<strong><?php echo esc_html__( 'Optimized to be Fast as Native', 'visual-portfolio' ); ?></strong>
<p><?php echo esc_html__( 'Due to the modular code structure, all scripts and styles are loaded only when they are needed for the current page that displays your gallery.', 'visual-portfolio' ); ?></p>
</li>
<li>
<span>📱</span>
<strong><?php echo esc_html__( 'Layouts', 'visual-portfolio' ); ?></strong>
<p><?php echo esc_html__( 'Our gallery plugin shipped with popular layouts such as Masonry and Justified (Flickr).', 'visual-portfolio' ); ?></p>
</li>
<li>
<span>🎨</span>
<strong><?php echo esc_html__( 'Visual Effects', 'visual-portfolio' ); ?></strong>
<p><?php echo esc_html__( 'Showcase your projects ang gallery images with clean and beautiful visual styles.', 'visual-portfolio' ); ?></p>
</li>
<li>
<span>⚙️</span>
<strong><?php echo esc_html__( 'Easy to Customize', 'visual-portfolio' ); ?></strong>
<p><?php echo esc_html__( 'The gallery block with live preview includes a lot of design settings that are point-and-click, no coding knowledge required.', 'visual-portfolio' ); ?></p>
</li>
<li>
<span>💎</span>
<strong><?php echo esc_html__( 'Posts Query Builder', 'visual-portfolio' ); ?></strong>
<p><?php echo esc_html__( 'Display posts, portfolios, and any other post types, filter by taxonomies, author, date ranges, and much more options.', 'visual-portfolio' ); ?></p>
</li>
<li>
<span>⚡</span>
<strong><?php echo esc_html__( 'Powerful Lightbox', 'visual-portfolio' ); ?></strong>
<p><?php echo esc_html__( 'Visual Portfolio uses scripts for lightboxes that is high performance, mobile optimized and retina-ready.', 'visual-portfolio' ); ?></p>
</li>
<li>
<span>📹</span>
<strong><?php echo esc_html__( 'Video and 🎵 Audio Support', 'visual-portfolio' ); ?></strong>
<p><?php echo esc_html__( 'Present not only photos, but also audios and videos within a single gallery.', 'visual-portfolio' ); ?></p>
</li>
</ul>
<hr>
<div class="vpf-welcome-content-buttons">
<a target="_blank" rel="noopener noreferrer" href="<?php echo esc_url( $go_pro_links['more_features'] ); ?>"><?php echo esc_html__( 'More Features', 'visual-portfolio' ); ?></a>
<a target="_blank" rel="noopener noreferrer" href="<?php echo esc_url( $go_pro_links['docs'] ); ?>"><?php echo esc_html__( 'Documentation', 'visual-portfolio' ); ?></a>
</div>
</div>
<div class="vpf-welcome-foot-pro-info">
<h2>
<?php echo esc_html__( 'Upgrade to Visual Portfolio Pro', 'visual-portfolio' ); ?>
<br>
<?php echo esc_html__( 'and Get More Power!', 'visual-portfolio' ); ?>
</h2>
<ul>
<li><?php echo esc_html__( 'Social Feeds', 'visual-portfolio' ); ?></li>
<li><?php echo esc_html__( 'Stylish Effects', 'visual-portfolio' ); ?></li>
<li><?php echo esc_html__( 'Watermarks Protection', 'visual-portfolio' ); ?></li>
<li><?php echo esc_html__( 'Age Gate Protection', 'visual-portfolio' ); ?></li>
<li><?php echo esc_html__( 'Instagram-like Image Filters', 'visual-portfolio' ); ?></li>
<li><?php echo esc_html__( 'Advanced Query Settings', 'visual-portfolio' ); ?></li>
<li><?php echo esc_html__( 'Popup for Posts and Pages', 'visual-portfolio' ); ?></li>
<li><?php echo esc_html__( 'Popup Deep Linking', 'visual-portfolio' ); ?></li>
<li><?php echo esc_html__( 'White Label', 'visual-portfolio' ); ?></li>
<li><?php echo esc_html__( 'And much more...', 'visual-portfolio' ); ?></li>
</ul>
<a target="_blank" rel="noopener noreferrer" href="<?php echo esc_url( $go_pro_links['foot'] ); ?>"><?php echo esc_html__( 'Upgrade to PRO Now', 'visual-portfolio' ); ?></a>
</div>
</div>
<?php
}
}
new Visual_Portfolio_Welcome_Screen();