Enforcing Style: Retroactive and Ongoing

Hello,

I could likely figure this out, but I don’t have the time to stumble through it at the moment. I’m hoping people have done some similar things and can give me a head start, or this may inspire someone else to try it for a similar need.

Administration has decided on a change to our styles, specifically how we append class years:

Given this change, I want to try and achieve two things:

  1. Have a run-once module (or method) that will go through database content (stories, profiles, etc) and retroactively update as many instances as possible.

  2. Have a module(s) that will check content changes on save and enforcing this style rule. (i.e. people can do the old way out of habit, but the end result will be the new way)

#1 would be great, but may be too risky and isn’t strictly necessary. #2 is the key item I’d like to establish that ensures consistency moving forward (and with some retroactive corrections on edited content).

Thus:

  • Has anyone done either of these things and have code snippets or advice to share?
  • Otherwise, any ideas on how best to do either or both?

Thanks,
Nick

I haven’t done either of those things specifically. But I have added code for this type of clean-up in the onOutput handler of a module. So the data may never actually be correct, but visitors will never know because it is corrected before they see it.

That may meet your needs just fine.

Here are a couple of examples:

public function onOutput($buffer) { 
    global $_LW;
    // Remove hyphen from all instances of 'e-mail" (case-insensitive)
    if ( preg_match('#(e)-(mail)#i', $buffer) ) {
        $buffer = preg_replace('#(e)-(mail)#i', '$1$2', $buffer);
    }
    
    // Convert phone numbers from (###) ###-#### format to ###-###-####
    if ( preg_match('#\(([0-9]{3})\)\s?([0-9]{3}-[0-9]{4})#', $buffer) ) {
        $buffer = preg_replace('#\(([0-9]{3})\)\s?([0-9]{3}-[0-9]{4})#', '$1-$2', $buffer);
    }
    return $buffer;
}

As a +1 to what Jon mentions, we mostly see this kind of thing being done onOutput rather than on saving or search-and-replacing.

One important caveat: the preg_replace() action is a “heavy” one in terms of LW page processing, so like in Jon’s example, it’s essential that custom code be wrapped in a tightly-scoped strpos or preg_match scan so that the code only runs when it will find something to run on. This is a mistake I’ve made in the past and you’d be surprised how much badly-scoped preg_replace functions can slog down a server. Learn from past me’s mistakes! :slight_smile:

Hello,

@jwilcox Thanks for the examples; exactly what I was looking for (+/- the handler, but…)

@karl I recalled you saying that previously about preg functions, and that’s why I was thinking of this approach.

  • Put the “heavy” processing on editor steps, where it will have little to no additional impact on users’ experiences because it is correct to begin with.

  • Have the style be correct in the content that editors see and work with, as a subtle nudge/reminder of what our styles are. (i.e. bad style habits on web may translate to print, email, etc.)

  • This class year change specifically would be very common and numerous (i.e. 200+ instances in magazine class notes). I suspect an onOutput solution (without a retroactive step) could be excessively “heavy” given the sheer volume of instances.

If adjusting on save is a technical possibility, I’d still be interested in looking into it. I’d also be curious to learn what complications there are or could be in such an approach (i.e. lack of a handler, hard to check custom fields, etc.).

Thanks,
Nick

Sure thing – probably a good starting point is to experiment with using (onBeforeValidate)[onBeforeValidate - LiveWhale Support] to check the $_POST data for anything you want to change. If I’m not mistaken, the global variable $_LW->save_data is what gets used for saving, so any replacements or edits you make to that array inside of onBeforeValidate should carry through to the saved item. Hope this helps!

that is great to know!!

I’ve done some preliminary work on this, and it looks fairly promising. I’ve hit two issues so far.


1. Karl mentioned that saving values to $_LW->_POST should be carried over to the actual save step somewhere after OnBeforeValidate. This doesn’t seem to be the case.

I’ve been using $_LW->REGISTERED_MESSAGES['failure'] as a kind of output for testing, and through that I was able was able to isolate fields to change, implement my REGEX check, and save back to $_LW->_POST. Had some trouble with different field types (text, textarea), but it appears to work for either now.

The following is some utlity output in failure message that shows when attempting to save:

Original POST Data:
"title" => "Test Years: Nick Mischler’14"
"description" => "John Castor’87"
"contact_info" => "Mokona Aigablai’01"
"custom_testing_custom_field" => "“This is a bit redundant in a profiles context, but can it be generalized?” - Mick Nischler’41"
"custom_331" => "Jane Smith’96"
"custom_332" => "Alyssa Luna’24 is a Geology Major working with thesis advisor Jim Rougvie. She is studying Dissolved Organic Matter (DOM) Properties During Wet and Dry Years in Spanish Fork Canyon, Central Utah."
"custom_333" => "Kelsey Engelke’25 is a Geology Major working with thesis advisor Jay Zambito. She is examining the Chronostratigraphy of Late Devonian Sandstones from Southwest Illinois and East-central Missouri."

Edited POST Data:
"title" => "Test Years: Nick Mischler ’14"
"description" => "John Castor ’87"
"contact_info" => "Mokona Aigablai ’01"
"custom_testing_custom_field" => "“This is a bit redundant in a profiles context, but can it be generalized?” - Mick Nischler ’41"
"custom_331" => "Jane Smith ’96"
"custom_332" => "Alyssa Luna ’24 is a Geology Major working with thesis advisor Jim Rougvie. She is studying Dissolved Organic Matter (DOM) Properties During Wet and Dry Years in Spanish Fork Canyon, Central Utah."
"custom_333" => "Kelsey Engelke ’25 is a Geology Major working with thesis advisor Jay Zambito. She is examining the Chronostratigraphy of Late Devonian Sandstones from Southwest Illinois and East-central Missouri."

