Using MODX Outside of MODX

Take advantage of MODX features and functionality in your custom scripts and applications.

By Bob Ray
January 11, 2022
Using MODX Outside of MODX

Did you ever wish you could use some of MODX’s capabilities in a script running outside of MODX? Sometimes, it’s nice to be able to use $modx->getChunk() to get some content or $modx->runSnippet() to use a utility Snippet. It’s also handy to be able to get and manipulate various MODX objects like Resources, Users, Templates, Chunks, and TVs in a script without having to create a Snippet inside MODX to do it.

I end up writing a fair number of Extras and utility scripts that can run both inside and outside MODX, so I’ve had to develop a reliable method for instantiating the $modx object in a way that keeps things from falling apart. When I need MODX outside of MODX, I use some variation of the following code. You can put the code directly in your PHP file or use include to pull it in.

The Code

<?php

/* Assume these are false until we know otherwise */
$cliMode = false;
$outsideModx = false;

/* Set a user (set to an actual username) */
$myUserName = 'JoeBlow';

/* Instantiate MODX if necessary
   (all the code below will only
   execute if we're outside of MODX) */

/* See if we're in MODX or not */
$inModx = isset($modx) && $modx instanceof modX;

if (!$inModx) {
    /* Correct this path to point to the config.core.php file
       in the MODX root directory! */
    require "c:/xampp/htdocs/addons/config.core.php";

    $outsideModx = true;

    /* Set path to config.inc.php
       (minus the filename) -- Note that this
       has to be corrected if the config directory
       is not directly under the core directory */

    $configPath = MODX_CORE_PATH . MODX_CONFIG_KEY. '/';

    /* get the MODX class file */
    require_once MODX_CORE_PATH . 'model/modx/modx.class.php';

    /* Set log options */

    /*  log_level options are:
        LOG_LEVEL_FATAL, LOG_LEVEL_ERROR,
        LOG_LEVEL_WARN, LOG_LEVEL_INFO,
        and LOG_LEVEL_DEBUG */

    $logLevel = xPDO::LOG_LEVEL_INFO;
    /* (If you don't send a log level,
       MODX will set it to LOG_LEVEL_ERROR) */

    /* Check for CLI mode and set log target */
    if (php_sapi_name() == 'cli') {
        $cliMode = true;
        $logTarget = 'ECHO';
    } else {
        $logTarget = 'HTML';
    }

    $options = array(
        'log_level' => $logLevel,
        'log_target' => $logTarget,
    );

    /* instantiate the $modx object */
    $modx = new modX($configPath, $options);

    if (!is_object($modx) || !$modx instanceof modX) {
        die('Could not create MODX class');
    }
    /* initialize MODX and set current context */
    $modx->initialize('web');
    // or $modx->initialize('mgr');

    /* load the error handler */
    $modx->getService('error', 'error.modError', '', '');

    /* Make sure there's a request */
    $modx->getRequest();

    /* Set $modx->resource, request, and $modx->user */

    /* Set $modx->resource to home page */
    $homeId = $modx->getOption('site_start');
    $homeResource = $modx->getObject('modResource', $homeId);

    if ($homeResource instanceof modResource) {
        $modx->resource = $homeResource;
    } else {
        die('No Resource');
    }

    /* set $modx->user */
    $myUser = $modx->getObject('modUser',
        array('username' => $myUserName));
    if ($myUser instanceof modUser) {
        $modx->user = $myUser;
    } else {
        die('No User');
    }
}

Making Sure It Works

Put the code above in a file (let’s call it instantiate-modx.php). Run the code from the command line (or inside your code editor). If it executes without throwing and error, it works.

Creating the $modx Object

To create MODX we use this line:

$modx = new modX($configPath, $options);

We send the modX constructor two arguments. The first is the path to the MODX config file (config.inc.php), minus the file name. The second is the $options array we set up earlier containing the log settings. Both arguments are optional, but it’s more efficient to send them, since otherwise MODX may make several attempts to find things, and it will often set the wrong log settings for our purposes.

If we don’t send the $options argument, we’d also have to call $modx->setLogTarget() and $modx->setLogLevel() later in our code to make sure they were set correctly, which means they’d both be called twice, once in the constructor and again in our code.

Do I Really Need All That?

The comments in the code above are fairly self-explanatory, but they don’t explain why were doing what we’re doing and when it’s necessary.

Not all of the code is necessary in every case, although using all of it will very seldom cause any trouble. For a simple utility Snippet, you often need only the first part of the code (up to and including the $modx->initialize() line).

