Could not load class modMediaSourceElement from mysql.modmediasourceelement

I’m trying to get an image sitemap setup and using SeoSuite as a basis. Normally I’d use SeoSuite but there is currently a bug in their snippet when dealing with MIGX.

I had to convert some of the class stuff to be in a snippet style. I am not sure why the query works with modTemplateVar but not with modMediaSourceElement

Snippet For Reference
<?php
$contextKey = $modx->getOption('context', $scriptProperties, 'web');
$outerTpl = $modx->getOption('outerTpl', $scriptProperties, 'images-sitemap-outerTpl');
$rowTpl = $modx->getOption('rowTpl', $scriptProperties, 'images-sitemap-rowTpl');
$itemTpl = $modx->getOption('itemTpl', $scriptProperties, 'images-sitemap-itemTpl');

$output = '';
$usedMediaSourceIds = [];

// Get all modResources and iterate through them
$resources = [];
foreach($modx->getIterator('modResource') as $modResource) {
    // if published and not deleted and in right context
    if ($modResource->get('published') == 1 && $modResource->get('deleted') == 0 && $modResource->get('context_key') == $contextKey) {
        // double check the template is correct
        if (!in_array($modResource->get('template'), [0,1])) {
            // then save to array
            $resources []= $modResource->get('id');
        }
    }
}

$query = $modx->newQuery('modTemplateVar');
$query->select('modTemplateVar.*', 'Value.*');
$query->leftJoin('modTemplateVarResource', 'Value', ['modTemplateVar.id = Value.tmplvarid']);
$query->where([
    'Value.contentid:IN' => $resources,
    'Value.value:!=' => '',
    'modTemplateVar.type:IN' => ['image', 'migx', 'imagecropper']
]);

$images = [];

if ($imageTVs = $modx->getIterator('modTemplateVar', $query)) {
    # This was modMediaSourceElement::class in SeoSuiteSitemap
    $query = $modx->newQuery('modMediaSourceElement');
    $query->where([
        'object_class' => 'modTemplateVar',
        'context_key:IN' => $contextKey
    ]);
    
    $getTVSources = $modx->getIterator('modMediaSourceElement', $query);
    
    $tvSources = [];
    
    if ($getTVSources) {
        foreach($getTVSources as $tvSource) {
            $tvSources[$tvSource->get('object')] = $tvSource->get('source');
        }
    }
    
    foreach($imageTVs as $tv) {
        $tv = $tv->toArray();
        $cid = $tv['contentid'];
        
        if ($tv['type'] === 'image') {
            $images[$cid][] = [
                'id' => $tv['id'],
                'value' => $tv['value'],
                'source' => $tvSources[$tv['tmplvarid']]
            ];
        }
        
        if (!in_array($tvSources[$tv['tmplvarid']], $usedMediaSourceIds)) {
            $usedMediaSourceIds[] = $tvsources[$tv['tmplvarid']];
        }
    }
}

if ($resources) {
    $mediasources = [];
    
    if (count($usedMediaSourceIds) > 0) {
        foreach($usedMediaSourceIds as $mediaSourceId) {
            $modx->loadClass('modMediaSource');
            
            if ($source = modMediaSource::getDefaultSource($modx, $mediaSourceId, false)) {
                $source->initialize();
                $url = ($source->get('class_key') === 'source.modFileMediaSource') ? rtrim(MODX_SITE_URL, '/').'/'.ltrim($source->getBaseUrl(), '/') : '';
                $mediasources[$mediaSourceId] = array_merge([ 'full_url' => $url ], $source->toArray());
            }
        }
    }
    
    foreach($resources as $resource) {
        $imagesOutput = '';
        if (isset($images[$resource])) {
            foreach($images[$resource] as $image) {
                if (array_key_exists($image['source'], $mediasources)) {
                    $image['value'] = rtrim($mediasources[$image['source']]['full_url'], '/').'/'.ltrim($image['value'], '/');
                    $imagesOutput .= $modx->getChunk($itemTpl, [
                        'url' => $image['value']
                    ]);
                }
            }
            
            $output .= $modx->getChunk($rowTpl, [
                'url' => $modx->makeUrl($resource, '', '', 'full'),
                'images' => $imagesOutput
            ]);
        }
    }
}

return $modx->getChunk($outerTpl, [
    'wrapper' => $output
]);

If I can provide more info to help, lemme know!

Is this MODX 3?
If so, try using the fully qualified class name (with the namespace) → MODX\Revolution\Sources\modMediaSourceElement

Yes it is MODX3, so I would do it like this?

<?php
$query = $modx->newQuery('MODX\Revolution\Sources\modMediaSourceElement');

Yes, or you write

use MODX\Revolution\Sources\modMediaSourceElement;

at the top (of the snippet) and then use

$query = $modx->newQuery(modMediaSourceElement::class);

Okay, that got rid of that error, I’m now having an issue where the resource is not updating after the snippet is updated. Everything is uncached, the snippet and the resource is set to un-cacheable.