If I disable all $_LW->REGISTERED_MESSAGES['failure'] statements and try to let LiveWhale save the profile, none of these changes are made or saved. This makes me believe that updating the values of $_LW->_POST in OnBeforeValidate isn’t enough to make these edits stick. Grr.


2. When the page reloads after triggering $_LW->REGISTERED_MESSAGES['failure'], some of the fields in the editor update with the changes made. There is a key exception: custom profiles fields.

This approach has its benefits: it puts the correct format into the fields to be saved normally (avoiding the above no-change), and it notifies (or annoys) the editor of the changes and improves awareness of style rules. Thinking this, I tried to trigger $_LW->REGISTERED_MESSAGES['failure'] for each field, similar to required field checks.

Modified to use class year format (First Last ’00): title
Modified to use class year format (First Last ’00): description
Modified to use class year format (First Last ’00): contact_info
Modified to use class year format (First Last ’00): custom_testing_custom_field
Modified to use class year format (First Last ’00): custom_331
Modified to use class year format (First Last ’00): custom_332
Modified to use class year format (First Last ’00): custom_333

When the page reloads, title, description, contact_info, and custom_testing_custom_field have all been updated with the correct style rule. However, the custom_xyz fields are unchanged. Thus, I get this set of failures indefinitely for every subsequent save.

Modified to use class year format (First Last ’00): custom_331
Modified to use class year format (First Last ’00): custom_332
Modified to use class year format (First Last ’00): custom_333

So it appears that trying to save updates for custom profiles fields to $_LW->_POST doesn’t persist when a page is reloaded for a failure. So close!


Thus, what might be wrong here? LiveWhale? Me?

I’m assuming LiveWhale isn’t handling this in the simple way we were hoping, but I could be missing something. Not sure what either would be; I doubt I should be calling $_LW->save_data “before validation” or anything like that.

Here’s my current custom module code for review.

public function onBeforeValidate($data_type, $id) {

	global $_LW;

	// if saving an testing profile from a backend editor
	if ($data_type=='profiles' && $_LW->page=='profiles_edit' && $_LW->_GET['tid']==72) { // testing profiles

		// REAL: Get default fields, append custom fields for this type/subtype.
		$fields = ['firstname', 'middlename', 'lastname', 'title', 'description', 'contact_info'];
		foreach ($_LW->_POST as $p_id => $p_value) { // universal approach for both profile and config custom fields (key only alternative?)
			if (str_starts_with($p_id, 'custom_') && ($p_id !== 'custom_fields')) {
				$fields[] = $p_id;
			}
		}

		// UTILITY: List all POST fields we're checking.
		$display = '';
		foreach ($fields as $field_name) {
			$display .= '  "'.$field_name.'" => "'.$_LW->_POST[$field_name].'"<br/>'; // isset or array_has_key
		}
		$_LW->REGISTERED_MESSAGES['failure'][]='<strong>Original POST Data</strong>:<br/>'.$display.'<strong>Individual Changes</strong>:';

		// REAL: Check each field, try to match REGEX, and if match then replace REGEX.
		foreach ($fields as $field_name) {
			// check isset or array_has_key to ensure key exists; likely isset to also check if empty
			$field_content = html_entity_decode($_LW->_POST[$field_name], ENT_QUOTES, 'UTF-8'); // decode so REGEX works
			$regex = '/(\w+ \w+)(?!\s{1}’\d{2})\s*[\'‘’´`]?\s*(\d{2})/u'; // enforce First Last ’00
			$substitution = '$1 ’$2';
			if ( preg_match($regex, $field_content) ) {
				$_LW->_POST[$field_name] = preg_replace($regex, $substitution, $field_content);
				$_LW->REGISTERED_MESSAGES['failure'][]='Field modified to use correct class year format (First Last ’00): '.$field_name;
			}
		}

		// UTILITY: List all POST fields we're checking, after edits made.
		$display = '';
		foreach ($fields as $field_name) {
			$display .= '  "'.$field_name.'" => "'.$_LW->_POST[$field_name].'"<br/>'; // isset or array_has_key
		}
		$_LW->REGISTERED_MESSAGES['failure'][]='<strong>Edited POST Data</strong>:<br/>'.$display;

	};

} // end onBeforeValidate

Any input, ideas, and direction welcome.

Thanks,
Nick


Summary of input from Alex during 2025 Conference: $_LW->save_data is a variable, not a function. Try to use that.

I think Alex hit the nail on the head here, and sorry for any confusion I added based on my earlier advice! When you’re rewriting save data in the module, rather than $_LW->_POST['foobar']=, try $_LW->save_data['foobar']= as a start. Hope this helps!

Yeah, using $_LW->save_data achieves the intended behavior: check, update, and save without impeding the editor.

An observation to note. I was hoping for the option to make the changes but also trigger a $_LW->REGISTERED_MESSAGES['failure'] to bring the editor back and inform them of the incorrect style. It appears editing $_LW->save_data doesn’t change what appears in the editor on a failure reload like $_LW->_POST did. If _POST to check, then profile custom fields won’t update, and if save_data to check, then nothing will update. Guess silently correcting things is the only practical approach.

I’ll work on this more when I can and see if I can do some work to generalize it: easily define and check any number of rules, make a base modules that others can put in and use, etc.

Thanks,
Nick