<?php
/**
* @package Unlimited Elements
* @author UniteCMS Enhanced
* @copyright Copyright (c) 2016-2024 UniteCMS
* @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPLv2 or later
*/
if(!defined('ABSPATH')) exit;
class UniteCreatorSchema {
private static $arrCollectedSchemaData = array();
private static $arrSchemas = array();
private $debugFields = array();
private static $showDebug = false;
private $arrSchemaDebugContent = array();
private $lastCustomSchema;
private $objAddon;
private static $showJsonErrorOnce = false;
const ROLE_TITLE = "title";
const ROLE_DESCRIPTION = "description";
const ROLE_HEADING = "heading";
const ROLE_LINK = "link";
const ROLE_CONTENT = "content";
const ROLE_IMAGE = "image";
const ROLE_EXTRA_FIELD1 = "field1";
const ROLE_EXTRA_FIELD2 = "field2";
const ROLE_EXTRA_FIELD3 = "field3";
const ROLE_EXTRA_FIELD4 = "field4";
const ROLE_AUTO = "_auto_";
const CONTENT_FIELDS = "content_fields";
const SCHEMA_ORG_SITE = "https://schema.org";
const MULTIPLE_SCHEMA_NAME = "ue_schema";
/**
* get roles keys
*/
private function getArrRolesKeys(){
$arrRoles = array(
self::ROLE_TITLE,
self::ROLE_DESCRIPTION,
self::ROLE_HEADING,
self::ROLE_LINK,
self::ROLE_CONTENT,
self::ROLE_IMAGE,
self::ROLE_EXTRA_FIELD1,
self::ROLE_EXTRA_FIELD2,
self::ROLE_EXTRA_FIELD3,
self::ROLE_EXTRA_FIELD4
);
return($arrRoles);
}
/**
* get schemas array
*/
private function getArrSchemas(){
if(!empty(self::$arrSchemas))
return(self::$arrSchemas);
$arrSchemas = array(
array(
"type"=>"FAQPage",
"title"=>"FAQ",
"multiple"=>true
),
array(
"type"=>"Person",
"title"=>"List Of Person"
),
array(
"type"=>"HowTo"
),
array(
"type"=>"Recipe"
),
array(
"type"=>"Course",
"title"=>"Courses"
),
array(
"type"=>"Book",
"title"=>"Books"
),
array(
"type"=>"ItemList",
"title"=>"Items List",
"multiple"=>true
),
array(
"type"=>"Event"
),
array(
"type"=>"Place",
"title"=>"Places"
),
array(
"type"=>"Product",
"title"=>"Products"
),
array(
"type"=>"TouristDestination",
"title"=>"Tourist Destinations"
),
array(
"type"=>"EventSeries",
"title"=>"Event Series",
"multiple"=>true
),
array(
"type"=>"MusicPlaylist",
"title"=>"Music Playlist",
"multiple"=>true
),
array(
"type"=>"SearchResultsPage",
"title"=>"Search Results Page",
"multiple"=>true
),
array(
"type"=>"NewsArticle",
"title"=>"News Article"
)
);
$arrOutput = array();
foreach($arrSchemas as $schama){
$type = UniteFunctionsUC::getVal($schama, "type");
$name = strtolower($type);
$title = UniteFunctionsUC::getVal($schama, "title");
if(!empty($title))
$title .= " ($type)";
else
$title = $type;
$schama["name"] = $name;
$schama["title"] = $title;
$arrOutput[$name] = $schama;
}
self::$arrSchemas = $arrOutput;
return(self::$arrSchemas);
}
/**
* get schema options by name
*/
private function getSchemaOptionsByName($name){
$arrSchemas = $this->getArrSchemas();
$arrSchema = UniteFunctionsUC::getVal($arrSchemas, $name);
return($arrSchema);
}
/**
* set the addon
*/
public function setObjAddon($addon){
//$this->objAddon = new UniteCreatorAddon();
$this->objAddon = $addon;
}
/**
* convert the items from post list name
*/
private function convertWidgetItems($arrItems, $paramName){
$arrItemsConverted = array();
foreach($arrItems as $item){
$arrItem = UniteFunctionsUC::getVal($item, "item");
$arrItem = UniteFunctionsUC::getVal($arrItem, $paramName);
$arrItemsConverted[] = $arrItem;
}
return($arrItemsConverted);
}
/**
* put schema items by post
*/
private function putSchemaByPost($schemaType, $arrItems, $arrSettings){
$postListName = UniteFunctionsUC::getVal($arrSettings, "post_list_name");
$arrItemsConverted = $this->convertWidgetItems($arrItems, $postListName);
$arrParamsItems = array();
$this->putSchemaItems($schemaType, $arrItemsConverted, $arrParamsItems , $arrSettings);
}
/**
* put schema items
*/
public function putSchemaItemsByType($type, $schemaType, $arrItems, $arrParamsItems, $arrSettings){
$arrSettings["item_type"] = $type;
switch($type){
case UniteCreatorAddon::ITEMS_TYPE_POST:
$this->putSchemaByPost($schemaType, $arrItems, $arrSettings);
break;
case UniteCreatorAddon::ITEMS_TYPE_MULTISOURCE:
$this->putSchemaItems($schemaType, $arrItems, $arrParamsItems , $arrSettings);
break;
}
}
/**
* put html items schema
*/
public function putSchemaItems($schemaType, $arrItems, $paramsItems, $arrSettings){
if(empty($schemaType))
$schemaType = "faqpage";
$title = UniteFunctionsUC::getVal($arrSettings, "title");
$title = wp_strip_all_tags($title);
if(!isset(self::$arrCollectedSchemaData[$schemaType]))
self::$arrCollectedSchemaData[$schemaType] = array("addons"=>array());
self::$arrCollectedSchemaData[$schemaType]["addons"][] = array(
"items"=>$arrItems,
"params"=>$paramsItems,
"settings"=>$arrSettings
);
//---- add title
$existingTitle = "";
if(isset(self::$arrCollectedSchemaData[$schemaType]["title"]))
$existingTitle = self::$arrCollectedSchemaData[$schemaType]["title"];
if(!empty($title) && empty($existingTitle))
self::$arrCollectedSchemaData[$schemaType]["title"] = $title;
$showDebug = UniteFunctionsUC::getVal($arrSettings, "debug");
$showDebug = UniteFunctionsUC::strToBool($showDebug);
//set the debug
if($showDebug === true){
self::$showDebug = true;
$this->showDebugMessage();
}
}
/**
* show schema code
*/
public function showAddonSchema(){
if(self::$showDebug == false){
self::$showDebug = HelperUC::hasPermissionsFromQuery("ucschemadebug");
}
if(empty(self::$arrCollectedSchemaData))
return false;
foreach (self::$arrCollectedSchemaData as $schemaType => $data)
$this->putAddonSchema($schemaType, $data);
self::$arrCollectedSchemaData = array();
}
/**
* generate schema code
*/
private function putAddonSchema($schemaType, $data) {
$arrSchema = $this->generateSchemaByType($schemaType, $data);
if(empty($arrSchema))
return;
//clean certain keys that not goo to leave empty
$arrSchema = $this->cleanSchemaArray($arrSchema, array(
'description',
'image',
'sameAs',
));
$jsonItems = json_encode($arrSchema, JSON_UNESCAPED_UNICODE);
if($jsonItems === false)
return(false);
$strSchema = '<script type="application/ld+json">' . $jsonItems . '</script>';
//show debug by url
if(self::$showDebug == true)
$this->showDebugSchema($schemaType, $arrSchema);
echo $strSchema;
}
private function a____________SCHEMA_ITEM_CONTENT________(){}
/**
* sanitize item value
*/
private function sanitizeItemValue($value, $role){
switch($role){
case self::ROLE_TITLE:
case self::ROLE_CONTENT:
case self::ROLE_HEADING:
case self::ROLE_DESCRIPTION:
case self::ROLE_EXTRA_FIELD1:
case self::ROLE_EXTRA_FIELD2:
case self::ROLE_EXTRA_FIELD3:
case self::ROLE_EXTRA_FIELD4:
$value = wp_strip_all_tags($value);
$value = trim($value);
break;
case self::ROLE_IMAGE:
if($value == GlobalsUC::$url_no_image_placeholder)
$value = "";
case self::ROLE_LINK:
$value = UniteFunctionsUC::sanitize($value, UniteFunctionsUC::SANITIZE_URL);
break;
}
return($value);
}
/**
* get extra field placeholder type. meta:key or term:key
* Enter description here ...
*/
private function getExtraFieldPlaceholderType($fieldName){
if(strpos($fieldName, "meta:") !== false)
return("meta");
if(strpos($fieldName, "term:") !== false)
return("term");
return("");
}
/**
* get item extra field
*/
private function getItemExtraFieldValue($fieldName, $postID, $fieldNameType){
if(empty($postID))
return("");
//get the meta
if($fieldNameType == "meta"){
$metaKey = str_replace("meta:", "", $fieldName);
$metaValue = UniteFunctionsWPUC::getPostCustomField($postID, $metaKey);
if(empty($metaValue))
return("");
if(is_array($metaValue))
$metaValue = implode(',', $metaValue);
if(is_string($metaValue) == false)
$metaValue = "";
return($metaValue);
}
//get the term
if($fieldNameType == "term"){
$taxonomu = str_replace("term:", "", $fieldName);
$termName = UniteFunctionsWPUC::getPostTerm_firstTermName($postID, $taxonomu);
return($termName);
}
return("");
}
/**
* get item schema content
*/
private function getItemSchemaContent($item, $arrFieldsByRoles, $itemType){
if(isset($item["item"]))
$item = $item["item"];
$arrContent = array();
$postID = null;
switch($itemType){
case UniteCreatorAddon::ITEMS_TYPE_POST:
$postID = UniteFunctionsUC::getVal($item, "id");
break;
case UniteCreatorAddon::ITEMS_TYPE_MULTISOURCE:
$itemSource = UniteFunctionsUC::getVal($item, "item_source");
if($itemSource == "posts")
$postID = UniteFunctionsUC::getVal($item, "object_id");
break;
}
foreach($arrFieldsByRoles as $role => $fieldName){
//get value
//meta or term type
$extraFieldType = $this->getExtraFieldPlaceholderType($fieldName);
if(!empty($extraFieldType)) //take the value from the extra field
$value = $this->getItemExtraFieldValue($fieldName, $postID, $extraFieldType);
else
//take the value from the item
$value = UniteFunctionsUC::getVal($item, $fieldName);
$value = $this->sanitizeItemValue($value, $role);
$arrContent[$role] = $value;
}
return($arrContent);
}
private function a____________MAP_FIELDS________(){}
/**
* get fields by type
*/
private function getParamNamesByTypes($params, $type){
$arrFieldNames = array();
foreach($params as $param){
$fieldType = UniteFunctionsUC::getVal($param, "type");
//content fields check
if($type == self::CONTENT_FIELDS){
$typeFound = in_array($fieldType, array(UniteCreatorDialogParam::PARAM_TEXTAREA,
UniteCreatorDialogParam::PARAM_EDITOR)
);
}else
$typeFound = $fieldType == $type;
if($typeFound == false)
continue;
$name = UniteFunctionsUC::getVal($param, "name");
$arrFieldNames[$name] = $fieldType;
}
return($arrFieldNames);
}
/**
* get fields by the params roles
*/
private function getFieldsByRoles($params){
$arrRoles = array();
$roleTitle = "";
$roleHeading = "";
$roleDescription = "";
$roleLink = "";
$roleImage = "";
//get title
$arrTextParams = $this->getParamNamesByTypes($params, UniteCreatorDialogParam::PARAM_TEXTFIELD);
if(isset($arrTextParams["title"]))
$roleTitle = "title";
else
$roleTitle = UniteFunctionsUC::getFirstNotEmptyKey($arrTextParams);
if(!empty($roleTitle))
unset($arrTextParams[$roleTitle]);
//guess heading
if(isset($arrTextParams["heading"])){
$roleHeading = "heading";
unset($arrTextParams[$roleHeading]);
}
//get description from text or content
$arrContentParams = $this->getParamNamesByTypes($params, self::CONTENT_FIELDS);
//get from text params with name: 'description'
if(isset($arrTextParams["description"])){
$roleDescription = $arrTextParams["description"];
unset($arrTextParams["description"]);
}
//get from content params
if(empty($roleDescription)){
if(isset($arrContentParams["description"]))
$roleDescription = "description";
else
$roleDescription = UniteFunctionsUC::getFirstNotEmptyKey($arrContentParams); //get first key
if(!empty($roleDescription))
unset($arrContentParams[$roleDescription]);
}
//guess content - get first field from the content
if(!empty($arrContentParams))
$roleContent = UniteFunctionsUC::getFirstNotEmptyKey($arrContentParams);
//copy from description if empty
if(empty($roleContent))
$roleContent = $roleDescription;
//guess link
$arrLinkParams = $this->getParamNamesByTypes($params, UniteCreatorDialogParam::PARAM_LINK);
if(!empty($arrLinkParams))
$roleLink = UniteFunctionsUC::getFirstNotEmptyKey($arrLinkParams);
//guess image
$arrImageParams = $this->getParamNamesByTypes($params, UniteCreatorDialogParam::PARAM_IMAGE);
if(isset($arrImageParams["image"]))
$roleImage = "image";
if(!empty($arrImageParams))
$roleImage = UniteFunctionsUC::getFirstNotEmptyKey($arrImageParams);
//return the params
$arrOutput = array(
self::ROLE_TITLE => $roleTitle,
self::ROLE_DESCRIPTION => $roleDescription,
self::ROLE_HEADING => $roleHeading,
self::ROLE_CONTENT => $roleContent,
self::ROLE_LINK => $roleLink,
self::ROLE_IMAGE => $roleImage
);
return($arrOutput);
}
/**
* check the extra fields fields mapping, modify to meta:key or term:key
*/
private function getFieldsByRoles_checkMetaTermKeys($arrFieldsByRoles, $fieldMap, $roleKey, $arrSettings){
//--- check if related to meta and term
if($fieldMap != "meta" && $fieldMap != "term")
return($arrFieldsByRoles);
//---- get the extra field key
$fieldKey = UniteFunctionsUC::getVal($arrSettings, "extrafieldkey_" . $roleKey);
if(empty($fieldKey))
return($arrFieldsByRoles);
//check the key and sanitize
switch($fieldMap){
case "meta": // check that meta field is valid
$isValid = UniteFunctionsUC::isMetaKeyValid($fieldKey);
if($isValid == false){
$fieldKey = UniteFunctionsUC::sanitize($fieldKey, UniteFunctionsUC::SANITIZE_HTML);
UniteFunctionsUC::throwError("The meta name: $fieldKey is not valid");
}
break;
case "term": //check that taxonomy is valid
$isValid = UniteFunctionsUC::isTaxonomyNameValid($fieldKey);
if($isValid == false){
$fieldKey = UniteFunctionsUC::sanitize($fieldKey, UniteFunctionsUC::SANITIZE_HTML);
UniteFunctionsUC::throwError("The taxonomy name: $fieldKey is not valid");
}
break;
}
//put the new field with the key
$arrFieldsByRoles[$roleKey] = $fieldMap.":".$fieldKey;
return($arrFieldsByRoles);
}
/**
* Get final fields mapping by roles, using manual settings if enabled.
*
* @param array $paramsItems The widget params items
* @param array $arrSettings The widget settings
* @return array The final mapping: role => paramName
*/
private function getFieldsByRolesFinal($paramsItems, $arrSettings) {
$itemsType = UniteFunctionsUC::getVal($arrSettings, "item_type");
switch($itemsType){
case UniteCreatorAddon::ITEMS_TYPE_POST:
$arrFieldsByRoles = array(
self::ROLE_TITLE => "title",
self::ROLE_DESCRIPTION => "intro_full",
self::ROLE_HEADING => "intro",
self::ROLE_CONTENT =>"content",
self::ROLE_IMAGE =>"image",
self::ROLE_LINK =>"link",
);
break;
default:
$arrFieldsByRoles = $this->getFieldsByRoles($paramsItems);
break;
}
// Check if manual mapping is enabled
$isMappingEnabled = UniteFunctionsUC::getVal($arrSettings, "enable_mapping");
$isMappingEnabled = UniteFunctionsUC::strToBool($isMappingEnabled);
if ($isMappingEnabled == false)
return $arrFieldsByRoles;
$arrManualRoles = array(
self::ROLE_TITLE,
self::ROLE_DESCRIPTION,
self::ROLE_HEADING,
self::ROLE_CONTENT
);
foreach ($arrManualRoles as $roleKey) {
$manualValue = UniteFunctionsUC::getVal($arrSettings, "fieldmap_" . $roleKey);
if (!empty($manualValue) && $manualValue !== self::ROLE_AUTO) {
$arrFieldsByRoles[$roleKey] = $manualValue;
//check for meta or term, add the extra key
$arrFieldsByRoles = $this->getFieldsByRoles_checkMetaTermKeys($arrFieldsByRoles, $manualValue, $roleKey, $arrSettings);
}
}
//-- add extra fields:
$arrExtraFields = array(
self::ROLE_EXTRA_FIELD1,
self::ROLE_EXTRA_FIELD2,
self::ROLE_EXTRA_FIELD3,
self::ROLE_EXTRA_FIELD4
);
foreach($arrExtraFields as $roleKey){
$fieldMap = UniteFunctionsUC::getVal($arrSettings, "fieldmap_" . $roleKey);
$arrFieldsByRoles[$roleKey] = "";
if($fieldMap == "nothing" || empty($fieldMap))
continue;
$arrFieldsByRoles = $this->getFieldsByRoles_checkMetaTermKeys($arrFieldsByRoles, $fieldMap, $roleKey, $arrSettings);
}
return $arrFieldsByRoles;
}
private function a____________SETTINGS________(){}
/**
* Add UI for fields mapping by roles.
*
* Adds enable mapping toggle + "Auto" option for each field.
*
* @param UniteCreatorDialogSettings $objSettings
* @param string $name
* @param array $paramsItems
*/
private function addFieldsMappingSettings($objSettings, $name, $paramsItems, $isPost) {
// ---- Add master toggle: enable mapping yes/no ----
$arrParam = array();
$arrParam["origtype"] = UniteCreatorDialogParam::PARAM_RADIOBOOLEAN;
$arrParam["elementor_condition"] = array($name . "_enable" => "true");
$arrParam["description"] = __("Enable manual fields mapping by roles. The post related fields are relevant only if the content from posts", "unlimited-elements-for-elementor");
$objSettings->addRadioBoolean(
"{$name}_enable_mapping",
__("Enable Fields Mapping", "unlimited-elements-for-elementor"),
false,
"Yes",
"No",
$arrParam
);
// ---- Build field options: only textfield, textarea, editor ----
$arrExtraFieldOptions = array();
$arrExtraFieldOptions["nothing"] = __("[Select Content]", "unlimited-elements-for-elementor");
$arrExtraFieldOptions["meta"] = __("Post Meta Field", "unlimited-elements-for-elementor");
$arrExtraFieldOptions["term"] = __("Post Term", "unlimited-elements-for-elementor");
$arrFieldOptions = array();
$arrFieldOptions[self::ROLE_AUTO] = __("[Auto Detect]", "unlimited-elements-for-elementor");
if($isPost == true){
$arrFieldOptions['title'] = __("Post Title", "unlimited-elements-for-elementor");
$arrFieldOptions['intro'] = __("Post Intro", "unlimited-elements-for-elementor");
$arrFieldOptions['intro_full'] = __("Post Intro Full", "unlimited-elements-for-elementor");
$arrFieldOptions['content'] = __("Post Content", "unlimited-elements-for-elementor");
}else{
foreach ($paramsItems as $param) {
$paramName = UniteFunctionsUC::getVal($param, "name");
$paramTitle = UniteFunctionsUC::getVal($param, "title");
$paramType = UniteFunctionsUC::getVal($param, "type");
if (empty($paramName))
continue;
$isTextType = ($paramType === UniteCreatorDialogParam::PARAM_TEXTFIELD);
$isContentType = in_array($paramType, array(
UniteCreatorDialogParam::PARAM_TEXTAREA,
UniteCreatorDialogParam::PARAM_EDITOR
));
if (!$isTextType && !$isContentType)
continue;
$arrFieldOptions[$paramName] = $paramTitle;
}
}
//modify extra fields
$arrExtraFieldOptions = array_merge($arrExtraFieldOptions, $arrFieldOptions);
unset($arrExtraFieldOptions[self::ROLE_AUTO]);
//add extra field option. meta or term
$arrFieldOptions['meta'] = __("Post Meta Field", "unlimited-elements-for-elementor");
$arrFieldOptions['term'] = __("Post Term", "unlimited-elements-for-elementor");
// ---- Flip options: label => value ----
$arrFieldOptions = array_flip($arrFieldOptions);
$arrExtraFieldOptions = array_flip($arrExtraFieldOptions);
// ---- Define roles (only text/content roles) ----
$arrRoles = array(
self::ROLE_TITLE => __("Title Field", "unlimited-elements-for-elementor"),
self::ROLE_DESCRIPTION => __("Description Field", "unlimited-elements-for-elementor"),
self::ROLE_HEADING => __("Heading Field", "unlimited-elements-for-elementor"),
self::ROLE_CONTENT => __("Content Field", "unlimited-elements-for-elementor"),
self::ROLE_EXTRA_FIELD1 => __("Extra Field 1", "unlimited-elements-for-elementor"),
self::ROLE_EXTRA_FIELD2 => __("Extra Field 2", "unlimited-elements-for-elementor"),
self::ROLE_EXTRA_FIELD3 => __("Extra Field 3", "unlimited-elements-for-elementor"),
self::ROLE_EXTRA_FIELD4 => __("Extra Field 4", "unlimited-elements-for-elementor")
);
// ---- Add a select control for each text/content role ----
foreach ($arrRoles as $roleKey => $roleLabel) {
$arrParam = array();
$arrParam["origtype"] = UniteCreatorDialogParam::PARAM_DROPDOWN;
$arrParam["elementor_condition"] = array(
$name . "_enable" => "true",
"{$name}_enable_mapping" => "true"
);
$arrOptions = $arrFieldOptions;
$defaultValue = self::ROLE_AUTO;
$isExtraField = (strpos($roleKey, "field") !== false);
if($isExtraField == true){
$arrOptions = $arrExtraFieldOptions;
$defaultValue = "nothing";
}
$objSettings->addSelect(
"{$name}_fieldmap_{$roleKey}",
$arrOptions, //options
$roleLabel, //label
$defaultValue, //default
$arrParam
);
//add meta and term field name
$arrParam = array();
$arrParam["origtype"] = UniteCreatorDialogParam::PARAM_TEXTFIELD;
$arrParam["description"] = "Meta Field name or Term Taxonomy name";
$arrParam["elementor_condition"] = array(
$name . "_enable" => "true",
"{$name}_enable_mapping" => "true",
"{$name}_fieldmap_{$roleKey}"=>array("meta","term")
);
$objSettings->addTextBox("{$name}_extrafieldkey_{$roleKey}", "", esc_html__("Field Name", "unlimited-elements-for-elementor"),$arrParam);
}
}
/**
* put schema settings
*/
public function addSchemaSettings(&$objSettings, $name, $param){
$arrParam = array();
$arrParam["origtype"] = UniteCreatorDialogParam::PARAM_RADIOBOOLEAN;
$arrParam["description"] = UniteFunctionsUC::getVal($param, "description");
$objSettings->addRadioBoolean($name."_enable", $param["title"],false,"Yes","No",$arrParam);
if(GlobalsUnlimitedElements::$enableCustomSchema == true){
//------- from list / custom
$arrParam = array();
$arrParam["origtype"] = UniteCreatorDialogParam::PARAM_DROPDOWN;
$arrParam["elementor_condition"] = array($name."_enable"=>"true");
$arrOptions = array(
__("From List","unlimited-elements-for-elementor") => "list",
__("Custom","unlimited-elements-for-elementor") => "custom",
);
$objSettings->addSelect($name."_selection",$arrOptions, __("Schema Source","unlimited-elements-for-elementor") , "list", $arrParam);
//------- custom textarea
$arrParam = array();
$arrParam["origtype"] = UniteCreatorDialogParam::PARAM_TEXTAREA;
$arrParam["elementor_condition"] = array($name."_enable"=>"true",$name."_selection"=>"custom");
$descripiton = __("Paste any JSON schema from ","unlimited-elements-for-elementor");
$descripiton .= "<a href='https://schema.org/Person' target='_blank'>schema.org</a>";
$descripiton .= __(" site","unlimited-elements-for-elementor");
$descripiton .= __("<br>Posible Placeholders Are: %title%, %description%, %heading%, %link%, %content%, %image%, %field1%, %field2%, %field3%, %field4% ","unlimited-elements-for-elementor");
$arrParam["description"] = $descripiton;
$objSettings->addTextArea($name."_custom", "", __("Custom JSON Schema","unlimited-elements-for-elementor") , $arrParam);
}
//------- schema types
$arrSchemas = $this->getArrSchemas();
$arrOptions = array();
foreach($arrSchemas as $schema){
$schemaName = UniteFunctionsUC::getVal($schema, "name");
$schemaTitle = UniteFunctionsUC::getVal($schema, "title");
$arrOptions[$schemaName] = $schemaTitle;
}
$arrParam = array();
$arrParam["origtype"] = UniteCreatorDialogParam::PARAM_DROPDOWN;
$arrParam["elementor_condition"] = array($name."_enable"=>"true",$name."_selection"=>"list");
if(GlobalsUnlimitedElements::$enableCustomSchema == false)
$arrParam["elementor_condition"] = array($name."_enable"=>"true");
$arrOptions = array_flip($arrOptions);
$title = __('Schema Type',"unlimited-elements-for-elementor");
$objSettings->addSelect("{$name}_type", $arrOptions, $title, "faqpage", $arrParam);
//------- main name
$arrParam = array();
$arrParam["origtype"] = UniteCreatorDialogParam::PARAM_TEXTFIELD;
$arrParam["add_dynamic"] = true;
$arrParam["elementor_condition"] = array($name."_enable"=>"true",$name."_type"=>
array("howto",
"recipe",
"faqpage",
"itemlist",
"eventseries",
"musicplaylist",
"searchresultspage"));
$arrParam["description"] = __('Use to describe the action, like how to tie a shoes',"unlimited-elements-for-elementor");
$arrParam["label_block"] = true;
$title = __('Schema Main Title',"unlimited-elements-for-elementor");
$objSettings->addTextBox($name."_title","", $title, $arrParam);
//------- hr before mapping -------
$arrParam = array();
$arrParam["origtype"] = UniteCreatorDialogParam::PARAM_HR;
$arrParam["elementor_condition"] = array($name."_enable"=>"true");
$objSettings->addHr($name."_hr_before_mapping", $arrParam);
//---- add schema mapping here:
if(empty($this->objAddon))
UniteFunctionsUC::throwError("No addon found, please set addon for the schema object");
$itemsType = $this->objAddon->getItemsType();
$isPost = ($itemsType == UniteCreatorAddon::ITEMS_TYPE_POST);
$paramsItems = $this->objAddon->getParamsItems();
if(!empty($paramsItems))
$this->addFieldsMappingSettings($objSettings, $name, $paramsItems, $isPost);
//------- debug ------
$arrParam = array();
$arrParam["origtype"] = UniteCreatorDialogParam::PARAM_RADIOBOOLEAN;
$arrParam["elementor_condition"] = array($name."_enable"=>"true");
$arrParam["description"] = __('Show schema debug in front end footer',"unlimited-elements-for-elementor");
$title = __('Show Schema Debug',"unlimited-elements-for-elementor");
$objSettings->addRadioBoolean($name."_debug", $title, false, "Yes","No", $arrParam);
//------- debug ------
$arrParam = array();
$arrParam["origtype"] = UniteCreatorDialogParam::PARAM_STATIC_TEXT;
$arrParam["elementor_condition"] = array($name."_enable"=>"true");
$text = __('To show post meta fields and terms turn on debug option in Advanced Section',"unlimited-elements-for-elementor");
$objSettings->addStaticText($text, $name."_debug_meta_text", $arrParam);
}
/**
* add schema settings for posts option
*/
public function addSchemaMultipleSettings(&$objSettings){
$param = array();
$param["title"] = __('Enable Schema',"unlimited-elements-for-elementor");
$param["description"] = "";
$this->addSchemaSettings($objSettings, self::MULTIPLE_SCHEMA_NAME, $param);
}
private function a____________DEBUG________(){}
/**
* show the debug message under the widget
*
*/
private function showDebugMessage(){
$message = __('Schema Debug: This widget will generate schema debug at the footer',"unlimited-elements-for-elementor");
$html = HelperHtmlUC::getDebugWarningMessageHtml($message);
uelm_echo($html);
}
/**
* show schema debug
*/
private function showDebugSchema($schemaType, $arrSchema){
dmp("Schema Output Debug: $schemaType");
dmp("The Fields Mapping and Content");
HelperHtmlUC::putHtmlDataDebugBox($this->debugFields);
dmp("The Schema Output");
HelperHtmlUC::putHtmlDataDebugBox($arrSchema);
}
private function a____________SCHEMAS________(){}
/**
* Clean only selected keys from schema array.
*
* @param mixed $data
* @param array $keysToClean Keys that should be removed when empty.
* @return mixed
*/
private function cleanSchemaArray($data, $keysToClean = array()) {
// If not array – return as is
if (!is_array($data)) {
return $data;
}
$clean = array();
foreach ($data as $key => $value) {
// --- Nested array ---
if (is_array($value)) {
// Clean recursively
$value = $this->cleanSchemaArray($value, $keysToClean);
// If the key is one of the "to clean" keys AND empty – skip it
if (in_array($key, $keysToClean, true) && empty($value)) {
continue;
}
$clean[$key] = $value;
continue;
}
// Normalize scalar values
if (is_string($value)) {
$value = trim($value);
}
// --- Clean only selected keys ---
if (in_array($key, $keysToClean, true)) {
// Remove ONLY if empty (empty string or null)
if ($value === '' || $value === null) {
continue;
}
}
// Keep ZERO, false, numeric values
$clean[$key] = $value;
}
return $clean;
}
/**
* normalize custom schema template, remove the tags
* in case that the user pasted the "script" tag around json
*/
private function normalizeCustomSchemaTemplate($strJsonSchema){
$strJsonSchema = trim($strJsonSchema);
$strJsonSchema = strip_tags($strJsonSchema,"script");
return($strJsonSchema);
}
/**
* get all schemas items content
*/
private function getAllSchemaItemsContent($data, $schemaType){
$arrAddonsData = UniteFunctionsUC::getVal($data, "addons");
if(empty($arrAddonsData))
return(null);
$arrItemsContent = array();
foreach($arrAddonsData as $addonData){
$items = UniteFunctionsUC::getVal($addonData, "items");
$params = UniteFunctionsUC::getVal($addonData, "params");
$settings = UniteFunctionsUC::getVal($addonData, "settings");
//field quessing
$arrFieldsByRoles = $this->getFieldsByRolesFinal($params, $settings);
$schemaContent = null;
if($schemaType == "custom"){
$schemaContent = UniteFunctionsUC::getVal($settings, "custom");
$schemaContent = $this->normalizeCustomSchemaTemplate($schemaContent);
}
$itemType = UniteFunctionsUC::getVal($settings, "item_type");
//add debug
$arrDebug = array();
if(self::$showDebug == true){
$arrParamsAssoc = UniteFunctionsUC::arrayToAssoc($params, "name", "type");
$arrDebug = array("params"=>$arrParamsAssoc,"fieldsbyroles"=>$arrFieldsByRoles);
}
$arrDebugContent = array();
foreach($items as $item){
$arrContent = $this->getItemSchemaContent($item, $arrFieldsByRoles, $itemType);
if(self::$showDebug == true)
$arrDebugContent[] = $arrContent;
if(!empty($schemaContent))
$arrContent["schema_custom_json"] = $schemaContent;
$arrItemsContent[] = $arrContent;
}
//---- show debug
if(self::$showDebug == true){
$arrDebug["items_content"] = $arrDebugContent;
$this->debugFields[$schemaType][] = $arrDebug;
}
}
return($arrItemsContent);
}
/**
* Generate schema structure based on specified type
*/
private function generateSchemaByType($schemaType, $data) {
$items = $this->getAllSchemaItemsContent($data, $schemaType);
$title = UniteFunctionsUC::getVal($data, "title");
switch ($schemaType) {
case "person":
return $this->schemaPerson($items);
case "howto":
return $this->schemaHowTo($items, $title);
case "course":
return $this->schemaCourse($items);
case "recipe":
return $this->schemaRecipe($items);
case "book":
return $this->schemaBook($items);
case "itemlist":
return $this->schemaItemList($items, $title);
case "event":
return $this->schemaEvent($items);
case "place":
return $this->schemaPlace($items);
case "product":
return $this->schemaProduct($items);
case "touristdestination":
return $this->schemaTouristDestination($items);
case "eventseries":
return $this->schemaEventSeries($items);
case "musicplaylist":
return $this->schemaMusicPlaylist($items);
case "searchresultspage":
return $this->schemaSearchResultsPage($items, $title);
case "newsarticle":
return $this->schemaNewsArticle($items);
case "custom":
if(GlobalsUnlimitedElements::$enableCustomSchema == true){
$jsonSchema = $this->schemaCustom($items, $title);
return($jsonSchema);
}else
return(null);
break;
default:
case "faqpage":
return $this->schemaFaq($items, $title);
}
}
private function a____________CUSTOM_SCHEMA________(){}
/**
* replace placeholders in the value string
*/
private function replacePlaceholders_values($value, $item){
if(is_string($value) == false)
return($value);
//check for %something%
if (preg_match('/%[a-zA-Z0-9_]+%/', $value) == false)
return($value);
$arrRoles = $this->getArrRolesKeys();
foreach($arrRoles as $role){
$content = UniteFunctionsUC::getVal($item, $role);
$value = str_replace("%{$role}%", $content, $value);
}
//check error, if found some unknown placeholder:
if (preg_match('/%[a-zA-Z0-9_]+%/', $value) == true){
if(HelperUC::isRunCodeOnce("uc_schema_replace_placeholders") == true){
$message = "Custom Schema Error: Unknown Placeholder: ".$value." Please change for a known placeholder from the list.";
$htmlError = HelperHtmlUC::getErrorMessageHtml($message,"",true);
dmp($htmlError);
}
}
return($value);
}
/**
* replace placeholders
*/
private function replacePlaceholders($arrSchema, $item){
if(empty($arrSchema))
return($arrSchema);
foreach ($arrSchema as $key => $value){
if (is_array($value))
$value = $this->replacePlaceholders($value, $item);
else {
$value = $this->replacePlaceholders_values($value, $item);
}
$arrSchema[$key] = $value;
}
return($arrSchema);
}
/**
* put schema custom item
*/
private function schemaCustomItem($item){
$schemaJson = UniteFunctionsUC::getVal($item, "schema_custom_json");
$this->lastCustomSchema = $schemaJson;
if(empty($schemaJson))
return($schemaJson);
try{
$arrSchema = UniteFunctionsUC::jsonDecodeValidate($schemaJson);
$arrSchema = $this->replacePlaceholders($arrSchema, $item);
}catch(Exception $e){
if(self::$showJsonErrorOnce == true)
return(array());
self::$showJsonErrorOnce = true;
$message = $e->getMessage();
$message = "Schema JSON Decode Error: <b> $message </b> in schema: ";
$htmlError = HelperHtmlUC::getErrorMessageHtml($message,"",true);
dmp($htmlError);
dmpHtml($schemaJson);
$message2 = "You can copy paste this json into chat gpt, and it will tell you where is the error";
$htmlError = HelperHtmlUC::getErrorMessageHtml($message2,"",true);
dmp($htmlError);
return(array());
}
return($arrSchema);
}
/**
* custom schema
*/
private function schemaCustom($items, $title){
$arrSchema = array();
foreach($items as $item){
$arrSchemaItem = $this->schemaCustomItem($item);
$arrSchema[] = $arrSchemaItem;
}
return($arrSchema);
}
private function a____________SCHEMA_FUNCTIONS________(){}
/**
* FAQ
*/
private function schemaFaq($items, $title = "") {
$schema = array(
'@context' => self::SCHEMA_ORG_SITE,
'@type' => 'FAQPage',
);
if (!empty($title)) $schema['name'] = $title;
$schema['mainEntity'] = array();
foreach ($items as $item) {
$question = array(
'@type' => 'Question',
'name' => $item[self::ROLE_TITLE],
'acceptedAnswer' => array(
'@type' => 'Answer',
'text' => $item[self::ROLE_CONTENT],
),
);
// Optional image on Question
if (!empty($item[self::ROLE_IMAGE])) {
$question['image'] = $item[self::ROLE_IMAGE];
}
$schema['mainEntity'][] = $question;
}
return $schema;
}
/**
* HowTo
*/
private function schemaHowTo($items, $title = "") {
$schema = array(
'@context' => self::SCHEMA_ORG_SITE,
'@type' => 'HowTo',
);
if (!empty($title)) $schema['name'] = $title;
$schema['step'] = array();
foreach ($items as $item) {
$step = array(
'@type' => 'HowToStep',
'name' => $item[self::ROLE_TITLE],
'text' => $item[self::ROLE_DESCRIPTION],
'url' => $item[self::ROLE_LINK],
);
if (!empty($item[self::ROLE_IMAGE])) {
$step['image'] = $item[self::ROLE_IMAGE];
}
$schema['step'][] = $step;
}
return $schema;
}
/**
* Recipe
*/
private function schemaRecipe($items, $title = "") {
$schema = array(
'@context' => self::SCHEMA_ORG_SITE,
'@type' => 'Recipe',
);
if (!empty($title))
$schema['name'] = $title;
$schema['recipeInstructions'] = array();
foreach ($items as $item) {
$step = array(
'@type' => 'HowToStep',
'name' => $item[self::ROLE_TITLE],
'text' => $item[self::ROLE_DESCRIPTION],
'url' => $item[self::ROLE_LINK],
);
if (!empty($item[self::ROLE_IMAGE])) {
$step['image'] = $item[self::ROLE_IMAGE];
}
$schema['recipeInstructions'][] = $step;
}
return $schema;
}
/**
* ItemList
*/
private function schemaItemList($items, $title = "") {
$schema = array(
'@context' => self::SCHEMA_ORG_SITE,
'@type' => 'ItemList',
);
if (!empty($title)) $schema['name'] = $title;
$schema['itemListElement'] = array();
$position = 1;
foreach ($items as $item) {
$listItem = array(
'@type' => 'ListItem',
'position' => $position++,
'item' => array(
'name' => $item[self::ROLE_TITLE],
'url' => $item[self::ROLE_LINK]
)
);
if (!empty($item[self::ROLE_IMAGE])) {
$listItem['item']['image'] = $item[self::ROLE_IMAGE];
}
$schema['itemListElement'][] = $listItem;
}
return $schema;
}
/**
* SearchResultsPage
*/
private function schemaSearchResultsPage($items, $title = "") {
$schema = array(
'@context' => self::SCHEMA_ORG_SITE,
'@type' => 'SearchResultsPage',
);
if (!empty($title)) $schema['name'] = $title;
$schema['mainEntity'] = array(
'@type' => 'ItemList',
'itemListElement' => array(),
);
$position = 1;
foreach ($items as $item) {
$listItem = array(
'@type' => 'ListItem',
'position' => $position++,
'name' => $item[self::ROLE_TITLE],
'url' => $item[self::ROLE_LINK],
);
if (!empty($item[self::ROLE_IMAGE])) {
$listItem['image'] = $item[self::ROLE_IMAGE];
}
$schema['mainEntity']['itemListElement'][] = $listItem;
}
return $schema;
}
/**
* Person Schema
*/
private function schemaPerson($items) {
$schema = array();
foreach ($items as $item) {
$person = array(
'@context' => self::SCHEMA_ORG_SITE,
'@type' => 'Person',
'name' => $item[self::ROLE_TITLE],
);
// Optional fields added only if not empty
if (!empty($item[self::ROLE_HEADING])) {
$person['jobTitle'] = $item[self::ROLE_HEADING];
}
if (!empty($item[self::ROLE_DESCRIPTION])) {
$person['description'] = $item[self::ROLE_DESCRIPTION];
}
if (!empty($item[self::ROLE_IMAGE])) {
$person['image'] = $item[self::ROLE_IMAGE];
}
if (!empty($item[self::ROLE_LINK])) {
$person['sameAs'] = array($item[self::ROLE_LINK]); // must be array
}
$schema[] = $person;
}
return $schema;
}
/**
* Course
*/
private function schemaCourse($items) {
$schema = array();
foreach ($items as $item) {
$course = array(
'@context' => self::SCHEMA_ORG_SITE,
'@type' => 'Course',
'name' => $item[self::ROLE_TITLE],
);
// optional: description
if (!empty($item[self::ROLE_DESCRIPTION])) {
$course['description'] = $item[self::ROLE_DESCRIPTION];
}
// Build provider only if needed
$provider = array(
'@type' => 'Organization'
);
$hasProvider = false;
if (!empty($item[self::ROLE_HEADING])) {
$provider['name'] = $item[self::ROLE_HEADING];
$hasProvider = true;
}
if (!empty($item[self::ROLE_LINK])) {
$provider['sameAs'] = array($item[self::ROLE_LINK]); // must be array
$hasProvider = true;
}
if ($hasProvider) {
$course['provider'] = $provider;
}
// Optional image
if (!empty($item[self::ROLE_IMAGE])) {
$course['image'] = $item[self::ROLE_IMAGE];
}
$schema[] = $course;
}
return $schema;
}
/**
* NewsArticle (schema.org/NewsArticle - Article subtype for news)
*/
private function schemaNewsArticle($items) {
$schema = array();
foreach ($items as $item) {
$article = array(
'@context' => self::SCHEMA_ORG_SITE,
'@type' => 'NewsArticle',
'headline' => $item[self::ROLE_TITLE],
);
if (!empty($item[self::ROLE_CONTENT])) {
$article['articleBody'] = $item[self::ROLE_CONTENT];
}
if (!empty($item[self::ROLE_DESCRIPTION])) {
$article['description'] = $item[self::ROLE_DESCRIPTION];
}
if (!empty($item[self::ROLE_IMAGE])) {
$article['image'] = $item[self::ROLE_IMAGE];
}
if (!empty($item[self::ROLE_LINK])) {
$article['url'] = $item[self::ROLE_LINK];
$article['mainEntityOfPage'] = $item[self::ROLE_LINK];
}
if (!empty($item[self::ROLE_HEADING])) {
$article['author'] = array(
'@type' => 'Person',
'name' => $item[self::ROLE_HEADING]
);
}
$schema[] = $article;
}
return $schema;
}
/**
* Book
*/
private function schemaBook($items) {
$schema = array();
foreach ($items as $item) {
$book = array(
'@context' => self::SCHEMA_ORG_SITE,
'@type' => 'Book',
'name' => $item[self::ROLE_TITLE],
);
// Optional: Description
if (!empty($item[self::ROLE_DESCRIPTION])) {
$book['description'] = $item[self::ROLE_DESCRIPTION];
}
// Optional: Image
if (!empty($item[self::ROLE_IMAGE])) {
$book['image'] = $item[self::ROLE_IMAGE];
}
// Optional: URL
if (!empty($item[self::ROLE_LINK])) {
$book['url'] = $item[self::ROLE_LINK];
$book['sameAs'] = array($item[self::ROLE_LINK]);
}
// Optional: Author (use heading field as author name)
if (!empty($item[self::ROLE_HEADING])) {
$book['author'] = array(
'@type' => 'Person',
'name' => $item[self::ROLE_HEADING]
);
}
$schema[] = $book;
}
return $schema;
}
/**
* Event
*/
private function schemaEvent($items) {
$schema = array();
foreach ($items as $item) {
$schema[] = array(
'@context' => self::SCHEMA_ORG_SITE,
'@type' => 'Event',
'name' => $item[self::ROLE_TITLE],
'description' => $item[self::ROLE_DESCRIPTION],
'image' => $item[self::ROLE_IMAGE],
'url' => $item[self::ROLE_LINK],
);
}
return $schema;
}
/**
* EventSeries
*/
private function schemaEventSeries($items) {
$schema = array();
foreach ($items as $item) {
$eventSeries = array(
'@context' => self::SCHEMA_ORG_SITE,
'@type' => 'EventSeries',
'name' => $item[self::ROLE_TITLE],
);
// Optional: description
if (!empty($item[self::ROLE_DESCRIPTION])) {
$eventSeries['description'] = $item[self::ROLE_DESCRIPTION];
}
// Optional: image
if (!empty($item[self::ROLE_IMAGE])) {
$eventSeries['image'] = $item[self::ROLE_IMAGE];
}
// Optional: sameAs (must be an array)
if (!empty($item[self::ROLE_LINK])) {
$eventSeries['sameAs'] = array($item[self::ROLE_LINK]);
}
$schema[] = $eventSeries;
}
return $schema;
}
/**
* MusicPlaylist
*/
private function schemaMusicPlaylist($items) {
$schema = array();
foreach ($items as $item) {
$playlist = array(
'@context' => self::SCHEMA_ORG_SITE,
'@type' => 'MusicPlaylist',
'name' => $item[self::ROLE_TITLE],
'description' => $item[self::ROLE_DESCRIPTION],
);
if (!empty($item[self::ROLE_IMAGE])) {
$playlist['image'] = $item[self::ROLE_IMAGE];
}
$schema[] = $playlist;
}
return $schema;
}
/**
* Place
*/
private function schemaPlace($items) {
$schema = array();
foreach ($items as $item) {
$place = array(
'@context' => self::SCHEMA_ORG_SITE,
'@type' => 'Place',
'name' => $item[self::ROLE_TITLE],
);
// optional: description
if (!empty($item[self::ROLE_DESCRIPTION])) {
$place['description'] = $item[self::ROLE_DESCRIPTION];
}
// optional: image
if (!empty($item[self::ROLE_IMAGE])) {
$place['image'] = $item[self::ROLE_IMAGE];
}
// optional: url / sameAs
if (!empty($item[self::ROLE_LINK])) {
$place['url'] = $item[self::ROLE_LINK];
$place['sameAs'] = array($item[self::ROLE_LINK]);
}
$schema[] = $place;
}
return $schema;
}
/**
* Product
*/
private function schemaProduct($items) {
$schema = array();
foreach ($items as $item) {
$product = array(
'@context' => self::SCHEMA_ORG_SITE,
'@type' => 'Product',
'name' => $item[self::ROLE_TITLE],
);
// Optional: description
if (!empty($item[self::ROLE_DESCRIPTION])) {
$product['description'] = $item[self::ROLE_DESCRIPTION];
}
// Optional: image
if (!empty($item[self::ROLE_IMAGE])) {
$product['image'] = $item[self::ROLE_IMAGE];
}
// Optional: main URL + sameAs
if (!empty($item[self::ROLE_LINK])) {
$product['url'] = $item[self::ROLE_LINK];
$product['sameAs'] = array($item[self::ROLE_LINK]); // must be array for Google
}
$schema[] = $product;
}
return $schema;
}
/**
* TouristDestination
*/
private function schemaTouristDestination($items) {
$schema = array();
foreach ($items as $item) {
$dest = array(
'@context' => self::SCHEMA_ORG_SITE,
'@type' => 'TouristDestination',
'name' => $item[self::ROLE_TITLE],
);
// optional description
if (!empty($item[self::ROLE_DESCRIPTION])) {
$dest['description'] = $item[self::ROLE_DESCRIPTION];
}
// optional image
if (!empty($item[self::ROLE_IMAGE])) {
$dest['image'] = $item[self::ROLE_IMAGE];
}
// optional sameAs / link
if (!empty($item[self::ROLE_LINK])) {
$dest['sameAs'] = array($item[self::ROLE_LINK]);
$dest['url'] = $item[self::ROLE_LINK]; // recommended for Google
}
$schema[] = $dest;
}
return $schema;
}
}