Home › Forums › Feature Requests › Change how the link field is stored in the database
Originally posted in the Feedback forum but I think it belongs here and will hopefully get more attention.
I have this scenario I run into far too often.. I’m developing a site locally that uses ACF link field to link to local and external URLs. It’s now time to go live, I export the database and run a query to replace any permalinks (e.g. http://example.local –> http://example.com). Perfect done! Until I look at the live site and see all link fields that had values relative to the site aren’t being returned. Ahhh!
Problem: link field values are being stored as serialised arrays which fail to unserialise if any of the array value lengths change (due to my DB query).
Potential solution: Store as html and parse to return values.
Code: I have quickly modified the class-acf-field-link.php to store and parse an tag to return field values.
<?php
if( ! class_exists('acf_field_link') ) :
class acf_field_link extends acf_field {
/*
* __construct
*
* This function will setup the field type data
*
* @type function
* @date 5/03/2014
* @since 5.0.0
*
* @param n/a
* @return n/a
*/
function initialize() {
// vars
$this->name = 'link';
$this->label = __("Link",'acf');
$this->category = 'relational';
$this->defaults = array(
'return_format' => 'array',
);
}
/*
* get_link
*
* description
*
* @type function
* @date 16/5/17
* @since 5.5.13
*
* @param $post_id (int)
* @return $post_id (int)
*/
function get_link( $value = '' ) {
// vars
$link = array(
'title' => '',
'url' => '',
'target' => ''
);
// html element (ACF N/A)
if ( is_string($value) && strncmp($value, '<a', 2) === 0 ) {
preg_match('/<a.*>(.*)<\/a>/Uims', $value, $match);
$link['title'] = isset($match[1]) ? $match[1] : '';
$link['url'] = $this->get_html_attribute( 'href', $value );
$link['target'] = $this->get_html_attribute( 'target', $value );
// array (ACF 5.6.0)
} elseif( is_array($value) ) {
$link = array_merge($link, $value);
// post id (ACF < 5.6.0)
} elseif( is_numeric($value) ) {
$link['title'] = get_the_title( $value );
$link['url'] = get_permalink( $value );
// string (ACF < 5.6.0)
} elseif( is_string($value) ) {
$link['url'] = $value;
}
// return
return $link;
}
/*
* render_field()
*
* Create the HTML interface for your field
*
* @param $field - an array holding all the field's data
*
* @type action
* @since 3.6
* @date 23/01/13
*/
function render_field( $field ){
// vars
$div = array(
'id' => $field['id'],
'class' => $field['class'] . ' acf-link',
);
// render scripts/styles
acf_enqueue_uploader();
// get link
$link = $this->get_link( $field['value'] );
// classes
if( $link['url'] ) {
$div['class'] .= ' -value';
}
if( $link['target'] === '_blank' ) {
$div['class'] .= ' -external';
}
/*<textarea id="<?php echo esc_attr($field['id']); ?>-textarea"><?php
echo esc_textarea('<a href="'.$link['url'].'" target="'.$link['target'].'">'.$link['title'].'</a>');
?></textarea>*/
?>
<div <?php acf_esc_attr_e($div); ?>>
<div class="acf-hidden">
<a class="link-node" href="<?php echo esc_url($link['url']); ?>" target="<?php echo esc_attr($link['target']); ?>"><?php echo esc_html($link['title']); ?></a>
<?php foreach( $link as $k => $v ): ?>
<?php acf_hidden_input(array( 'class' => "input-$k", 'name' => $field['name'] . "[$k]", 'value' => $v )); ?>
<?php endforeach; ?>
</div>
<a href="#" class="button" data-name="add" target=""><?php _e('Select Link', 'acf'); ?></a>
<div class="link-wrap">
<span class="link-title"><?php echo esc_html($link['title']); ?></span>
<a class="link-url" href="<?php echo esc_url($link['url']); ?>" target="_blank"><?php echo esc_html($link['url']); ?></a>
<i class="acf-icon -link-ext acf-js-tooltip" title="<?php _e('Opens in a new window/tab', 'acf'); ?>"></i><?php
?><a class="acf-icon -pencil -clear acf-js-tooltip" data-name="edit" href="#" title="<?php _e('Edit', 'acf'); ?>"></a><?php
?><a class="acf-icon -cancel -clear acf-js-tooltip" data-name="remove" href="#" title="<?php _e('Remove', 'acf'); ?>"></a>
</div>
</div>
<?php
}
/*
* render_field_settings()
*
* Create extra options for your field. This is rendered when editing a field.
* The value of $field['name'] can be used (like bellow) to save extra data to the $field
*
* @type action
* @since 3.6
* @date 23/01/13
*
* @param $field - an array holding all the field's data
*/
function render_field_settings( $field ) {
// return_format
acf_render_field_setting( $field, array(
'label' => __('Return Value','acf'),
'instructions' => __('Specify the returned value on front end','acf'),
'type' => 'radio',
'name' => 'return_format',
'layout' => 'horizontal',
'choices' => array(
'array' => __("Link Array",'acf'),
'url' => __("Link URL",'acf'),
'element' => __("HTML Element",'acf'),
)
));
}
/*
* format_value()
*
* This filter is appied to the $value after it is loaded from the db and before it is returned to the template
*
* @type filter
* @since 3.6
* @date 23/01/13
*
* @param $value (mixed) the value which was loaded from the database
* @param $post_id (mixed) the $post_id from which the value was loaded
* @param $field (array) the field array holding all the field options
*
* @return $value (mixed) the modified value
*/
function format_value( $value, $post_id, $field ) {
// bail early if no value
if( empty($value) ) return $value;
// get link
$link = $this->get_link( $value );
// format value
if( $field['return_format'] == 'url' ) {
return $link['url'];
}
// HTML format value
if( $field['return_format'] == 'element' ) {
return $this->generate_element( $link );
}
// return link
return $link;
}
/*
* generate_element()
*
* Generate HTML element from link values
*
* @type filter
* @since N/A
* @date N/A
*
* @param $link (array)
*
* @return $link (string)
*/
function generate_element( $link ) {
if ( empty($link) || empty($link['url']) ) return null;
return '<a href="'.$link['url'].'"'.( !empty($link['target']) ? ' target="'.$link['target'].'"' : '' ).'>'.( isset($link['title']) ? $link['title'] : '' ).'</a>';
}
/*
* get_html_attribute()
*
* Run regex query on html string to return attribute value
*
* @type filter
* @since N/A
* @date N/A
*
* @param $attr (string)
* @param $html (string)
*
* @return $value (string)
*/
function get_html_attribute( $attr, $html ) {
$regex = '/'.$attr.'\s*=\s*[\'"]([^\'"]+)[\'"]/i';
preg_match($regex, $html, $result);
return isset($result[1]) ? $result[1] : '';
}
/*
* validate_value()
*
* description
*
* @type function
* @date 11/02/2014
* @since 5.0.0
*
* @param $post_id (int)
* @return $post_id (int)
*/
function validate_value( $valid, $value, $field, $input ){
// bail early if not required
if( !$field['required'] ) return $valid;
// URL is required
if( empty($value) || empty($value['url']) ) {
return false;
}
// return
return $valid;
}
/*
* update_value()
*
* This filter is appied to the $value before it is updated in the db
*
* @type filter
* @since 3.6
* @date 23/01/13
*
* @param $value - the value which will be saved in the database
* @param $post_id - the $post_id of which the value will be saved
* @param $field - the field array holding all the field options
*
* @return $value - the modified value
*/
function update_value( $value, $post_id, $field ) {
// URL is required
if( empty($value) || empty($value['url']) ) {
return false;
}
// Store as an html element for easier database querying
return $this->generate_element( $value );
}
}
// initialize
acf_register_field_type( 'acf_field_link' );
endif; // class_exists check
?>
You can find a tool that will search and replace in the DB and not alter serialized data. ACF is not the only plugin that stores serialized data in the database. https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
@hube2 Thank you. However, this question is in regards to ACF not any other plugins.
I feel the DB search and replace tool you suggested is overkill for such a simple task and wouldn’t solve my scenario as I need serialized data to be altered, not ignored.
I mention other plugins because it is the commonly accepted way to store arrays in the database.
The tool that I linked to does not ignore serialized date. It extracts the serialized data, makes the string replacement and then correctly serializes and stores the changed data. It may seem like overkill, but it is the only safe way to find an replace strings in serialized data. Doing a simple search and replace will never work.
I use this tool on every site launch, whether I think I need it or not. As I said many plugins use the same method of storing URLs and I never know if I’m using a plugin that does so or not. I have seen a lot of sites crash because of altering serialize data.
To be honest, I’ve never used a link field myself, but other developers I work with have. But I have used other plugins that store absolute URLs to media files and other types of URLs for the site. Other plugins I’ve worked with also store absolute server paths in serialized data in the DB, a stupid practice, but it is done. Not altering these correctly can crash a site. Using the tool is simple a way to make sure data does not disappear.
I’m not the developer, and I can’t say that the solution you propose could not be done. But it would not be backwards compatible. ACF stores several fields as serialized arrays, not just the link field. I doubt that this will change.
The topic ‘Change how the link field is stored in the database’ is closed to new replies.
Welcome to the Advanced Custom Fields community forum.
Browse through ideas, snippets of code, questions and answers between fellow ACF users
Helping others is a great way to earn karma, gain badges and help ACF development!
We use cookies to offer you a better browsing experience, analyze site traffic and personalize content. Read about how we use cookies and how you can control them in our Privacy Policy. If you continue to use this site, you consent to our use of cookies.