Initializing MODX

The first part of the code, checks to see whether the $modx object already exists. There’s no point in creating it if it’s there already. It’s theoretically possible that a variable called $modx already exists, so our test checks to make sure that if $modx exists, it’s an instance of the modX Class.

The choice of whether to initialize MODX in the 'web' or 'mgr' context depends on what your code does. Most of the time, it doesn’t matter, but if your code calls processors that check permissions or if your code checks permissions directly, MODX will use the Policy tied to the user’s user group for the specified context when doing the checks.

For security purposes, using 'web' is generally safer since the user is less likely to have permissions in that context that will let them cause trouble.

$outsideModx and $cliMode

The script above sets the $outsideModx variable just in case we need to know later on in the script whether we’re running inside MODX or not. We also set the $cliMode variable. This indicates whether the script is running from the command line (true), or in a browser (false). We also set the MODX log target so $modx->log() messages will be formatted properly.

When running in a Snippet or Plugin inside MODX, $cliMode will always be false, which is why the check is inside the section that only runs outside of MODX (and why we set it to true above that section). When running outside of MODX, $cliMode will be true only if the script is run from the command line or from inside a programming editor, and false if it is running in a browser. This is important to know because in CLI mode, HTML code will be displayed rather than executed and will clutter up the output.

If you’re running in CLI mode and need to print an error message, for example, you might use code like this:

    if ($cliMode) {
        echo "\nFile not found";
    } else {
        echo '<br/>File not found';
    }

If you have lots of output, you’d probably create a function to print error messages, possibly using nl2br() to convert newlines to br tags when running inside MODX.

$error, $request, $resource, and $user

It’s difficult to know for sure when these will be necessary. You might be doing things in your script that will access various MODX objects directly or indirectly. If you call a processor, for example, with $modx->runProcessor(), the odds are good that the processor will fire an event or two. In many event invocations, $modx->resource or $modx->user get sent along for the ride. If they don’t exist, the program may crash.

You might think these settings are unnecessary if you’re not calling any processors or invoking any events, but remember that the code is running as if it’s in MODX. That means any of the site’s Plugins might come into play. One of them could be accessing $modx->user or $modx->resource. It could also be looking at the current request, or throwing an error using $modx->error->addError().

Every one of the four settings has crashed code of mine when a processor or Plugin tried to access them, and they weren’t set.

The code above sets $modx->resource to the page specified by the site_start System Setting. If your code will be made public, this is a safe bet because every site has one. For private code, you might want to create a dummy resource so Plugins won’t have a chance to mess with the home page (e.g., the DefaultResourceGroup Plugin).

If your code doesn’t use Resources, you can usually remove that section.

If you use Resources, but don’t save them, you can probably remove that section, but be prepared to put it back if the code crashes.

You’ll have to modify the code above to change “JoeBlow” to the username of an actual user. For security purposes, it’s best not to set this to the admin Super User unless you need that user’s permissions to execute your code. If you will be distributing your code, you’ll have to make a decision about whether to use the admin (ID = 1), create a user, pick a random user, or advise people to set the username themselves.

You might be tempted to use the (anonymous) user, but that’s not a real user and the getObject() call will fail. You can set the user to the (anonymous) user by replacing all the user code above with the simple call below, but be aware that the user’s permissions may be too limited for your use case and code that relates to the user object may fail, since the (anonymous) user is not in the database (and yes, the parentheses are part of that user’s username).

$modx->getUser();

Although the code above will set the user to the (anonmyous) user, the code below will fail because the user is not in the database:

$user = $modx->getObject('modUser', array('username' =>
    '(anonymous)'));

    /* At this point $user === null */

    /* The next line will cause a fatal PHP error */
    $username = $user->get('username');

Note that if you’re running in CLI mode, permissions may not an issue. In CLI mode, your code may bypass most of MODX’s permission checks. If you’re running in MODX, or in a browser, though, and you call any MODX processors, they will balk if the user doesn’t have the necessary permissions. Plugins may also do permission checks.

As with $modx->resource, if your code doesn’t involve users at all you can remove that section and if you don’t save any users, you can probably remove it.

Summing Up

In practice, I usually use all the code, the time penalty is minimal, and you never know when a new Plugin will require some part of it.

Including the full code above only adds a few milliseconds, and could prevent a confusing outcome, or a fatal PHP error.

What About MODX3?

Using MODX outside of MODX in MODX3 is a little different, though much of the code is the same. We’ll look at how to do it in an upcoming article.


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.