⚠️ Urgent! Active Attacks on MODX Revolution Sites Below Revolution 2.6.5
Subscribe: RSS
  • Hello,

    I didn't know that this will be difficult to accomplish. But that's it. I have this small plugin that is fired on the "onDocFormSave" event to increment a TV value each time a resource is saved, in order to make a version system :
    <?php
    $resource->setTVValue('version', $resource->getTVValue('version') + 1);
    


    If the resource page is not reloaded, the TV value is not incremented. I don't know why, but when we save the resource for the second time, it ALWAYS get the old tv value.

    I tried to empty the cache between each save but it does not work.
    I also tried to do this directly by using xPDO statements in the bdd :
    $result = $modx->query("SELECT value FROM modx_site_tmplvar_contentvalues WHERE tmplvarid=2 AND contentid=$id");
    $row = $result->fetch(PDO::FETCH_ASSOC);
    $version = $row['value'] + 1;
    
    $modx->query("UPDATE modx_site_tmplvar_contentvalues SET value=$version  WHERE tmplvarid=2 AND contentid=$id");
    


    But the result is the same.

    Does I have to use PDO statements to achieve this instead of using xPDO ?
    Is there another way ? A cache to clean ? A variable reference to remove ?

    Thanks for helping

    This question has been answered by Bruno17. See the first response.

    • One xPDO version would be:

      $tvr = $modx->getObject('modTemplateVarResource', array('contentid' => $docId, 'tmplvarid' => $tvId));
      
      if ($tvr) {
         $val = (int) $tvr->get('value');
         $tvr->set('value', $val + 1);
         $tvr->save(); 
      }
      


      I'm not sure it will give you a different result, but it's worth a try.

      If it doesn't work try adding this at the end:

      $cm = $modx->getCacheManager();
      $cm->refresh();
      


      If it still doesn't work, try adding this:

      $modx->reloadContext('web');
        Get my Book: MODX:The Official Guide
        MODX info for everyone: http://bobsguides.com/MODx.html
        My MODX Extras
        Bob's Guides is now hosted at A2 MODX Hosting
      • Thanks for your answer Bob.

        Unfortunately, It gives me the same result, I also tried adding :
        $modx->reloadContext('mgr');


        But nothing. Once the resource il loaded it always send the old values. I wonder if this is not the resource saving process (that fires before the plugin) which erases the tv values by replacing it with the one that is currently displayed in the resource form. So while this value is not updated (by the page reload), it could never works.

        With this idea in mind, I tried to put the plugin on the "OnBeforeDocFormSave" event, but here, TV value is not updated at all, never.

        I also tried to reload the resource page on save but it's not possible :
        $modx->sendRedirect(MODX_MANAGER_URL . '?a=resource/update&id=' . $id)


        This does not work on the onDocFormSave event.
        • After re-reading your post, I see the problem. When your plugin fires, it updates the database, but not the values in the Create/Edit Resource panel, so if you save it again without reloading it, it will always save the old values.

          I think the only way out would be to either use JavaScript to update the values in the form, or to have your plugin reload the form by creating (or grabbing) the URL for the page and use $modx->sendRedirect($url) to reload it. The first would be faster for the user, but the second would be easier to implement.

          Of course you could also train your users not to save a form twice without reloading it. wink

            Get my Book: MODX:The Official Guide
            MODX info for everyone: http://bobsguides.com/MODx.html
            My MODX Extras
            Bob's Guides is now hosted at A2 MODX Hosting
          • Unfortunately, $modx->sendRedirect($url) does not work on the "onDocFormSave" event. We already saw this behavior on another dicussion on the "onResourceDelete" event : https://forums.modx.com/thread/98798/disable-remove-trash-system-in-modx-revolution

            Anyway, I manage PHP in modx but not JS. Where can I put a custom JS script that can access the ressource fields ? Is that about ExtJS ? I've never understood ExtJS and how it works with modx.

            Concerning my users, it is not an option, I can only train Pokemons :/
            • I mentioned JS, but it's usually a nightmare in the Manager. I'm not sure how you would load it. It might work to use $modx->regClientStartupScript() in a plugin attached to OnDocFormPrerender.

              The main reason it's a nightmare is the extJS creates the Manager pages on the fly and the HTML you want to find and modify may not exist at the time you need to access it.

              On reflection, most of the JS ideas I had wouldn't actually work. Maybe someone else has an idea.
                Get my Book: MODX:The Official Guide
                MODX info for everyone: http://bobsguides.com/MODx.html
                My MODX Extras
                Bob's Guides is now hosted at A2 MODX Hosting
              • discuss.answer
                couldn't you save the values somewhere else, for example in a custom-table?
                You could read that value on every OnDocFormSave, incement it, save the incremented value back to your custom-table and to your TV at the same time.

                This way the value would really increment on each save.
                  -------------------------------

                  you can buy me a beer, if you like MIGX

                  http://webcmsolutions.de/migx.html

                  Thanks!
                • Yeah, I didn't even thinking about custom tables. It works very well.

                  Here is my complete procedure to achieve my version system :

                  Step 1 :
                  Create a new table in the MODX database called "modx_site_content_version" with three columns "id", "contentid" and "version" (set "id" column as INDEX):
                  CREATE TABLE `modx_site_content_version` (
                    `id` int(11) NOT NULL,
                    `contentid` int(11) NOT NULL,
                    `version` int(11) NOT NULL
                  ) DEFAULT CHARSET=utf8;
                  ALTER TABLE `modx_site_content_version` ADD PRIMARY KEY(`id`);
                  ALTER TABLE `modx_site_content_version` CHANGE `id` `id` INT(11) NOT NULL AUTO_INCREMENT;
                  ALTER TABLE `modx_site_content_version` ADD INDEX(`id`);
                  


                  Step 2 :
                  Create a new snippet called "createXpdoClasses" and fill it with this code (available at http://bobsguides.com/custom-db-tables.html, thanks to Bob):
                  <?php
                  /**
                  * @package = CreateXpdoClasses
                  *
                  * Create Xpdo Classes script
                  *
                  * This script creates xPDO-ready classes from existing custom
                  * database tables. Run it again whenever you change
                  * your schema.
                  *
                  * It assumes that your custom tables have been imported into
                  * the MODX DB and use a different table prefix than the MODX
                  * tables.
                   
                  * In theory, you can use a separate DB but this has not been
                  * tested and the process of using the classes would be more
                  * complicated. To do this, you would need to set the
                  * following variables after including the config file,
                  * but before creating the $modx object:
                  * 
                  * $database_server = 'yourDbServer';
                  * $database_type = 'mysql';
                  * $dbase = 'yourDbName';
                  * $database_user = 'user';
                  * $database_password = 'password';
                  * $table_prefix = 'yourPrefix_';
                  *
                  * In the snippets that use the classes, you'd need to
                  * instantiate a new $xpdo object, and use $xpdo-> rather
                  * than $modx-> to call the methods.
                  * $xpdo = new xPDO('mysql:host=localhost;dbname=yourDbName',
                  *   $database_user,$database_password,$table_prefix);
                  *
                  * Note: If you are running this outside of MODX and testing
                  * it in the Manager, it will log you out when it runs, even
                  * though the Manager screen will still be visible. Actions
                  * taken in the Manager (e.g., saving a snippet) will not be
                  * successful. After running the script, reload the Manager
                  * page in your browser and you will be prompted to
                  * log back in.
                  *
                  *
                  */
                  /* assume we're in a snippet */
                  $outsideModx = false;
                   
                   if (!defined('MODX_CORE_PATH')) {
                       $outsideModx = true;
                      /* put the path to your core in the next line to run
                       * outside of MODX */
                      define(MODX_CORE_PATH, 'c:/xampp/htdocs/test/core/');
                      include_once MODX_CORE_PATH .
                          '/model/modx/modx.class.php';
                      $modx= new modX();
                      $modx->initialize('mgr');
                  }
                   
                  /* set these if running outside of MODX */
                   
                  if ($outsideModx) {
                      $myPackage = 'mypackage';
                   
                      /* table prefix; must match the prefix of the tables
                       * to process. Using the same prefix as  the MODX
                       * tables is not recommended */
                      $myPrefix = 'bobs_';
                   
                      // $myTables = 'bobs_quotation';
                  }
                   
                   
                  /* These two switches let you write the schema and/or
                   * create the classes; useful for debugging and for leaving
                   * a manually edited schema file alone*/
                   
                  $createSchema = $modx->getOption('createSchema',
                      $scriptProperties,true);
                  $createClasses = $modx->getOption('createClasses',
                      $scriptProperties,true);
                   
                  /* Used to include the phpDoc templates below or
                   * other custom templates */
                  $includeCustomTemplates = empty($includeCustomTemplates)?
                  false : $includeCustomTemplates;
                   
                  /* $myPackage is the name of your package. Use this in
                   * your addPackage() call to load all classes created
                   * by this script.
                   * The class files will be created under the
                   * /core/components/$myPackage/model/
                   * directory.
                   * Example:
                   * $myPackage = 'quotes';
                   * $myPrefix = 'bobs_';
                   * $path = MODX_CORE_PATH . 'components/' . $myPackage .
                         '/';
                   * $result = $modx->addPackage($myPackage, $path .
                         'model/', $myPrefix);
                   * if (! $result) {
                   *   return('Failed to add package');
                   * }
                   */
                  $myPackage = empty($myPackage)? 'mypackage' : $myPackage;
                   
                  /* table prefix; must match the prefix of the
                   * tables you want to process */
                  $myPrefix = empty ($myPrefix)? '' : $myPrefix;
                   
                  /* Table names to process -- this is only necessary if
                   * your table prefix is the same as that of the MODX
                   * tables (not recommended). You can send a comma-separated
                   * list of full table names. In that case the class name
                   * will be the table name minus the prefix with any
                   * underscores removed and any letter after an
                   * underscore in upper case.
                   *
                   * You can also send an array of arrays of
                   * tableName => className,
                   * which allows you to specify the exact class name rather
                   * then letting MODX create it from the table name.
                   * Each inner array specifies a full table name and the
                   * class name to use.
                   *
                   * NOTE: This feature may not be implemented yet.
                   *
                   * Examples:
                   
                  $myTables = 'bobs_quotation';
                   
                  $myTables = array(
                      array(
                          'bobs_quotation'=>'bobQuotation'
                      )
                  );
                   
                  */
                  $myTables = empty($myTables)? '' : $myTables;
                   
                  /* You shouldn't need to modify the code beyond this point
                  ******************************************************* */
                   
                  $sources = array(
                      'config' => MODX_CORE_PATH . 'config/config.inc.php',
                      'package' => MODX_CORE_PATH . 'components/' .
                          $myPackage . '/',
                      'model' => MODX_CORE_PATH. 'components/' . $myPackage .
                          '/model/',
                      'schema' => MODX_CORE_PATH . 'components/' .
                          $myPackage . '/schema/',
                  );
                   
                  if (! file_exists($sources['package'])) {
                      mkdir($sources['package'],0777);
                  }
                   
                  if (! file_exists($sources['model'])) {
                      mkdir($sources['model'],0777);
                  }
                  if (! file_exists($sources['schema'])) {
                      mkdir($sources['schema'],0777);
                  }
                   
                  $modx->setLogLevel(modX::LOG_LEVEL_INFO);
                  $modx->setLogTarget(XPDO_CLI_MODE ? 'ECHO' : 'HTML');
                   
                  $manager = $modx->getManager();
                  $generator = $manager->getGenerator();
                   
                  if ($includeCustomTemplates) {
                      customTemplates($generator);
                  }
                   
                   
                  $file = $sources['schema'] . $myPackage .
                      '.mysql.schema.xml';
                   
                  // echo '<br/>File: ' . $file;
                   
                  /* writeSchema() arguments
                   * ---------------------------
                   * string $schemaFile -- Full path to the schema file
                   *     you want to write
                   * string $package -- (optional) Name of your component
                   * string $baseClass -- (optional) xPDO base class to use;
                   *     (send '' if using args below)
                   * string $tablePrefix -- Table prefix (of tables to
                   *     process)
                   * boolean $restrictPrefix) -- (optional) Process only
                   * tables with $tablePrefix
                   * mixed $tableList -- Array of arrays of
                   *    full-table-name=>className or
                   *    a string with a comma-separated
                   *    list of full table names;
                   *    if you send the string the table
                   *    name will be used as the class name.
                   *
                   * returns true on success, false on failure
                   */
                   
                  if ($createSchema) {
                      $xml= $generator->writeSchema($file,
                          $myPackage, '',$myPrefix,true,$myTables);
                   
                      if ($xml) {
                          $modx->log(modX::LOG_LEVEL_INFO,
                              'Schema file written to ' . $file);
                      } else {
                          $modx->log(modX::LOG_LEVEL_INFO,
                              'Error writing schema file');
                      }
                  }
                   
                  if ($createClasses) {
                      if ($generator->parseSchema($file, $sources['model'])){
                          $modx->log(modX::LOG_LEVEL_INFO,
                              'Schema file parsed -- Files written to ' .
                                  $sources['model']);
                      } else {
                          $modx->log(modX::LOG_LEVEL_INFO,
                              'Error parsing schema file');
                      }
                  }
                  $modx->log(modX::LOG_LEVEL_INFO, 'FINISHED');
                  exit();
                   
                  function customTemplates($generator) {
                  $generator->classTemplate= <<<EOD
                  <?php
                  /**
                   * [+phpdoc-package+]
                   * [+phpdoc-subpackage+]
                   */
                  class [[+class]] extends [[+extends]] {
                   
                  }
                  ?>
                  EOD;
                  $generator->platformTemplate= <<<EOD
                  <?php
                  /**
                   * [+phpdoc-package+]
                   * [+phpdoc-subpackage+]
                   */
                  require_once (dirname(dirname(__FILE__)) .
                      '/[+class-lowercase+].class.php');
                  class [+class+]_[+platform+] extends [+class+] {
                  }
                  ?>
                  EOD;
                  $generator->mapHeader = <<<EOD
                  <?php
                  /**
                   * [+phpdoc-package+]
                   * [+phpdoc-subpackage+]
                   */
                  EOD;
                  }
                  


                  Step 3 :
                  Create a new ressource called "createXpdoClasses" with no richtext editor and put this code in it :
                  [[!CreateXpdoClasses? &myPackage=`Version` &myPrefix=`modx_site_content_`]]

                  -> View the ressource one time in your browser.
                  -> Delete your "createXpdoClasses" resources and snippet. We do not need them anymore.

                  Step 4 :
                  Create a new plugin called "version" on the "onDocFormSave" event. This will increment the version.
                  <?php
                  $path = MODX_CORE_PATH . 'components/version/';
                  $result = $modx->addPackage('Version', $path . 'model/','modx_site_content_');
                  $currentResource= $modx->getObject('Version', array('contentid' => $id));
                      
                  if (empty($currentResource)) {
                      $newResource = $modx->newObject('Version', array(
                          'contentid' => $id,
                          'version' => 1
                      ));
                      $newResource->save();
                  }
                  else {
                      $currentResource->set('version', $currentResource->get('version') + 1);
                      $currentResource->save();
                  }
                  
                  unset($path, $result, $currentResource, $newResource, $id);
                  


                  Step 5 :
                  To get the version on templates and chunks, we will use this little snippet called "getVersion" :
                  <?php
                  $result = $modx->query("SELECT version FROM modx_site_content_version WHERE contentid=$id");
                  $row = $result->fetch(PDO::FETCH_ASSOC);
                  return $row['version'];
                  unset($result, $row);
                  


                  We use it this way in templates :

                  [[getVersion?id=`[[*id]]`]]
                  


                  Step 6 :
                  To generate the version on all existing resources, simply create and run a new snippet "versionAllResources" :
                  <?php
                  $resources = $modx->getCollection('modResource', array('class_key'=>'modDocument'));
                   
                  foreach ($resources as $resource) {
                      $path = MODX_CORE_PATH . 'components/version/';
                      $result = $modx->addPackage('Version', $path . 'model/','modx_site_content_');
                      $currentResource= $modx->getObject('Version', array('contentid' => $resource->get('id')));
                      
                      if (empty($currentResource)) {
                          $newResource = $modx->newObject('Version', array(
                              'contentid' => $resource->get('id'),
                              'version' => 1
                          ));
                          $newResource->save();
                      }
                      else {
                          $currentResource->set('version', $currentResource->get('version') + 1);
                          $currentResource->save();
                      }
                  }
                  
                  • Nice one! Definitely one for the CookBook! http://modxcookbook.com/basics/plugins/versioning-resources.html
                      Studying MODX in the desert - http://sottwell.com
                      Tips and Tricks from the MODX Forums and Slack Channels - http://modxcookbook.com
                      Join the Slack Community - http://modx.org
                    • Looks good. It's nice to know that old code of mine still works. wink

                      Thanks for posting such a clear description of the steps. smiley
                        Get my Book: MODX:The Official Guide
                        MODX info for everyone: http://bobsguides.com/MODx.html
                        My MODX Extras
                        Bob's Guides is now hosted at A2 MODX Hosting