get_settings_for_display();
$clean = [];
foreach ($keys as $key) {
if (array_key_exists($key, $all_settings)) {
$clean[$key] = $all_settings[$key];
}
}
return $clean;
}
/**
* Convert array setting to comma-separated string.
*
* @since 1.0.0
* @param array $settings Settings array.
* @param string $key Key of the setting to convert.
* @return array Modified settings array.
*/
protected function arrayToComma(array $settings, string $key): array
{
if (isset($settings[$key]) && is_array($settings[$key])) {
$settings[$key] = implode(',', array_map('sanitize_text_field', $settings[$key]));
}
return $settings;
}
/**
* Convert multiple array settings to comma-separated strings.
*
* @since 1.0.0
* @param array $settings Settings array.
* @param array $keys Keys to convert.
* @return array Modified settings array.
*/
protected function arraysToComma(array $settings, array $keys): array
{
foreach ($keys as $key) {
$settings = $this->arrayToComma($settings, $key);
}
return $settings;
}
/**
* Sanitize settings based on type definitions.
*
* Supported types:
* - 'int': Integer value (absint)
* - 'bool': Boolean value
* - 'text': Sanitized text field
* - 'url': Sanitized URL
* - 'html': Allowed HTML (wp_kses_post)
* - 'css_class': CSS class name
* - 'css_style': Inline CSS styles
* - 'slug': URL slug
* - 'order': ASC or DESC
* - 'orderby': date, title, modified, or rand
*
* @since 1.0.0
* @param array $settings Settings array.
* @param array $sanitizers Associative array of key => type.
* @return array Sanitized settings array.
*/
protected function sanitizeSettings(array $settings, array $sanitizers): array
{
foreach ($sanitizers as $key => $type) {
if (!isset($settings[$key])) {
continue;
}
$value = $settings[$key];
switch ($type) {
case 'int':
$settings[$key] = absint($value);
break;
case 'bool':
$settings[$key] = (bool) $value;
break;
case 'text':
$settings[$key] = sanitize_text_field($value);
break;
case 'url':
$settings[$key] = esc_url_raw($value);
break;
case 'html':
$settings[$key] = wp_kses_post($value);
break;
case 'css_class':
$settings[$key] = sanitize_html_class($value);
break;
case 'css_style':
$settings[$key] = $this->sanitizeCssStyle($value);
break;
case 'slug':
$settings[$key] = sanitize_title($value);
break;
case 'order':
$settings[$key] = in_array(strtoupper($value), ['ASC', 'DESC'], true)
? strtoupper($value)
: 'DESC';
break;
case 'orderby':
$allowed = ['date', 'title', 'modified', 'rand', 'menu_order'];
$settings[$key] = in_array($value, $allowed, true) ? $value : 'date';
break;
}
}
return $settings;
}
/**
* Sanitize inline CSS style attribute.
*
* Removes potentially dangerous CSS functions and HTML tags.
*
* @since 1.0.0
* @param string $style Raw CSS style string.
* @return string Sanitized CSS style.
*/
protected function sanitizeCssStyle(string $style): string
{
// Remove any HTML tags
$style = wp_strip_all_tags($style);
// Remove potentially dangerous CSS functions
$dangerous = ['expression', 'javascript:', 'behavior:', 'vbscript:', 'url('];
foreach ($dangerous as $pattern) {
if (stripos($style, $pattern) !== false) {
return '';
}
}
return esc_attr($style);
}
/**
* Wrap output in a container div.
*
* @since 1.0.0
* @param string $content HTML content to wrap.
* @param string $class Additional CSS class (optional).
* @return string Wrapped content.
*/
protected function wrapOutput(string $content, string $class = ''): string
{
$classes = 'wpdm-elementor-widget';
if (!empty($class)) {
$classes .= ' ' . sanitize_html_class($class);
}
return sprintf('
%s
', esc_attr($classes), $content);
}
/**
* Convert boolean-like settings (0/1 strings) to actual booleans.
*
* @since 1.0.0
* @param array $settings Settings array.
* @param array $keys Keys to convert.
* @return array Modified settings array.
*/
protected function toBooleans(array $settings, array $keys): array
{
foreach ($keys as $key) {
if (isset($settings[$key])) {
$settings[$key] = (bool) $settings[$key];
}
}
return $settings;
}
/**
* Helper to get WPDM category terms for controls.
*
* @since 1.0.0
* @return array Category slug => name pairs.
*/
protected function getCategoryOptions(): array
{
return get_wpdmcategory_terms();
}
/**
* Helper to get link template options for controls.
*
* @since 1.0.0
* @return array Template options.
*/
protected function getLinkTemplateOptions(): array
{
return get_elementor_link_templates();
}
/**
* Helper to get WPDM tag terms for controls.
*
* @since 1.0.0
* @return array Tag slug => name pairs.
*/
protected function getTagOptions(): array
{
if (function_exists('get_wpdm_tag_terms')) {
return get_wpdm_tag_terms();
}
$tags = get_terms(['taxonomy' => 'wpdmtag', 'hide_empty' => false]);
$options = [];
if (is_array($tags) && !is_wp_error($tags)) {
foreach ($tags as $tag) {
$options[$tag->slug] = $tag->name;
}
}
return $options;
}
/**
* Get the SELECT2 AJAX configuration for package search.
*
* @since 1.0.0
* @return array SELECT2 options array.
*/
protected function getPackageSearchConfig(): array
{
return [
'placeholder' => __('Type package title', WPDM_ELEMENTOR),
'ajax' => [
'url' => get_rest_url(null, 'wpdm-elementor/v1/search-packages'),
'dataType' => 'json',
'delay' => 250,
],
'minimumInputLength' => 2,
];
}
/**
* Register container style controls section.
*
* Adds background, border, and shadow controls for the widget container.
*
* @since 1.3.0
* @param string $section_id Section ID (default: 'style_container').
* @param string $section_label Section label.
* @return void
*/
protected function registerContainerStyleControls(
string $section_id = 'style_container',
string $section_label = ''
): void {
if (empty($section_label)) {
$section_label = __('Container Style', WPDM_ELEMENTOR);
}
$this->start_controls_section(
$section_id,
[
'label' => $section_label,
'tab' => Controls_Manager::TAB_STYLE,
]
);
$this->add_control(
'container_background',
[
'label' => __('Background Color', WPDM_ELEMENTOR),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} .wpdm-elementor-widget' => 'background-color: {{VALUE}};',
],
]
);
$this->add_responsive_control(
'container_padding',
[
'label' => __('Padding', WPDM_ELEMENTOR),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => ['px', 'em', '%'],
'selectors' => [
'{{WRAPPER}} .wpdm-elementor-widget' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
]
);
$this->add_responsive_control(
'container_margin',
[
'label' => __('Margin', WPDM_ELEMENTOR),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => ['px', 'em', '%'],
'selectors' => [
'{{WRAPPER}} .wpdm-elementor-widget' => 'margin: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
]
);
$this->add_group_control(
Group_Control_Border::get_type(),
[
'name' => 'container_border',
'selector' => '{{WRAPPER}} .wpdm-elementor-widget',
]
);
$this->add_responsive_control(
'container_border_radius',
[
'label' => __('Border Radius', WPDM_ELEMENTOR),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => ['px', '%'],
'selectors' => [
'{{WRAPPER}} .wpdm-elementor-widget' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
]
);
$this->add_group_control(
Group_Control_Box_Shadow::get_type(),
[
'name' => 'container_box_shadow',
'selector' => '{{WRAPPER}} .wpdm-elementor-widget',
]
);
$this->end_controls_section();
}
/**
* Register typography style controls section.
*
* Adds typography controls for title and content.
*
* @since 1.3.0
* @param string $selector CSS selector for the typography target.
* @return void
*/
protected function registerTypographyControls(string $selector = '.wpdm-elementor-widget'): void
{
$this->start_controls_section(
'style_typography',
[
'label' => __('Typography', WPDM_ELEMENTOR),
'tab' => Controls_Manager::TAB_STYLE,
]
);
$this->add_control(
'title_color',
[
'label' => __('Title Color', WPDM_ELEMENTOR),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} ' . $selector . ' h1, {{WRAPPER}} ' . $selector . ' h2, {{WRAPPER}} ' . $selector . ' h3, {{WRAPPER}} ' . $selector . ' h4, {{WRAPPER}} ' . $selector . ' h5, {{WRAPPER}} ' . $selector . ' .title' => 'color: {{VALUE}};',
],
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'title_typography',
'label' => __('Title Typography', WPDM_ELEMENTOR),
'selector' => '{{WRAPPER}} ' . $selector . ' h1, {{WRAPPER}} ' . $selector . ' h2, {{WRAPPER}} ' . $selector . ' h3, {{WRAPPER}} ' . $selector . ' h4, {{WRAPPER}} ' . $selector . ' h5, {{WRAPPER}} ' . $selector . ' .title',
]
);
$this->add_control(
'text_color',
[
'label' => __('Text Color', WPDM_ELEMENTOR),
'type' => Controls_Manager::COLOR,
'separator' => 'before',
'selectors' => [
'{{WRAPPER}} ' . $selector => 'color: {{VALUE}};',
],
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'text_typography',
'label' => __('Text Typography', WPDM_ELEMENTOR),
'selector' => '{{WRAPPER}} ' . $selector,
]
);
$this->add_control(
'link_color',
[
'label' => __('Link Color', WPDM_ELEMENTOR),
'type' => Controls_Manager::COLOR,
'separator' => 'before',
'selectors' => [
'{{WRAPPER}} ' . $selector . ' a' => 'color: {{VALUE}};',
],
]
);
$this->add_control(
'link_hover_color',
[
'label' => __('Link Hover Color', WPDM_ELEMENTOR),
'type' => Controls_Manager::COLOR,
'selectors' => [
'{{WRAPPER}} ' . $selector . ' a:hover' => 'color: {{VALUE}};',
],
]
);
$this->end_controls_section();
}
/**
* Check if we are in Elementor editor mode.
*
* Useful for showing placeholder content in the editor.
*
* @since 1.3.0
* @return bool True if in editor mode.
*/
protected function isEditorMode(): bool
{
return \Elementor\Plugin::$instance->editor->is_edit_mode();
}
/**
* Render placeholder for empty content in editor.
*
* @since 1.3.0
* @param string $message Message to display.
* @return void
*/
protected function renderEditorPlaceholder(string $message): void
{
if ($this->isEditorMode()) {
printf(
'%s
',
esc_html($message)
);
}
}
}