Edit: If I update the content type the snippet runs again, weird

I went ahead and updated where I was using 'modClassName' to use modClassName::class and was able to narrow down my issue a little further. When I log the TV after calling $tv->toArray() I get an object like this

Array (
    [id] => 7
    [source] => 3
    [property_preprocess] => 
    [type] => image
    [name] => hero-img
    [caption] => Background Image (1920 x 1080px)
    [description] => jpg or webp only
    [editor_type] => 0
    [category] => 22
    [locked] => 
    [elements] => 
    [rank] => 0
    [display] => default
    [default_text] => 
    [properties] => Array
        (
        )

    [input_properties] => Array
        (
            [allowBlank] => true
        )

    [output_properties] => Array
        (
        )

    [static] => 
    [static_file] => 
    [content] => 
)

This is missing the properties that the SeoSuite snippet looks for ‘contentid’, and ‘value’. It is also missing the ‘tmplvarid’ key that should be there after using

$query->leftJoin(modTemplateVarResource::class, 'Value', ['modTemplateVar.id = Value.tmplvarid']);

I haven’t messed with this query stuff before, but considering I’m only making small alterations from the SeoSuite plugin, I would think this would be going smoother.

Is there a way to log the results of a query? When I try print_r($query, true) it says it can’t convert to a string.

You can output what SQL query is generated by xPDO, by using the function toSQL() like this:

$query = $modx->newQuery('modTemplateVar');
...
$query->prepare();
$sql = $query->toSQL();

It looks like the line

$query->select('modTemplateVar.*', 'Value.*');

in the code doesn’t work.

Try using an array instead

$query->select(['modTemplateVar.*', 'Value.*']);

or maybe use code like this:

$query->select($modx->getSelectColumns('modTemplateVar', 'modTemplateVar', ''));
$query->select($modx->getSelectColumns('modTemplateVarResource', 'Value', '', ['tmplvarid', 'contentid', 'value']));

Looks like I need my glasses updated, comparing it to the SeoSuite code, it looks like they have the $query->select as this:

# notice its all one string :roll_eyes:
$query->select('modTemplateVar.*, Value.*');

Updating that fixes the fields missing.

Now two things are happening, 1 the $getTVSources iterator is empty and when I try to view the page it runs out of memory. Since $getTVSources is empty, it means that the error log is filled with “PHP warning: undefined array key N”

Could an undefined array key lead to a memory leak?

What exactly do you mean by “empty” in relation to an iterator?

No.
When the page runs out of memory, it’s more likely an indication that a recursion happens. E.g. a snippets call itself again and again, or a page redirects to itself again.


I’m not sure if 'object_class' => 'modTemplateVar' is correct for MODX 3.
The column “object_class” of the database table modx_media_sources_elements seems to have the value “MODX\Revolution\modTemplateVar”.

What exactly do you mean by “empty” in relation to an iterator?

It looked like it was false in the if statement right below it since the $tvSources variable had undefined keys. I see now that the iterator can’t be “empty” in that context.

If I add a log here and run the snippet again:

if ($getTVSources) {
    foreach($getTVSources as $tvSource) {
        $modx->log(MODX::LOG_LEVEL_ERROR, $tvSource);
        $tvSources[$tvSource->get('object')] = $tvSource->get('source');
    }
}

There’s no new log messages.

I think I’m missing something in the query because it seems like the loop isn’t running. That would mean that the iterator isn’t finding anything to iterate through (Even after updating the object_class value), right?

Yes, it looks like this is the case.

Maybe output the generated SQL query (with ->toSQL();) and test it in phpMyAdmin (or similar tool). Also take a look at the database table modx_media_sources_elements to see what entries it contains.

I hadn’t considered that! SQL/Backend-y stuff is outside my usual wheel house, I appreciate the debugging training :slight_smile:

I logged it using $modx->log(MODX::LOG_LEVEL_ERROR, $query->toSQL()) and putting the $query->toSQL() inside of a print_r and both logged nothing (see image)

Mid reply edit:
bangs head in frustration at answer…

$query = $modx->newQuery(modMediaSourceElement::class);
$query->where([
    'object_class'   => 'MODX\Revolution\modTemplateVar',
    /* This used to be 'context_key:IN', but $contextKey is a string */
    'context_key' => $contextKey 
]);

Fixing that runs the log in the $getTVSources loop, but the $query->toSQL() still logs as empty like above. Also getting a memory exhaustion error still, but I don’t understand where a recursion could happen in the code.

Most recent version of snippet for sanity check
use MODX\Revolution\modResource;
use MODX\Revolution\modTemplateVar;
use MODX\Revolution\modTemplateVarResource;
use MODX\Revolution\Sources\modMediaSource;
use MODX\Revolution\Sources\modMediaSourceElement;

