We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
    • 42215
    • 17 Posts
    I need to display a list of names+values of all the TVs of a given Resource. Is there a way to do this using some reliable snippet? Or is the only way - to develop my own one?

    I looked up some xPDO info. As far as I understand, achieving my goal requires using its query techniques. Please, correct me if I am wrong, also any advice (particularly on performance optimization) would be appreciated.

    Thanks!

    This question has been answered by multiple community members. See the first response.

    • discuss.answer
      • 9207 ☆ A M B ☆
      • 2,475 Posts
      xPDO is an abstraction layer that has benefits including a query optimization and sanitization, but you never have to use it. Ultimately, you want to get values from the modx_site_tmplvar_contentvalues table, so you could run a query like this:

      SELECT * FROM modx_site_tmplvar_contentvalues WHERE contentid=<id-of-current-page>


      But a more elegant solution would use xPDO in a Snippet -- one of the tricks is to know which PHP class corresponds to which table -- I often keep open the core/model/schema/modx.mysql.schema.xml file as a reference for this because it defines the classes and the tables. So your snippet might look like this:


      if ($TVs = $modx->getCollection('modTemplateVarResource', array('contentid' => $modx->resource->get('id'))) {
          $out = 'Values:<br/>'; // init
           foreach ($TVs as $t) {
              $out .= $t->get('value') .'<br/>';
          }
          return $out;
      }
      else {
          return 'Sorry, no TVs were found.';
      }


      The trick here is that the way raw values are stored in the database is not always how they're displayed on the front-end, so the above query might return weird results if your TVs are using input/output filters (e.g. some values are stored as JSON data).

      So the next step up would be to use the getTVValue() method to render the value correctly (e.g. if the value were stored as JSON, this would trigger the expected output filters) -- so instead of just reading the value, we could pass the id of the template variable to the getTVValue method:

      if ($TVs = $modx->getCollection('modTemplateVarResource', array('contentid' => $modx->resource->get('id'))) {
          $out = 'Values:<br/>'; // init
           foreach ($TVs as $t) {
              $out .= $modx->getTVValue($t->get('tmplvarid')) .'<br/>';
          }
          return $out;
      }
      else {
          return 'Sorry, no TVs were found.';
      }


      The final step would be to read the names of each TV along with the value -- this gets tricky because the data is normalized across multiple tables. One way to do that would be to join the two tables via the getCollectionGraph method:

      if ($TVs = $modx->getCollectionGraph('modTemplateVarResource', '{"TemplateVar":{}}'array('contentid' => $modx->resource->get('id'))) {
          $out = ''; // init
           foreach ($TVs as $t) {
              $out .= $t->TemplateVar->get('name') . ' :' .$modx->getTVValue($t->get('tmplvarid')) .'<br/>';
          }
          return $out;
      }
      else {
          return 'Sorry, no TVs were found.';
      }



      All the code there is untested, but hopefully that gives you an idea of how the DB is structured and how xPDO accesses it.
      • discuss.answer
        • 4172
        • 5,888 Posts
        what about using $resource->getTemplateVars()?
        Does not something like this work?

        $docid = $modx->getOption('docid',$scriptProperties,$modx->resource->get('id'));
        $outputSeparator = $modx->getOption('outputSeparator',$scriptProperties,'');
        $tpl = $modx->getOption('tpl',$scriptProperties,'');
        $output = array();
        
        if ($resource = $modx->getObject('modResource',$docid) && $tvs = $resource->getTemplateVars()){
            foreach ($tvs as $tv){
                if (empty($tpl)){
                    $output[] = $tv->toArray();
                }else{
                    $output[] = $modx->getChunk($tpl,$tv->toArray());
                }
                
            }
        }
        
        if (empty($tpl)){
            $output = '<pre>' . print_r($output,1) . '</pre>';
        }
        else{
            $output = implode($outputSeparator,$output);
        }
        
        return $output;
        
          -------------------------------

          you can buy me a beer, if you like MIGX

          http://webcmsolutions.de/migx.html

          Thanks!
          • 42215
          • 17 Posts
          Thanks everyone for the replies!

          Everett, in the end I decided to do this with xPDO+innerJoins. Plus, later it was decided the TVs should be filtered by a certain category-acestor of the categories they are in.

          The result is like this:

          $docid = $modx->getOption('docid',$scriptProperties,$modx->resource->get('id'));
          $outputSeparator = $modx->getOption('outputSeparator',$scriptProperties,'');
          $categories = $modx->getOption('categories', $scriptProperties, '');
          
          $tpl = $modx->getParser()->getElement('modChunk', $modx->getOption('tpl', $scriptProperties, 'catalogItemPropsBrief'));
          $tpl->setCacheable(false);
          $outerTpl = $modx->getParser()->getElement('modChunk', $modx->getOption('outerTpl', $scriptProperties, 'catalogItemPropsBriefOuter'));
          $outerTpl->setCacheable(false);
          $classnames = $modx->getOption('classnames', $scriptProperties, '');
          
          	$criteria = $modx->newQuery('modTemplateVar');
          
          	$criteria->innerJoin(
          		'modTemplateVarResource', 
          		'TVi', 
          		array('`modTemplateVar`.id = TVi.tmplvarid')
          	);
          	if($categories){
          		$criteria->innerJoin(
          			'modCategoryClosure',
          			'CATs',
          			array('CATs.descendant = modTemplateVar.category')
          		);
          	}
          	
          $criteria->select(array(
          	'modTemplateVar.id', 
          	'TVi.id', 
          	'modTemplateVar.caption', 
          	'TVi.value', 
          	'TVi.tmplvarid'
          ));	
          $criteria->where(
          	array(
          		'TVi.contentid'=>$docid,
          	)
          );
          if($categories){
          	$criteria->where(
          		array(
          			'CATs.ancestor:IN'=>array_map('trim',explode(',',$categories))
          		)
          	);
          }
          $criteria->sortby('rank', 'ASC');
          
          $tvs = $modx->getCollection('modTemplateVar',$criteria);
          	
          $output = '';
          foreach($tvs as $tv){
          	$processed = $tv->toArray();
          	$tpl->_processed = false;
          	$output .= $tpl->process($processed).$outputSeparator;
          }
          $outerTpl->_processed = false;
          $output = $outerTpl->process(array("inner"=>$output, "classnames" => $classnames));
          
          return $output;


          Quote from: Bruno17 at Feb 05, 2014, 01:57 PM
          what about using $resource->getTemplateVars()?
          Does not something like this work?
          It does. Actually, if I had found this API method a before I researched on xPDO, I would definetely choose it. It's much simpler, but slower (depending on the conditions and the number of the TVs - up to 10 times!), consdering the category filtering requirement.