In my two previous articles(1,2), we saw how to use PHP's IntlDateFormatter to display dates in a format that is typical for different locales. In this one, we'll circle back to how to use the techniques from those articles to display MODX hidden fields in the Manager's Create/Edit Resource panel. The code will be same code we used in my first article in this series with a couple of added functions to get the timestamps quickly, and format the dates with the IntDateFormatter. The only changes from our original code are the addition of the two functions, and the code of the 'date' section of the switch statement.
This article shows that it's relatively easy to display any of the hidden fields on the Create/Edit Resource panel using a plugin. Unfortunately, letting the user update those fields and storing the results in the database is much more complicated. Doing so is beyond the scope of this article.
Getting a Timestamp the Fast Way
As I mentioned in a previous article, MODX object date fields are stored in the database as Unix timestamps. When you ask for them with $object->get('dateFieldName'), MODX returns a human-readable date. Since it's recommended to use a timestamp, we originally converted that human-readable date with PHP' strtotime() function. This is quite inefficient and somewhat offensive to most PHP coders. Here is a function that will return the raw timestamp from any MODX date field. It's mostly relevant for Resources, but could be used with Settings and TVs that have a date field for their value:
function getObjectTimestamp($modx, $object, $objectId, $field) {
/* $object should be a fully qualified object for either
MODX 2 or MODX 3+, e.g., `modResource` for MODX2
or `MODX\Revolution\modResource` for MODX 3+ */
$query = $modx->newQuery($object, $objectId);
$query->select($field);
$timestamp = $modx->getValue($query->prepare());
return $timestamp;
}
We send the modX object as the first argument to the function above, so we don't need to use global $modx in the function.
DateFormatter Function
This function returns a date formatted by the InlDateFormatter. It will handle using the pattern method we saw in my previous article or the built-in date and time formats we saw, in the article before that.
function dateFormatter(
$timestamp,
$locale,
$dateFormat,
$timeFormat,
$timezone = NULL,
$calendar = IntlDateFormatter::GREGORIAN,
$pattern = NULL,
$method = 'constants') {
/* $method should be either 'constants' or 'pattern' */
/* $pattern (if any) should be a "skeleton" like 'yMMMMdHms' */
switch ($method) {
case 'constants':
$dateFormatter = new IntlDateFormatter(
$locale,
$dateFormat,
$timeFormat,
$timezone,
$calendar,
$pattern,
);
$output = $dateFormatter->format($timestamp);
break;
case 'pattern':
$patternGenerator = new IntlDatePatternGenerator($locale);
// Ask for the best localized pattern for the skeleton
$bestPattern = $patternGenerator->getBestPattern($pattern);
$dateFormatter = new IntlDateFormatter(
$locale,
IntlDateFormatter::NONE, // ignored
IntlDateFormatter::NONE, // ignored
$timezone,
$calendar,
$bestPattern // The generated pattern
);
$output = $dateFormatter->format($timestamp);
break;
default:
return 'Unknown Method';
}
return $output;
}
The Method
If you create a plugin attached to the OnDocFormRender event, put some HTML in a string, and send it to the modx->event->output() function, MODX will place the HTML at the bottom of the top section on the main tab of the Create/Edit Resource panel (just above the Content section). In the code below, you can see that we've done that at the end of the code, just above the return statement.
If you have code that also uses this technique (e.g., the plugins in the ClassExtender package), the code below will place the elements above or below the ones inserted by that extra, depending on the Priority of the two plugins, set on the System Events tab next to any attached events. Plugins with lower Priority numbers will execute first.
This code will display all the fields you might want to see using the IntlDateFormatter class. It gets the raw timestamp directly from the Resource table in the database. If there are some fields you don't want displayed, just comment out or delete the line in the $fields array near the top of the code. Be sure each line in the array ends in a comma. The Tpl used to display each field sets the input section for each field to readonly, because editing these fields directly is a recipe for disaster. That's why MODX doesn't show them. In the code, the attribute is readonly="readonly". If you use just readonly, as is often done, it will work, but the code will not be valid XHTML. To make the fields editable, you need to remove the entire attribute, though MODX may not save them correctly.
The Fields
Here's a quick rundown of the most useful hidden resource fields and what is stored in each one.
id— ID of the resourcecontext_key— Name of the resource's context (e.g., web)editedon— The date and time of the last update for the resourcecreatedon— The date and time the resource was createdcreatedby— ID of the user who created the resourceeditedby— ID of the last user to edit the resourcepublishedby— ID of the user who published the resourceThere are actually a number of other fields. It's easy to add them to the display, just add a new line for each one to the array near the top of the plugin code below.
The Code
Because the user fields hold user IDs rather than names, we have to get the information from the user with that ID. Our code will have an option to use the username field for this or the fullname field. We'll also make it possible to format the display of the date fields.
Here's the code of the plugin. I called it ExtraDocFields, but you can use any name you like (note that ClassExtender uses ExtraResourceFields, so don't use that). Just paste the code into a new plugin. On the System Events tab, put a check next to OnDocFormRender, and click on the "Save" button.
$output = '';
$fullName = true;
/* Make it run in either MODX 2 or MODX 3 */
$prefix = $modx->getVersionData()['version'] >= 3
? 'MODX\Revolution\\'
: '';
/* Note that the Published On (publishedon), Publish Date (pub_date),
and Unpublish Date (unpub_date) fields are omitted here because
they are shown in the Manager. You can use a similar technique
if you want to show them in the front end. */
$fields = array(
'Resource ID,id,integer',
'Context,context_key,default',
'Last Edited On,editedon,date',
'Created On,createdon,date',
'Created By,createdby,user',
'Edited By,editedby,user',
'Published By,publishedby,user',
);
/* Don't execute for new documents.
The fields would be bogus except for context_key */
if ($mode == modSystemEvent::MODE_NEW) {
return '';
}
foreach($fields as $field) {
$parts = explode(',', $field);
if (count($parts) != 3) {
$caption = 'Error: ';
$value = 'Malformed array member';
$output .= '<div class="x-form-item x-tab-item">
<label class="x-form-item-label" style="color:red">' .
$caption . ' <span>' . $value .
'</span></label></div>';
} else {
$caption = $parts[0];
$fieldName = $parts[1];
$type = $parts[2];
$value = $resource->get($fieldName);
$docId = $resource->get('id');
switch ($type) {
case 'date':
$timestamp = getObjectTimestamp(
$modx,
$prefix . 'modResource,
$docId,
$field
);
/* This is the only part that needs editing. You can leave the $pattern
as is even if you're not using it. If the $method is set to
'constants' it will be removed. */
$locale = 'en_us';
$dateFormat = IntlDateFormatter::MEDIUM;
$timeFormat = IntlDateformatter::MEDIUM;
$timezone = 'America/Chicago;;
$calendar = IntlDateFormatter::GREGORIAN;
$pattern = 'yMMMMdHms';
$method = 'constants';
$value = dateFormatter(
$timestamp,
$locale,
$dateFormat,
$timeFormat,
$timezone,
$calendar,
$pattern,
$method,
);
break;
case 'user':
if ($fullName) {
$profile = $modx->getObject($prefix . 'modUserProfile',
array('internalKey' => $value));
$value = $profile->get('fullname');
} else {
$userObject = $modx->getObject($prefix . 'modUser', $value);
$value = $userObject->get('username');
}
break;
case 'integer':
default:
/* Value used as is */
break;
}
$fieldTpl = <<< TPL
<div class="x-form-item x-tab-item">
<label for="modx-resource-{$fieldName}" class="x-form-item-label">{$caption}</label>
<div class="x-form-element" id = "x-form-el-modx-resource-'{$fieldName}" style="padding-left:0;">
<input type = "text" size="30" readonly="readonly" autocomplete = on"
msgtarget="under" id="modx-resource-{$fieldName}
name="{$fieldName} "
class="x-form-text x-form-field" title=""
style = "width: 973px;" value="{$value}">
<div class="x-form-clear-left"></div>
</div >
</div>
TPL;
$output .= $fieldTpl;
}
}
// $modx->log(modX::LOG_LEVEL_ERROR, 'Output: ' . $output);
$modx->event->output($output);
return '';
The code starts by initializing the $output variable to an empty string, and setting the $dateFormat and $fullName options. The $dateFormat string will be sent to our dateFormatter() function for formatting. If the $fullName variable is set to false, the username will be used.
Next, we create an array with the fields to show. Each field has three elements, separated by commas. The first one is the caption to be shown for the field, the second one is the field name in the database, the third is the type of field it is. Valid types are date, user, integer, and default. Default fields will be shown exactly as they are retrieved from the DB.
Once we have the $fields array, we loop through it, adding the appropriate stuff to the $output variable as we go. The explode() function returns a PHP array containing the three members of the array element. If any element doesn't have exactly three parts, we add an error message to the output. If the array element is valid, we use the three member of the $parts array to set the $caption, $fieldName, and $type variables. Then, we get the value of the field from the $resource object which is set automatically by MODX when the plugin is called. The value goes in the $value variable, but it may be modified in the switch statement below.
Each section of the switch statement handles a different type of field.
For date fields, we get the raw timestamp using our getObjectTimeStamp() function.
The user fields (createdby, editedby, and publishedby) are a little trickier because the username is in the modUser object and the full name is in the modUserProfile object. Depending on the value of the $fullName variable, we get the appropriate object. Notice that the user object is retrieved directly with the user ID. The user profile object, though, is retrieved by looking for that value in the internalKey field. If you get a profile using the user's ID, it will usually work, but not always. In the profile table, the user's ID is held in the internalKey field and the id field is arbitrary. Since users and their profiles are almost always created at the same time, the two are often the same, but over time, they can diverge.
Integer and default fields are not modified. They're used just as they come from the DB. The 'integer' and 'default' cases in the switch statement are not actually necessary, but they make the code easier to understand.
Finally, we use our Tpl to create the output for the field being processed and add it to the $output variable. Notice that this happens inside the loop, but after the switch statement when the $value is in the form we want.
The Tpl is in $modx->getChunk() and having MODX parse the chunk and replace all the placeholders each time through the loop.
Finally, we call $modx->event->output() with our string of HTML ($output) as the argument.
Other Formats
You may not like the formatted output produced by the date() function. In that case, you can simply change the $dateFormat variable to modify the outout. For example, you might want to include the time the Resource was created, edited, or published. To do that, you can add g:i a to the end of the formatting string to see something like this:
Dec 23, 2025 4:07:10 pm
For more information on the formatting codes, see this table in the PHP manual.
Styling the Display
The current code mimics the format of the standard fields of the Create/Edit Resource panel, but it pushes the content section down (depending on how many of the fields you show). On some displays, it could be off the screen. It's certainly possible to reformat the display. The values could be shown on the same line as the captions. In fact, the captions and values could all be shown on the same line. The easiest and safest way to reformat the extra field displays would be to add or modify inline styles for the elements to the Tpl code without changing the existing MODX classes. You could, for example, add something like style="display:inline; margin:5px 15px;" in the Tpl code to make everything appear on the same line. Any inline styles you add this way will override the MODX CSS.
Moving the Hidden Field Display
In the Tpl chunk used to display the fields, I've used the MODX standard IDs for each field. I haven't tested it, but I think it might be possible to move the fields around with Form Customization. If that works, the extra fields could go on the Settings tab, or on another existing tab. In theory, you could create another tab and put them there. Doing this is beyond the scope of this article, but feel free to attempt it.
Coming Up
If you're going to the trouble of using the IntDateFormatter, you've probably noticed that the code above isn't quite what you want. You don't really want to set the locale manually in the code. You want to set it to the appropriate value for your visitor. You might want to do the same thing with the timezone. We'll look both topics in my next article.
About Bob Ray
Bob Ray is the author of the MODX: The Official Guide and dozens of MODX Extras including QuickEmail, NewsPublisher, SiteCheck, GoRevo, Personalize, EZfaq, MyComponent and many more. His website is Bob’s Guides. It not only includes a plethora of MODX tutorials but there are some really great bread recipes there, as well.
Learn more about Bob Ray.