Disable Yoast 14+ for a dynamic archive - yoast

When using Yoast with WC Vendors Pro it will treat all vendor store pages as the main store archive page. This results in loading the main sites Open Graph data instead of the vendors SEO data that they input.
Up until Yoast 14.0, the code below worked well, but it doesn't work with Yoast 14 anymore. Any ideas on how to revise this since they've deprecated functions?
remove_action( 'template_redirect', 'wpseo_frontend_head_init', 999 );
add_action( 'template_redirect', 'enable_wcv_store_seo' );
function enable_wcv_store_seo(){
if ( !WCV_Vendors::is_vendor_page() ) {
add_action( 'template_redirect', 'wpseo_frontend_head_init', 999 );
}
}
Based on a snippet of code found on the Yoast developer portal I was able to target the vendor pages and successfully change the locale (as a test, this doesn't need to be changed).
function fleece_wpseo_change_og_locale( $locale ) {
if ( WCV_Vendors::is_vendor_page() ) {
return 'nl_NL';}
}
add_filter( 'wpseo_locale', 'fleece_wpseo_change_og_locale' );
I can't find enough documentation to figure out how to target the proper items and know just enough to be dangerous.

I managed to code an answer and gave it to WC Vendors. This is the more generic option in case someone stumbles onto this. I went a bit deeper into customization on the code I finally implemented:
// Change OG title for Yoast on Vendor Pages
function wcv_wpseo_change_og_title( $title ) {
WC_Vendors::log( $title );
if ( WCV_Vendors::is_vendor_page() ) {
$vendor_shop = urldecode( get_query_var( 'vendor_shop' ) );
$vendor_id = WCV_Vendors::get_vendor_id( $vendor_shop );
$shop_title = get_user_meta( $vendor_id, 'pv_shop_name', true );
$og_title = get_user_meta( $vendor_id, 'wcv_seo_fb_title', true );
if ( ! empty( $og_title ) ) {
$title = $og_title;
} else {
$title = $shop_title;
}
}
return $title;
}
add_filter( 'wpseo_opengraph_title', 'wcv_wpseo_change_og_title' );
// Change Meta description for Yoast on Vendor Pages
function wcv_wpseo_change_meta_description( $desc ) {
if ( WCV_Vendors::is_vendor_page() ) {
$vendor_shop = urldecode( get_query_var( 'vendor_shop' ) );
$vendor_id = WCV_Vendors::get_vendor_id( $vendor_shop );
$shopdesc = get_user_meta( $vendor_id, 'pv_shop_description', true );
$meta_desc = get_user_meta( $vendor_id, 'wcv_seo_meta_description', true );
if ( ! empty( $meta_desc ) ) {
$desc = $meta_desc;
} else {
$desc = $shopdesc;
}
}
return $desc;
}
add_filter( 'wpseo_metadesc', 'wcv_wpseo_change_meta_description' );
// Change OG description for Yoast on Vendor Pages
function wcv_wpseo_change_og_description( $desc ) {
if ( WCV_Vendors::is_vendor_page() ) {
$vendor_shop = urldecode( get_query_var( 'vendor_shop' ) );
$vendor_id = WCV_Vendors::get_vendor_id( $vendor_shop );
$shopdesc = get_user_meta( $vendor_id, 'pv_shop_description', true );
$meta_desc = get_user_meta( $vendor_id, 'wcv_seo_meta_description', true );
$og_desc = get_user_meta( $vendor_id, 'wcv_seo_fb_description', true );
if ( ! empty( $og_desc ) ) {
$desc = $og_desc;
} elseif (! empty( $meta_desc)) {
$desc = $meta_desc;
} else {
$desc = $shopdesc;
}
}
return $desc;
}
add_filter( 'wpseo_opengraph_desc', 'wcv_wpseo_change_og_description' );
// Change OG image for Yoast on Vendor Pages
function wcv_wpseo_change_og_image ( $image ) {
if ( WCV_Vendors::is_vendor_page() ) {
$vendor_shop = urldecode( get_query_var( 'vendor_shop' ) );
$vendor_id = WCV_Vendors::get_vendor_id( $vendor_shop );
$og_image = get_user_meta( $vendor_id, 'wcv_seo_fb_image_id', true );
if (!empty($og_image)) {
$image = wp_get_attachment_url( $og_image );
}
}
return $image;
}
add_filter( 'wpseo_opengraph_image', 'wcv_wpseo_change_og_image' );
// Change OG URL for Yoast on Vendor Pages.
function wcv_wpseo_change_og_url ( $url ) {
if ( WCV_Vendors::is_vendor_page() ) {
$vendor_shop = urldecode( get_query_var( 'vendor_shop' ) );
$vendor_id = WCV_Vendors::get_vendor_id( $vendor_shop );
$url = WCV_Vendors::get_vendor_shop_page( $vendor_id );
}
return $url;
}
add_filter( 'wpseo_opengraph_url', 'wcv_wpseo_change_og_url' );

Related

Ckeditor 5, image src to data-src

Ckeditor 5 image upload by default outputs <img src="url">, while I want to integrate UI kit images with output like <img uk-img data-src="url" data-srcset="sourcet">.
I am having trouble getting src and srcset from the uploaded image in downcastCustomImg() function, because viewElement is an EmptyElement class and getIdentity() method returns an empty img without attributes.
My idea is to convert src to data-src and srcset to data-srceset, also add empty uk-img.
How do I achieve that?
P.s. using vue.js 3 with cli
I am trying to use the official guide. my code is as follows:
export default class CustomFigureAttributes {
/**
* Plugin's constructor - receives an editor instance on creation.
*/
constructor( editor ) {
// Save reference to the editor.
this.editor = editor;
}
/**
* Sets the conversion up and extends the table & image features schema.
*
* Schema extending must be done in the "afterInit()" call because plugins define their schema in "init()".
*/
afterInit() {
const editor = this.editor;
editor.model.schema.extend('image', {
allowAttributes: ['data-src', 'data-srcset', 'uk-img']
});
editor.conversion.for( 'upcast' ).add( upcastCustomClasses( 'figure' ), { priority: 'low' } );
editor.conversion.for( 'downcast' ).add( downcastCustomImg( 'img', 'image' ), { priority: 'low' } );
// Define custom attributes that should be preserved.
setupCustomAttributeConversion( 'img', 'image', 'uk-img', editor );
setupCustomAttributeConversion( 'img', 'image', 'src', editor );
setupCustomAttributeConversion( 'img', 'image', 'srcset', editor );
}
}
function downcastCustomImg( viewElementName, modelElementName ) {
return dispatcher => dispatcher.on( `insert:${ modelElementName }`, ( evt, data, conversionApi ) => {
if ( conversionApi.consumable.consume( data.item, 'insert' ) ) {
return;
}
const modelElement = data.item;
const viewFigure = conversionApi.mapper.toViewElement( modelElement );
const viewElement = findViewChild( viewFigure, viewElementName, conversionApi );
if ( !viewElement ) {
return;
}
const src = viewElement.getAttribute( 'src' ) || [];
// CANNOT GET SRC & SRCSET from viewElement, because it is an EmptyElement !!!
conversionApi.writer.setAttribute( 'uk-img', '', viewElement );
conversionApi.writer.setAttribute( 'data-src', src, viewElement );
// conversionApi.writer.setAttribute( 'data-srcset', srcSet, viewElement );
} );
}
/**
* Helper method that searches for a given view element in all children of the model element.
*
* #param {module:engine/view/item~Item} viewElement
* #param {String} viewElementName
* #param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
* #return {module:engine/view/item~Item}
*/
function findViewChild( viewElement, viewElementName, conversionApi ) {
const viewChildren = Array.from( conversionApi.writer.createRangeIn( viewElement ).getItems() );
return viewChildren.find( item => item.is( 'element', viewElementName ) );
}
/**
* Sets up a conversion for a custom attribute on the view elements contained inside a <figure>.
*
* This method:
* - Adds proper schema rules.
* - Adds an upcast converter.
* - Adds a downcast converter.
*/
function setupCustomAttributeConversion( viewElementName, modelElementName, viewAttribute, editor ) {
// Extends the schema to store an attribute in the model.
const modelAttribute = `${ viewAttribute }`;
editor.model.schema.extend( modelElementName, { allowAttributes: [ modelAttribute ] } );
editor.conversion.for( 'upcast' ).add( upcastAttribute( viewElementName, viewAttribute, modelAttribute ) );
editor.conversion.for( 'downcast' ).add( downcastAttribute( modelElementName, viewElementName, viewAttribute, modelAttribute ) );
}
/**
* Returns the custom attribute upcast converter.
*/
function upcastAttribute( viewElementName, viewAttribute, modelAttribute ) {
return dispatcher => dispatcher.on( `element:${ viewElementName }`, ( evt, data, conversionApi ) => {
const viewItem = data.viewItem;
const modelRange = data.modelRange;
const modelElement = modelRange && modelRange.start.nodeAfter;
if ( !modelElement ) {
return;
}
conversionApi.writer.setAttribute( modelAttribute, viewItem.getAttribute( viewAttribute ), modelElement );
} );
}
/**
* Returns the custom attribute downcast converter.
*/
function downcastAttribute( modelElementName, viewElementName, viewAttribute, modelAttribute ) {
return dispatcher => dispatcher.on( `insert:${ modelElementName }`, ( evt, data, conversionApi ) => {
const modelElement = data.item;
const viewFigure = conversionApi.mapper.toViewElement( modelElement );
const viewElement = findViewChild( viewFigure, viewElementName, conversionApi );
if ( !viewElement ) {
return;
}
conversionApi.writer.setAttribute( viewAttribute, modelElement.getAttribute( modelAttribute ), viewElement );
} );
}

CKEditor5 Custom Plugin changes the Model but not the View

I have create a custom build of CKEditor5 and created a plugin BorderColor. The Plugin isn't working good. The problem is that the model is changing, but the view isn't changed.
border-color.js
import Plugin from '#ckeditor/ckeditor5-core/src/plugin';
import BorderColorEditing from './border-color-editing';
import BorderColorUi from './border-color-ui';
export default class BorderColor extends Plugin {
/**
* #inheritDoc
*/
static get requires() {
return [ BorderColorEditing, BorderColorUi ];
}
/**
* #inheritDoc
*/
static get pluginName() {
return 'BorderColor';
}
}
border-color-ui.js
import ColorUI from '#ckeditor/ckeditor5-font/src/ui/colorui';
export default class BorderColorUi extends ColorUI {
/**
* #inheritDoc
*/
constructor( editor ) {
const t = editor.locale.t;
super( editor, {
commandName: 'borderColor',
componentName: 'borderColor',
dropdownLabel: t( 'Rahmenfarbe' )
} );
}
/**
* #inheritDoc
*/
static get pluginName() {
return 'BorderColorUI';
}
}
border-color-editing.js
import Plugin from '#ckeditor/ckeditor5-core/src/plugin';
import BorderColorCommand from './border-color-command';
import { isDefault, isSupported, supportedOptions } from './utils';
export default class BorderColorEditing extends Plugin {
/**
* #inheritDoc
*/
constructor( editor ) {
super( editor );
editor.config.define( 'borderColor', supportedOptions );
}
/**
* #inheritDoc
*/
init() {
const editor = this.editor;
const schema = editor.model.schema;
// Filter out unsupported options.
const enabledOptions = editor.config.get( 'borderColor.colors' );
// Allow alignment attribute on all blocks.
schema.extend( '$block', { allowAttributes: 'borderColor' } );
editor.model.schema.setAttributeProperties( 'borderColor', { isFormatting: true } );
editor.model.schema.addAttributeCheck( ( context, attributeName ) => {
if ( context.endsWith( 'table' ) || context.endsWith( 'tableRow' ) || context.endsWith( 'tableCell' )) {
return true;
}
} );
const definition = _buildDefinition( enabledOptions.filter( option => !isDefault( option ) ) );
editor.conversion.attributeToAttribute( definition );
editor.commands.add( 'borderColor', new BorderColorCommand( editor ) );
}
}
// Utility function responsible for building converter definition.
// #private
function _buildDefinition( options ) {
const definition = {
model: {
key: 'borderColor',
values: options.slice()
},
view: {}
};
for ( const option of options ) {
const _def = { key: 'style', value: {} };
_def.value[ 'border-color' ] = option.color;
definition.view[ option ] = _def;
}
return definition;
}
border-color-command.js
import Command from '#ckeditor/ckeditor5-core/src/command';
import first from '#ckeditor/ckeditor5-utils/src/first';
import { isDefault } from './utils';
const BORDER_COLOR = 'borderColor';
export default class BorderColorCommand extends Command {
/**
* #inheritDoc
*/
refresh() {
let childBlocks;
if ( this.editor.model.document.selection.getSelectedElement() != null )
childBlocks = this.getSelectedBlocks( this.editor.model.document, 'all-tablerow' );
const firstBlock = this.editor.model.document.selection.getSelectedElement() != null
? this.editor.model.document.selection.getSelectedElement()
: first( this.editor.model.document.selection.getSelectedBlocks() );
// As first check whether to enable or disable the command as the value will always be false if the command cannot be enabled.
this.isEnabled = !!firstBlock && this._canBeAligned( firstBlock );
/**
* A value of the current block's alignment.
*
* #observable
* #readonly
* #member {String} #value
*/
if ( this.isEnabled && firstBlock.hasAttribute( BORDER_COLOR ) )
this.value = firstBlock.getAttribute( BORDER_COLOR );
else if ( this.isEnabled && childBlocks && childBlocks[ 0 ].hasAttribute( BORDER_COLOR ) )
this.value = childBlocks[ 0 ].getAttribute( BORDER_COLOR );
else
this.value = '';
}
/**
* Executes the command. Applies the alignment `value` to the selected blocks.
* If no `value` is passed, the `value` is the default one or it is equal to the currently selected block's alignment attribute,
* the command will remove the attribute from the selected blocks.
*
* #param {Object} [options] Options for the executed command.
* #param {String} [options.value] The value to apply.
* #fires execute
*/
execute( options = {} ) {
const editor = this.editor;
const model = editor.model;
const doc = model.document;
const value = options.value;
model.change( writer => {
let childBlocks;
if ( this.editor.model.document.selection.getSelectedElement() != null )
childBlocks = this.getSelectedBlocks( this.editor.model.document, 'all-tablerow' );
const firstBlock = this.editor.model.document.selection.getSelectedElement() != null
? this.editor.model.document.selection.getSelectedElement()
: first( this.editor.model.document.selection.getSelectedBlocks() );
let currentAlignment;
if ( firstBlock.hasAttribute( BORDER_COLOR ) )
currentAlignment = firstBlock.getAttribute( BORDER_COLOR );
else if ( childBlocks && childBlocks[ 0 ].hasAttribute( BORDER_COLOR ) )
currentAlignment = childBlocks[ 0 ].getAttribute( BORDER_COLOR );
// Remove alignment attribute if current alignment is:
// - default (should not be stored in model as it will bloat model data)
// - equal to currently set
// - or no value is passed - denotes default alignment.
const removeAlignment = isDefault( value ) || currentAlignment === value || !value;
console.log( 'childBlocks / firstBlock', childBlocks, firstBlock, currentAlignment);
const blocks = childBlocks ? Array.from( childBlocks ) : [];
blocks.push( firstBlock );
if ( removeAlignment ) {
removeAlignmentFromSelection( blocks, writer );
} else {
setAlignmentOnSelection( blocks, writer, value );
}
} );
}
getSelectedBlocks( doc, value ) {
let blocks;
if ( doc.selection.getSelectedElement() == null )
blocks = Array.from( doc.selection.getSelectedBlocks() ).filter( block => this._canBeAligned( block ) )
else {
blocks = [ doc.selection.getSelectedElement() ];
if ( value.indexOf( 'tablerow' ) > -1 ) {
var children = doc.selection.getSelectedElement().getChildren();
console.log( 'Selected Element Children', children );
var out = [];
let _next = children.next();
while ( !_next.done ) {
console.log( _next );
if ( _next.value.getChildren ) {
const children2 = _next.value.getChildren();
let _next2 = children2.next();
while ( !_next2.done ) {
console.log( _next2 );
out[ out.length ] = _next2.value;
_next2 = children2.next();
}
}
else {
out[ out.length ] = _next.value;
}
_next = children.next();
}
blocks = out;
}
}
return blocks;
}
/**
* Checks whether a block can have alignment set.
*
* #private
* #param {module:engine/model/element~Element} block The block to be checked.
* #returns {Boolean}
*/
_canBeAligned( block ) {
return this.editor.model.schema.checkAttribute( block, BORDER_COLOR );
}
}
// Removes the alignment attribute from blocks.
// #private
function removeAlignmentFromSelection( blocks, writer ) {
for ( const block of blocks ) {
writer.removeAttribute( BORDER_COLOR, block );
}
}
// Sets the alignment attribute on blocks.
// #private
function setAlignmentOnSelection( blocks, writer, border ) {
for ( const block of blocks ) {
writer.setAttribute( BORDER_COLOR, border, block );
}
}
The code can also be seen at Github: CKEditor Custom Build Github.
I have found a solution.
I edited border-color-editing.js:
I replaced following code:
editor.conversion.attributeToAttribute( definition );
with:
editor.conversion.for( 'downcast' ).attributeToAttribute( {
model: 'borderColor',
view: function( option ) {
const allSelectedBlocks = getAllSelectedBlocks( this.editor );
const borderAttribute = allSelectedBlocks[ 0 ].getAttribute( BORDER );
if ( !borderAttribute || !option )
return { key: 'style', value: '' };
const _value = {};
const styleOption = borderAttribute.replace( '-tablerow', '' );
_value[ 'border' + ( styleOption == 'all' ? '' : '-' + styleOption ) + '-color' ] = option ? option : 'black';
return { key: 'style', value: _value };
}.bind( this )
} );

Gravityform - update ACF Repeater subfield mixed with dynamic pre-populate fields

I have an ACF repeater field that create Courses with a certain amount of place available. This repeater is on each single custom-post-type courses.
In the other hand, I have a Gravityform that have a radio field who is auto-populated with those ACF repeater:
add_filter( 'gform_pre_render_3', 'populate_posts' );
add_filter( 'gform_pre_validation_3', 'populate_posts' );
add_filter( 'gform_pre_submission_filter_3', 'populate_posts' );
add_filter( 'gform_admin_pre_render_3', 'populate_posts' );
function populate_posts( $form ) {
foreach ( $form['fields'] as &$field ) {
// Vient check tous les fields du formulaire
if ( $field->type != 'radio' || strpos( $field->cssClass, 'populate-posts' ) === false ) {
// Choisit seulement les fields de type [radio] qui ont la class [populate-posts]
continue;
}
$date_type = get_field( 'date_type' );
$range_dates = get_field( 'date_range_repeater' );
$sinlge_dates = get_field( 'sinlge_dates_repeater' );
$choices = array();
if( $date_type === 'date_type_range' ){
foreach ( $range_dates as $range_date ) {
$dateformatstring = "j F Y";
$dateclean_start = strtotime( $range_date['date_starting'] );
$final_date_start = date_i18n($dateformatstring, $dateclean_start);
$dateclean_end = strtotime( $range_date['date_ending'] );
$final_date_end = date_i18n($dateformatstring, $dateclean_end);
$choices[] = array(
'text' => 'Du '.$final_date_start.' au '.$final_date_end.'<br />'.$range_date['availability'].' places disponibles',
'value' => 'Du '.$final_date_start.' au '.$final_date_end
);
}
}elseif( $date_type === 'date_type_single' ){
foreach ( $sinlge_dates as $sinlge_date ) {
$dateformatstring = "j F Y";
$dateclean_start = strtotime( $sinlge_date['date_starting'] );
$final_date_start = date_i18n($dateformatstring, $dateclean_start);
$dateclean_end = strtotime( $sinlge_date['date_ending'] );
$final_date_end = date_i18n($dateformatstring, $dateclean_end);
$choices[] = array(
'text' => 'Du '.$final_date_start.' au '.$final_date_end.'<br />'.$sinlge_date['availability'].' places disponibles',
'value' => 'Du '.$final_date_start.' au '.$final_date_end
);
}
}else{}
// update 'Select a Post' to whatever you'd like the instructive option to be
$field->placeholder = 'Select a Post';
$field->choices = $choices;
}
return $form;
}
I want that after each submission the available place number decrease from 1. I have figured out how to manage this with this ACF function:
https://www.advancedcustomfields.com/resources/update_sub_field/
This is my code for this:
add_action( 'gform_after_submission', 'set_post_content', 10, 2 );
function set_post_content( $entry, $form ) {
//$radio_value = rgar( $entry, '11' );
$repeater = 'date_range_repeater';
$acf_repeater = get_field('date_range_repeater' ); // get all the rows
$row = 0;
$specific_row = $acf_repeater[$row];
$sub_field = 'availability';
$current_availability = $specific_row['availability'];
$availability_new = --$current_availability;
update_sub_field( array($repeater, ++$row, $sub_field), $availability_new );
//echo $radio_value;
}
The thing is I have to select manually which row must be used to update de subfield. How can I manage to detect in which ACF repeater row is my submitted value ?
Thanks !
Maybe you can add some hidden fields in your GF and populate them in your function populate_posts( $form ){}like that you should have accessible directly in your after_submit function.

replacing "view cart" button with "proceed to checkout" button upon adding a product to cart

After product page reloads upon adding the product to cart, a notification appears on top saying "product was added to cart" followed by a "view cart" button.
However after product gets added to cart I want to direct customers to checkout page (where I'll add cart page functionality), not cart page. How can I replace the notification's link to cart with a link to checkout page?
wc_add_to_cart_message() function (in includes/wc-cart-functions.php) seems to generate this particular notification, should I override this function? If so, how?
try this...
function rei_wc_add_to_cart_message( $message, $product_id ) {
$titles = array();
if ( is_array( $product_id ) ) {
foreach ( $product_id as $id ) {
$titles[] = get_the_title( $id );
}
} else {
$titles[] = get_the_title( $product_id );
}
$titles = array_filter( $titles );
$added_text = sprintf( _n( '%s has been added to your cart.', '%s have been added to your cart.', sizeof( $titles ), 'woocommerce' ), wc_format_list_of_items( $titles ) );
// Output success messages
if ( 'yes' === get_option( 'woocommerce_cart_redirect_after_add' ) ) {
$return_to = apply_filters( 'woocommerce_continue_shopping_redirect', wp_get_referer() ? wp_get_referer() : home_url() );
$message = sprintf( '%s %s', esc_url( $return_to ), esc_html__( 'Continue Shopping', 'woocommerce' ), esc_html( $added_text ) );
} else {
$message = sprintf( '%s %s', esc_url( wc_get_page_permalink( 'checkout' ) ), esc_html__( 'Proceed to Checkout', 'woocommerce' ), esc_html( $added_text ) );
}
return $message;
}
add_filter('wc_add_to_cart_message','rei_wc_add_to_cart_message',10,2);

How can I get my Amazon S3 encoding function to understand subfolders inside buckets?

I wrote the following function to encode my S3 links so that they handle Amazon's S3 encoding system to protect links. The trouble is that it only works if the file is in the bucket. If I make a subfolder to the bucket and stick the file in there, it's not working. What am I doing wrong?
function encodeS3($sURL,$sAccessKey,$sSecretKey,$nExpireMinutes = 5) {
$sFile = basename($sURL);
$sBucket = basename(str_replace('/' . $sFile,'',$sURL));
$asQuery = array(
'AWSAccessKeyId' => $sAccessKey,
);
$nExpireSecs = absint( $nExpireMinutes ) * 60;
$nExpireSecs = time() + absint( $nExpireSecs );
$asQuery[ 'Expires' ] = $nExpireSecs;
$sAmazonText = "GET\n\n\n{$nExpireSecs}\n/{$sBucket}/{$sFile}";
$asQuery[ 'Signature' ] = urlencode( base64_encode( ( hash_hmac( 'sha1', utf8_encode( $sAmazonText ), $sSecretKey, TRUE ) ) ) );
$s = add_query_arg( $asQuery, "https://s3.amazonaws.com/{$sBucket}/{$sFile}" );
return esc_url($s);
}
I just needed to change the way I was parsing the URL.
function encodeS3($sURL,$sAccessKey,$sSecretKey,$nExpireMinutes = 5) {
/*
$sFile = basename($sURL);
$sBucket = basename(str_replace('/' . $sFile,'',$sURL));
*/
$sURL = str_replace('https://','',$sURL);
$sURL = str_replace('http://','',$sURL);
$sURL = str_replace(urlencode('https://'),'',$sURL);
$sURL = str_replace(urlencode('http://'),'',$sURL);
$asParts = explode('/',$sURL);
array_shift($asParts);
$sBucket = array_shift($asParts);
$sFile = implode('/',$asParts);
$asQuery = array(
'AWSAccessKeyId' => $sAccessKey,
);
$nExpireSecs = absint( $nExpireMinutes ) * 60;
$nExpireSecs = time() + absint( $nExpireSecs );
$asQuery[ 'Expires' ] = $nExpireSecs;
$sAmazonText = "GET\n\n\n{$nExpireSecs}\n/{$sBucket}/{$sFile}";
$asQuery[ 'Signature' ] = urlencode( base64_encode( ( hash_hmac( 'sha1', utf8_encode( $sAmazonText ), $sSecretKey, TRUE ) ) ) );
$s = add_query_arg( $asQuery, "https://s3.amazonaws.com/{$sBucket}/{$sFile}" );
return esc_url($s);
}
BTW, the above uses some functions that are only available in WordPress. If you need straight PHP, you'll have to adjust this.