$contextKey = $modx->getOption('context', $scriptProperties, 'web');
$outerTpl = $modx->getOption('outerTpl', $scriptProperties, 'images-sitemap-outerTpl');
$rowTpl = $modx->getOption('rowTpl', $scriptProperties, 'images-sitemap-rowTpl');
$itemTpl = $modx->getOption('itemTpl', $scriptProperties, 'images-sitemap-itemTpl');

$output = '';
$usedMediaSourceIds = [];

$query = $modx->newQuery(modResource::class);

$query->where([
    [
        'modResource.context_key' => $contextKey,
        'modResource.published' => 1,
        'modResource.deleted' => 0
    ]
]);

$query->where(['modResource.template:NOT IN' => [0,1]]);

// Get all modResources and iterate through them
$resources = [];
foreach($modx->getIterator(modResource::class, $query) as $modResource) {
    $resources []= $modResource->get('id');
}


$query = $modx->newQuery(modTemplateVar::class);

$query->select('modTemplateVar.*, Value.*');
$query->leftJoin(modTemplateVarResource::class, 'Value', ['modTemplateVar.id = Value.tmplvarid']);
$query->where([
    'Value.contentid:IN' => $resources,
    'Value.value:!=' => '',
    'modTemplateVar.type:IN' => ['image']
]);

$images = [];

if ($imageTVs = $modx->getIterator(modTemplateVar::class, $query)) {
    $query = $modx->newQuery(modMediaSourceElement::class);
    $query->where([
        'object_class'   => 'MODX\Revolution\modTemplateVar',
        'context_key' => $contextKey
    ]);

    $getTVSources = $modx->getIterator(modMediaSourceElement::class, $query);
    $tvSources = [];
    
    
    if ($getTVSources) {
        foreach($getTVSources as $tvSource) {
            $tvSources[$tvSource->get('object')] = $tvSource->get('source');
        }
    }
    
    foreach($imageTVs as $tv) {
        $tv = $tv->toArray();
        $cid = $tv['contentid'];
        
        if ($tv['type'] === 'image') {
            $images[$cid][] = [
                'id' => $tv['id'],
                'value' => $tv['value'],
                'source' => $tvSources[$tv['tmplvarid']]
            ];
        }
        
        if (!in_array($tvSources[$tv['tmplvarid']], $usedMediaSourceIds)) {
            $usedMediaSourceIds[] = $tvSources[$tv['tmplvarid']];
        }
    }
}

if ($resources) {
    $mediasources = [];
    
    if (count($usedMediaSourceIds) > 0) {
        foreach($usedMediaSourceIds as $mediaSourceId) {
            $modx->loadClass(modMediaSource::class);
            
            if ($source = modMediaSource::getDefaultSource($modx, $mediaSourceId, false)) {
                $source->initialize();
                $url = ($source->get('class_key') === 'MODX\Revolution\Sources\modFileMediaSource') ? rtrim(MODX_SITE_URL, '/').'/'.ltrim($source->getBaseUrl(), '/') : '';
                $mediasources[$mediaSourceId] = array_merge([ 'full_url' => $url ], $source->toArray());
            }
        }
    }
    
    foreach($resources as $resource) {
        $imagesOutput = '';
        if (isset($images[$resource])) {
            foreach($images[$resource] as $image) {
                if (array_key_exists($image['source'], $mediasources)) {
                    $image['value'] = rtrim($mediasources[$image['source']]['full_url'], '/').'/'.ltrim($image['value'], '/');
                    $imagesOutput .= $modx->getChunk($itemTpl, [
                        'url' => $image['value']
                    ]);
                }
            }
            
            $output .= $modx->getChunk($rowTpl, [
                'url' => $modx->makeUrl($resource, '', '', 'full'),
                'images' => $imagesOutput
            ]);
        }
    }
}

return $modx->getChunk($outerTpl, [
    'wrapper' => $output
]);

Did you call $query->prepare(); before calling $query->toSQL();?


The line numbers (83, 87, 88) in the log for the “Undefined array key” messages imply that these errors happen further down in the code.
What lines of code are these in the file ../82.include.cache.php?

I did not call that, doing that and checking in phpMyAdmin, the query is fine, it was that :IN modifier for the context key that was messing it up.

Those errors have disappeared now that the query has been fixed. All that is left is to try and track down the memory exhaustion error

Fatal error: Allowed memory size of 134217728 bytes exhausted 
  (tried to allocate 655465224 bytes) in
  /home/schrappe/public_html/core/src/Revolution/modParser.php on line 253

Is it possible that there are just too many images to try and keep track of as it’s building the file? Or that there is

Another mid reply update

I decided I should test out what is being produced now that all the queries are fixed. Logging the output string it looks like my chunks were a little funky or expected parameters that didn’t exist while passing in data to params that weren’t used. No idea if that was the reason, but fixing those chunks did fix the memory issue and I am now generating the correct xml output.

Thank you for your help and the tips on testing the SQL statement, I really appreciate it!