File: /home/crowdandsafety/public_html/wp-content/plugins/cornerstone/includes/classes/Services/Wpml.php
<?php
namespace Themeco\Cornerstone\Services;
use Themeco\Cornerstone\Util\Endpoint;
use Themeco\Cornerstone\Documents\Document;
class Wpml implements Service {
protected $previous_lang;
// Map of post to original language data
protected $postTypeMigrationData = [];
protected $controllerTranslationEndpoint;
private $wpml_lang;
private $wpml_trid;
public function __construct(Endpoint $controllerTranslationEndpoint) {
$this->controllerTranslationEndpoint = $controllerTranslationEndpoint;
}
public function setup() {
if ( ! $this->is_active() ) {
return;
}
$this->controllerTranslationEndpoint->config( [
'requestKey' => 'cs-translation',
'handler' => [ $this, 'createTranslation' ]
])->start();
add_action( 'option_wpml_registered_endpoints', array( $this, 'unset_endpoint' ) );
add_action( 'cs_before_preview_frame', array( $this, 'before_preview_frame' ) );
add_filter( 'cs_locate_wpml_language', array( $this, 'locate_wpml_language'), 10, 2);
add_filter( 'cs_global_block_id', array($this, 'icl_global_block_id' ) );
add_filter('cs_before_preview_frame_filter', [$this, 'before_preview_frame_filter'] );
add_filter( 'wpml_enqueue_browser_redirect_language', array( $this, 'disable_browser_redirect' ) );
add_filter( 'pre_get_posts', array( $this, 'pre_get_posts' ) );
if ( apply_filters ( 'cs_enable_wpml_legacy_filters', false ) ) {
add_filter( 'the_title', array( $this, 'filter_title' ), 99, 2 );
add_filter( 'the_permalink', array( $this, 'filter_permalink' ) );
}
// Prevent '_cornerstone_data' from being duplicated
add_action( 'save_post', array( $this, 'save_post' ), -100 );
// Detect and filter to correct translation
add_filter("cs_detect_assigned_document", [ $this, 'filterAssignedDoc' ], -100);
add_filter("cs_filter_app_url", [ $this, 'filterAppURL' ], -100);
add_filter("cs_app_url_base", [ $this, 'filterUrlBase' ], 0);
add_action("cs_before_save_request", [ $this, 'preventDuplicatePageSave' ]);
add_action('cs_before_migrate_untyped_layouts_grab', [$this, 'beforeMigrateUntypedLayoutsNonDefault']);
add_action("cs_before_migrate_untyped_layouts", [ $this, 'setTranslationModeLayouts' ]);
// WooCommerce shop page URL change per translation setting of document
add_filter('cs_document_layout_archive-wc_builder_info', [ $this, 'filterWCArchiveBuilderInfo'], 0, 2 );
add_filter('cs_url_to_validate', function($url) {
// We use raw option because WPML will overwrite
// And this causes issues on subdomain mode
if (!is_multisite()) {
return esc_attr( trailingslashit( get_option('home') ) );
}
// Multisite doesn't work properly anyway so send the normal URL
return $url;
});
//add_action("cs_before_migrate_untyped_ignore", [ $this, "beforeMigrateUntypedLayoutsIgnoreList" ], 0, 2 );
//add_action("cs_after_migrate_untyped_layouts_post", [ $this, 'afterMigrateUntypedLayouts' ], 0, 1 );
//add_action("cs_after_migrate_untyped_layouts", [ $this, 'afterMigrateUntypedLayouts' ], 0, 1);
// PHP 8 media issue
// add_filter('wpml_media_content_for_media_usage', '__return_false');
}
public function is_active() {
return class_exists( 'SitePress' );
}
public function locate_wpml_language( $lang, $post ) {
global $sitepress;
$language_details = $sitepress->get_element_language_details( $post->ID, 'post_' . $post->post_type );
if ($language_details) {
$lang = $language_details->language_code;
}
return $lang;
}
public function before_preview_frame( $state ) {
if ( isset( $state['lang']) && ! isset( $_REQUEST['lang']) ) {
$_REQUEST['lang'] = $state['lang'];
}
if ( isset( $_REQUEST['lang'] ) &&
( '3' === \wpml_get_setting_filter(false, 'language_negotiation_type')
|| isset( $state['lang'] ) )
) {
add_action('wp_loaded', array( $this, 'set_preview_lang' ), 11 );
}
}
/**
* Filter url so we don't encounter redirect
* None of disable redirect function so I think it's internal
* this filters the URL so this doesn't happen
*/
public function before_preview_frame_filter($state) {
$post = get_post($state['documentId']);
// This only happens on posts in a draft state
if ($post->post_status !== "draft") {
return $state;
}
// Get default language as that what redirects to
// for draft pages and cs-render
$translated = $this->getTranslatedObjectId(
$state['documentId'],
$state['docType'],
$this->get_default_language()
);
// Found translation
if ($translated && $translated !== $state['documentId']) {
$state['url'] = get_option("home") . "/" . '?page_id=' . $translated . '&lang=' . $state['lang'] . '/';
}
// Filter
return $state;
}
public function set_preview_lang() {
global $sitepress;
$sitepress->switch_lang($_REQUEST['lang']);
}
public function switch_lang( $lang = 'all' ) {
if ( ! $this->is_active() ) {
return;
}
global $sitepress;
$this->previous_lang = $sitepress->get_current_language();
$sitepress->switch_lang($lang);
}
public function switch_back() {
if ( ! $this->is_active() ) {
return;
}
global $sitepress;
$sitepress->switch_lang( $this->previous_lang );
}
public function get_previous_lang() {
return $this->previous_lang;
}
public function pre_get_posts( $query ) {
global $sitepress;
if ( ! is_callable( array( $sitepress, 'switch_lang' ) ) || ! is_callable( array( $sitepress, 'get_current_language' ) ) ) {
return $query;
}
if ( isset( $query->query_vars['cs_all_wpml'] ) && $query->query_vars['cs_all_wpml'] ) {
return $query;
}
// $sitepress->switch_lang( $sitepress->get_current_language() ); //Make sure that even custom query gets the current language
$query->query_vars['suppress_filters'] = false;
return $query;
}
//WPML Post object usable by multiple filters
private function wpml_post() {
global $post, $sitepress;
if ( ! $post || ! function_exists( 'icl_object_id' ) || ! is_callable( array( $sitepress, 'get_current_language' ) ) ) {
return;
}
return get_post( icl_object_id( $post->ID, 'post', false, $sitepress->get_current_language() ) );
}
public function icl_global_block_id ( $ID ) {
if( !$this->is_active() ) return $ID;
global $sitepress;
$global_block_id = icl_object_id( $ID, 'cs_global_block', false, $sitepress->get_current_language() );
return get_post( empty( $global_block_id ) ? apply_filters('cs_global_block_linked_id', $ID ) : $global_block_id )->ID;
}
public function filter_title( $title, $id = null ) {
$wpml_post = $this->wpml_post();
return ( ! is_a( $wpml_post, 'WP_Post' ) || $wpml_post->ID !== $id ) ? $title :
// Let's apply the_title filters (apply_filters causes loop)
trim( convert_chars( wptexturize( esc_html( $wpml_post->post_title ) ) ) );
}
public function filter_permalink( $permalink ) {
$wpml_post = $this->wpml_post();
if ( is_a( $wpml_post, 'WP_Post' ) ) {
$permalink = get_permalink( $wpml_post->ID );
}
return $permalink;
}
public function get_language_data_from_post( $post, $all = false ) {
if ( is_int( $post ) ) {
$post = get_post( $post );
}
return $this->get_language_data( $post->ID, $post->post_type, $all );
}
public function get_language_data( $original_id, $object_type = 'post', $all = false ) {
if ( ! $this->is_active() ) {
return array();
}
$languages = $this->get_languages();
global $sitepress;
$details = $sitepress->get_element_language_details( $original_id );
if ( ! $details ) {
$default_language = $sitepress->get_default_language();
$output = array(
'code' => $default_language,
'source' => $original_id,
'fallback' => array_values( array_diff( array_keys( $languages ), array( $default_language ) ) )
);
if ( $all ) {
$output['missing'] = $output['fallback'];
$output['translations'] = array( $default_language => $original_id );
}
return $output;
}
$translations = (array) $sitepress->get_element_translations( $details->trid, $object_type );
$available = array();
$is_source = false;
$source = null;
foreach ($translations as $translation ) {
$available[$translation->language_code] = (int) $translation->element_id;
if ( $translation->original ) {
if( (int) $original_id === (int) $translation->element_id ) {
$is_source = true;
} else {
$source = (int) $translation->element_id;
}
}
}
// Get valid domain / origin of this document
$this->switch_lang($details->language_code);
$homeURL = get_post_permalink($original_id);
$parsedUrl = parse_url($homeURL);
$homeURL = $parsedUrl['scheme'] . '://' . $parsedUrl['host'];
$this->switch_back();
$output = array(
'code' => $details->language_code,
'domain' => $homeURL,
'source' => $source,
'fallback' => array()
);
if ( $all ) {
$output['missing'] = array();
$output['translations'] = $available;
}
foreach ( $languages as $code => $language ) {
if ( ! isset( $available[$code] ) ) {
if ( $is_source ) {
$output['fallback'][] = $code;
}
if ( $all ) {
$output['missing'][] = $code;
}
}
}
return $output;
}
public function get_languages() {
if ( ! $this->is_active() ) {
return array();
}
return apply_filters('wpml_active_languages', array() );
}
public function get_default_language() {
global $sitepress;
return $this->is_active() ? $sitepress->get_default_language() : null;
}
public function appData() {
if ( ! $this->is_active() ) return null;
// I coudln't find a clean way to use the current language from the "backend" so unfortunately it always uses the default lang
return [
'defaultLang' => $this->get_default_language(),
'languages' => $this->get_languages(),
'translateableTypes' => $this->get_translateable_post_types(),
];
}
public function get_translateable_post_types() {
$output = array( 'page', 'post' );
if ( $this->is_active() ) {
global $sitepress;
$types = $sitepress->get_setting('custom_posts_sync_option');
foreach ($types as $key => $value) {
if ( $value ) {
$output[] = $key;
}
}
}
return $output;
}
public function get_element_language_details( $post_id ) {
if ( ! $this->is_active() ) {
return null;
}
global $sitepress;
return (array) $sitepress->get_element_language_details( $post_id );
}
public function get_language_label( $code ) {
// @TODO In createTranslation I believe that code runs before this
// can properly get the wpml_active_languages?
// Load languages / make sure WPML is loaded
$languages = $this->get_languages();
foreach ($languages as $lang_code => $value) {
if ( $code === $lang_code ) {
return isset( $value['translated_name'] ) ? $value['translated_name'] : '';
}
}
return $code;
}
public function before_get_permalink() {
if ( ! $this->is_active() ) {
return;
}
global $wpml_url_filters;
$wpml_url_filters->remove_global_hooks();
if ( $wpml_url_filters->frontend_uses_root() === true ) {
remove_filter( 'page_link', array( $wpml_url_filters, 'page_link_filter_root' ), 1, 2 );
} else {
remove_filter( 'page_link', array( $wpml_url_filters, 'page_link_filter' ), 1, 2 );
}
}
public function after_get_permalink() {
if ( ! $this->is_active() ) {
return;
}
global $wpml_url_filters;
$wpml_url_filters->add_global_hooks();
if ( $wpml_url_filters->frontend_uses_root() === true ) {
add_filter( 'page_link', array( $wpml_url_filters, 'page_link_filter_root' ), 1, 2 );
} else {
add_filter( 'page_link', array( $wpml_url_filters, 'page_link_filter' ), 1, 2 );
}
}
public function get_source_id_for_post( $post_id, $post_type = false ) {
if ( ! $this->is_active() ) {
return $post_id;
}
if ( ! $post_type ) {
$post = get_post( $post_id );
$post_type = $post->post_type;
}
global $sitepress;
$details = (array) $sitepress->get_element_language_details( $post_id );
if ( ! $details || ! isset( $details['source_language_code'] ) ) {
return $post_id;
}
return apply_filters( 'wpml_object_id', $post_id, $post_type, true, $details['source_language_code'] );
}
public function unset_endpoint( $endpoints ) {
unset($endpoints['cornerstone-endpoint']);
return $endpoints;
}
public function save_post() {
global $sitepress_settings;
if ( $sitepress_settings && isset( $sitepress_settings['translation-management'] ) && isset( $sitepress_settings['translation-management']['custom_fields_translation'] ) ) {
unset($sitepress_settings['translation-management']['custom_fields_translation']['_cornerstone_data']);
}
}
public function disable_browser_redirect ( $enable ) {
return did_action('cs_before_preview_frame') ? false : $enable;
}
public function createTranslation( $data ) {
if ( ! isset( $data['source'] ) ) {
return new \WP_Error( 'cornerstone', 'Source missing.' );
}
if ( ! isset( $data['lang'] ) ) {
return new \WP_Error( 'cornerstone', 'Target language missing.' );
}
$source_post = get_post( $data['source'] );
if ( ! is_a( $source_post, 'WP_POST' ) ) {
return new \WP_Error( 'cornerstone', 'Source invalid.' );
}
if ( ! in_array( $source_post->post_type, $this->get_translateable_post_types(), true ) ) {
return new \WP_Error( 'cornerstone', 'WPML does not allow this post type (' . $source_post->post_type . ') to be translated.' );
}
if ( ! function_exists('wpml_load_post_translation') ) {
return new \WP_Error( 'cornerstone', 'WPML not active' );
}
global $sitepress;
global $iclTranslationManagement;
$copy_from = isset( $data['copyFrom'] ) && $data['copyFrom'] ? $data['copyFrom'] : null;
// Valid post type
//$post_type = in_array($source_post->post_type, ['post', 'page'])
//? 'post_' . $source_post->post_type
//: $source_post->post_type;
$post_type = 'post_' . $source_post->post_type;
// Setup new post
$lang_label = $this->get_language_label( $data['lang'] );
$post_title = sprintf( csi18n('common.amended-title'), $source_post->post_title, $lang_label);
// Args for new post to create
$args = array(
'post_type' => $source_post->post_type,
'post_status' => $source_post->post_status,
'post_title' => $post_title,
);
// Copy slug for usability
if (!empty($source_post->slug)) {
$args['slug'] = $source_post->slug . '-' . $lang_label;
}
// Set trid
$this->wpml_lang = $data['lang'];
//$details = $this->get_element_language_details( $source_post->ID );
// Translation ID grabber
$this->wpml_trid = apply_filters(
'wpml_element_trid',
null,
$source_post->ID,
$post_type
);
// Translation details like language code
$details = apply_filters( 'wpml_post_language_details', null, $source_post->ID );
$source_language = isset( $details['language_code'] ) ? $details['language_code'] : null;
//
// trid will not exist if the header/footer has not been saved at least once since
// WPML was activated. If this is the case, we could trigger a save here to generate
// the source trid before continuing
//
if ( ! $this->wpml_trid ) {
$this->wpml_trid = null; // Try to populate this
return new \WP_Error( 'cornerstone', 'No Translation ID found. Please save before creating a translation and ensure that this is a post type WPML can translate' );
}
if ( $this->wpml_trid ) {
$id = wp_insert_post( $args, true );
if ( is_wp_error( $id ) ) {
return $id;
}
// Duplicate content
if ($copy_from) {
$doc = Document::locate($id);
$doc->update([
'title' => $post_title,
'clone' => $copy_from,
]);
$doc->save();
}
global $wpml_post_translations;
$sitepress->set_element_language_details (
$id,
'post_' . $source_post->post_type,
$this->wpml_trid,
$this->wpml_lang,
$source_language
);
$settings = get_option( 'icl_sitepress_settings' );
$translation_sync = new \WPML_Post_Synchronization( $settings, $wpml_post_translations, $sitepress );
$original_id = $wpml_post_translations->get_original_element( $id );
$new_post = (array) get_post($id);
$translation_sync->sync_with_translations( $original_id ? $original_id : $id, $new_post );
$translation_sync->sync_with_duplicates( $id );
if ( ! function_exists( 'icl_cache_clear' ) ) {
require_once WPML_PLUGIN_PATH . '/inc/cache.php';
}
icl_cache_clear( $source_post->post_type . 's_per_language', true );
wp_defer_term_counting( false );
if ( $source_post->post_type !== 'nav_menu_item' ) {
do_action( 'wpml_tm_save_post', $id, get_post( $id ), false );
}
// Flush object cache.
$cache_groups = array( 'ls_languages', 'element_translations' );
foreach ( $cache_groups as $group ) {
$cache = new \WPML_WP_Cache( $group );
$cache->flush_group_cache();
}
do_action( 'wpml_after_save_post', $id, $this->wpml_trid, $this->wpml_lang, $source_language );
return array( 'id' => $id );
}
return new \WP_Error( 'cornerstone', 'Could not locate a translation for the source post.' );
}
/**
* Get wpml_object_id for given data
* Returns zero on no translation
*/
private function getTranslatedObjectId($id, $docType, $lang) {
// Attempt to find translation
// Fallback to what was passed after
return (int) apply_filters(
'wpml_object_id',
$id,
Document::resolvePostTypeForDocType( $docType ),
false,
$lang
);
}
/**
* Filter Assignment to be proper translation page
*
* @param array $input ['id', 'type']
*/
public function filterAssignedDoc($input) {
list ($type, $assignment) = $input;
// Find master post id to get proper translation
$master = apply_filters( 'wpml_master_post_from_duplicate', $assignment );
// Not passed master post id
$assignmentToCheck = !empty($master)
? $master
: $assignment;
// Attempt to find translation
// Fallback to what was passed after
$translated = $this->getTranslatedObjectId(
$assignmentToCheck,
$type,
apply_filters( 'wpml_current_language', null )
);
// Filter input or keep the same
$input[1] = empty($translated)
? $assignment
: $translated;
return $input;
}
/**
* or /es/cornerstone
* which won't work
*/
public function filterAppURL($url) {
// We use get_option because WPML overwrites home_url()
$url = get_option('home') . '/' . apply_filters('cs_app_slug', 'cornerstone');
return $url;
}
/**
* If the language default is to redirect to a directory
* the url base will be wrong
* this fixes that and usually just points to /
*/
public function filterUrlBase($result) {
$directory_for_default_language = apply_filters( 'wpml_sub_setting', false, 'urls', 'directory_for_default_language' );
// Not using default directory
if (empty($directory_for_default_language)) {
return $result;
}
$parsed = parse_url(get_option("home") . "/");
$urlBase = empty($parsed['path'])
? "/"
: $parsed['path'];
return $urlBase;
}
/**
* Hack to remove WPML page builder settings
* @TODO how do we really want to handle this?
*/
public function preventDuplicatePageSave() {
// To turn off and dev
if (apply_filters("cs_wpml_enable_page_builder_integration", false)) {
return;
}
// Prevent CS string translated for now
remove_all_actions('wpml_page_builder_string_translated');
}
/**
* Set translation modes of our layout types
*/
public function setTranslationModeLayouts() {
$layoutTypes = [
'cs_layout_archive',
'cs_layout_single',
'cs_layout_archive_wc',
'cs_layout_single_wc',
];
$translationMode = 'translate';
foreach ($layoutTypes as $type) {
do_action( 'wpml_set_translation_mode_for_post_type', $type, $translationMode );
}
}
/**
* Figure out which languages need filter suppression
*/
public function beforeMigrateUntypedLayoutsNonDefault($ids = []) {
if (!apply_filters("cs_wpml_use_untyped_migration", true)) {
return;
}
// Nothing to do
if (!$this->hasLegacyLayoutTranslations()) {
return;
}
$this->removeWPMLActions();
if (!defined("CS_MIGRATE_UNTYPED_USE_RAW_UPDATE")) {
define("CS_MIGRATE_UNTYPED_USE_RAW_UPDATE", true);
}
$this->cleanupCSLayoutsNotMigrated();
$this->postTypeMigrationData = $ids;
}
// Remove all WPML filters
// This is so WPML doesn't move the icl_translations
// table by itself which won't move stuff correctly
// all the time
public function removeWPMLActions() {
remove_all_filters("pre_get_posts");
remove_all_filters("pre_get_post");
remove_all_actions("save_post");
remove_all_actions("pre_post_update");
}
/**
* Is post in different then default language
*/
public function isDifferentLanguage($id, $postType) {
// Translation ID grabber
$wpml_trid = apply_filters(
'wpml_element_trid',
null,
$id,
"post_" . $postType
);
// This one does not give you source_language_code
//$details = apply_filters( 'wpml_post_language_details', null, $id );
$details = (array) apply_filters( 'wpml_element_language_details', null, [
'element_id' => $id,
'element_type' => 'post_' . $postType,
]);
$defaultLang = apply_filters( 'wpml_default_language', null );
// Not even setup or no trid for this post
if (
empty($defaultLang)
|| empty($wpml_trid)
|| empty($details)
// If not the original post but a translation
|| empty($details['source_language_code'])
) {
return false;
}
return true;
}
/**
* Saving ignore list did not use this method
*/
public function beforeMigrateUntypedLayoutsIgnoreList($defaultVal, $id) {
return false;
}
/**
* This essentially runs the same functions
* just after the post types
*/
public function afterMigrateUntypedLayouts($ids = []) {
//$this->cleanupCSLayoutsNotMigrated();
}
/**
* Legacy translation detection
*/
public function hasLegacyLayoutTranslations() {
global $wpdb;
$table = esc_sql("{$wpdb->prefix}icl_translations");
// Grab Check if has a count
$sql = "SELECT COUNT(element_id) as count
FROM {$table}
WHERE element_type = 'post_cs_layout'
";
$results = $wpdb->get_results($sql, ARRAY_A);
return !empty($results[0]['count'])
&& (int)$results[0]['count'] !== 0;
}
/**
* Raw SQL updates of icl_translations table
* Runs before post type change as
* changing post type before caused issues
*/
public function cleanupCSLayoutsNotMigrated() {
if (!apply_filters("cs_wpml_use_untyped_migration", true)) {
return;
}
global $wpdb;
$table = esc_sql("{$wpdb->prefix}icl_translations");
// Grab by legacy post type
$sql = "SELECT element_id
FROM {$table}
WHERE element_type = 'post_cs_layout'
";
$results = $wpdb->get_results($sql);
foreach ($results as $result) {
$id = $result->element_id;
$doc = Document::locate( $id );
// No doc found, but translation
// data there
if (empty($doc)) {
continue;
}
$newType = Document::resolvePostTypeForDocType($doc->getDocType());
// Post type is already changed at this point
$type = $newType;
// Always needed for this table
$type = "post_" . $type;
// Update icl_translations
$sql = "UPDATE {$table}
SET element_type = %s
WHERE element_id = %d
AND element_type = 'post_cs_layout'
";
$prepared = $wpdb->prepare($sql, $type, $id);
// Run update query
$wpdb->query($prepared);
}
}
// Adds in a proper documentUrl for the translations shop page
public function filterWCArchiveBuilderInfo($builderInfo, $doc) {
$langInfo = $this->get_element_language_details($doc->id());
if (empty($langInfo)) {
return $builderInfo;
}
// Grab Shop ID based on document language code
$this->switch_lang($langInfo['language_code']);
$shop_id = wc_get_page_id( 'shop' );
$this->switch_back();
$builderInfo['documentUrl'] = get_permalink($shop_id);
return $builderInfo;
}
}