We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
    • 22303 MODX Staff
    • 10,725 Posts
    Lot’s of bug fixes this week for XPDO, as well as new features, including MakeForm and MakeTable for XPDO, as well as a new idea for offering a complete OO API similar to the one that will be featured in 1.0 (using XPDO) as an optional add-on for MODx 0.9.5, if nothing else, just to offer PDO and users the change to become familiar with using it with their existing sites. wink

    Sometime Friday, after becoming quite frustrated with creating a custom document + TV publisher, I decided to complete the reverse-engineering portion of XPDO, so I could use it to build a publishing tool. What does this mean? It means you can reverse engineer any database structure (in MySQL only currently) into an XPDO object model schema, which is an XML file that describes your object-relational mapping. Once this is generated, you can then simply forward engineer all of your classes for an instant OO API. I am only now beginning to fathom the possibilities for use in the current and future MODx, but needless to say, you could theoretically communicate with any persistent data you have access to from your web server, and build a single, consistent API for integrating those systems.

    So with this new tool working, I proceeded to reverse-engineer and forward-engineer the current MODx database (from trunk) in a total of under 2 seconds, and then authored a snippet in a matter of minutes to create a form that when submitted will create and publish a new document with various TV values saved. Here’s what it looks like...

    <?php
    if (!function_exists('cleanAlias')) {
        function cleanAlias($alias) {
            if(strtoupper($modx->config['etomite_charset'])=='UTF-8') $alias = utf8_decode($alias);
            $alias = strtr($alias, array(chr(196) => 'Ae', chr(214) => 'Oe', chr(220) => 'Ue', chr(228) => 'ae', chr(246) => 'oe', chr(252) => 'ue', chr(223) => 'ss'));
        
            $alias = strip_tags($alias); 
            //$alias = strtolower($alias); 
            $alias = preg_replace('/&.+?;/', '', $alias); // kill entities 
            $alias = preg_replace('/[^\.%A-Za-z0-9 _-]/', '', $alias); 
            $alias = preg_replace('/\s+/', '-', $alias); 
            $alias = preg_replace('|-+|', '-', $alias); 
            $alias = trim($alias); 
            return $alias;
        }
    }
    
    $userId= $modx->getLoginUserID() ? $modx->getLoginUserID() * -1 : 0;
    $parentId= isset ($parentId) ? $parentId : $modx->documentIdentifier;
    $templateId= isset ($templateId) ? $templateId : $modx->documentObject['template'];
    
    require_once ($modx->config['base_path'] . 'manager/includes/tmplvars.inc.php');
    require_once ($modx->config['base_path'] . 'manager/includes/tmplvars.commands.inc.php');
    
    include_once ($modx->config['base_path'] . 'xpdo/xpdo.class.php');
    $xpdo= new xPDO('mysql:host=localhost;dbname=mydb', 'mydbuser', 'mydbpassword', 'modx_');
    $xpdo->setPackage('modx09x');
    $xpdo->setLogLevel(XPDO_LOG_LEVEL_ERROR);
    $xpdo->setLogTarget('HTML');
    
    $docgrp= false;
    if ($_SESSION['webDocgroups']) {
        $docgrp= implode(",", $_SESSION['webDocgroups']);
    }
    
    $tvTable= $xpdo->getTableName('SiteTmplvars');
    $tvtTable= $xpdo->getTableName('SiteTmplvarTemplates');
    $tvaTable= $xpdo->getTableName('SiteTmplvarAccess');
    $sql= "SELECT tv.* FROM {$tvTable} tv ";
    $sql .= "INNER JOIN {$tvtTable} tvtpl ON tvtpl.templateid = :template_id AND tvtpl.tmplvarid = tv.id ";
    $sql .= "LEFT JOIN {$tvaTable} tva ON tva.tmplvarid=tv.id  ";
    $sql .= "WHERE (1='" . $_SESSION['mgrRole'] . "' OR ISNULL(tva.documentgroup)" . ((!$docgrp) ? "" : " OR tva.documentgroup IN ($docgrp)") . ") ORDER BY tv.rank;";
    
    $stmt= $xpdo->prepare($sql);
    $stmt->bindValue(':template_id', $templateId);
    $stmt->execute();
    
    $tvCollection= array ();
    while ($tv= $stmt->fetch(PDO_FETCH_ASSOC)) {
        $tvCollection['tv' . $tv['name']]= $tv;
        $xpdo->_log(XPDO_LOG_LEVEL_DEBUG, '$tv var: ' . print_r($tv, true));
    }
    
    if ($_POST) {
        $inserts= array ();
        $insertsByName= array ();
        $xpdo->_log(XPDO_LOG_LEVEL_DEBUG, '$_POST var: ' . print_r($_POST, true));
        foreach ($_POST as $key => $value) {
            if (substr($key, 0, 2) === 'tv' && array_key_exists($key, $tvCollection)) {
                $tvId= $tvCollection[$key]['id'];
                if (is_array($value)) {
                    $inserts[$tvId]= implode('||', $value);
                    $insertsByName[$key]= $inserts[$tvId];
                } else {
                    $inserts[$tvId]= $value;
                    $insertsByName[$key]= $inserts[$tvId];
                }
            }
        }
        $alias= cleanAlias($insertsByName['tvcompany-name']);
        $xpdo->_log(XPDO_LOG_LEVEL_DEBUG, '$inserts var: ' . var_export($inserts, true));
        $document= $xpdo->newObject('SiteContent');
        $document->set('pagetitle', $insertsByName['tvcompany-name']);
        $document->set('longtitle', $insertsByName['tvcompany-name']);
        $document->set('description', $insertsByName['tvcompany-name']);
        $document->set('introtext', $insertsByName['tvelevator-pitch']);
        $document->set('alias', $alias);
        $document->set('parent', $parentId);
        $document->set('createdon', time());
        $document->set('createdby', $userId);
        $document->set('published', 1);
        $document->set('pub_date', time());
        $document->set('menutitle', $insertsByName['tvcompany-name']);
        $document->set('template', $templateId);
        $document->set('content', null);
        if ($document->save()) {
            foreach ($inserts as $tvKey => $insert) {
                $tvContentValue= $xpdo->newObject('SiteTmplvarContentvalues');
                $tvContentValue->set('tmplvarid', $tvKey);
                $tvContentValue->set('contentid', $document->getPrimaryKey());
                $tvContentValue->set('value', $insert);
                if (!$tvContentValue->save()) {
                    $xpdo->_log(XPDO_LOG_LEVEL_ERROR, "Error saving content value for {$tvKey}: " . print_r($insert, true));
                }
                $tvContentValue= null;
            }
        } else {
            $xpdo->_log(XPDO_LOG_LEVEL_ERROR, "Error saving new document " . print_r($document->toArray(), true));
        }
    }
    
    $output= "\n\n\n<form action=\"[~[*id*]~]\" method=\"post\">\n";
    foreach ($tvCollection as $tvKey => $tv) {
        if ($tv['type'] == 'richtext' || $tv['type'] == 'htmlarea') {
            if (is_array($replace_richtexteditor)) {
                $replace_richtexteditor= array_merge($replace_richtexteditor, array (
                    "tv" . $tv['name']
                ));
            } else {
                $replace_richtexteditor= array (
                    "tv" . $tv['name']
                );
            }
        }
        $output .= "<label for=\"{$tvKey}\">{$tv['ca
      • 22303 MODX Staff
      • 10,725 Posts
      Here is a temporary location for XPDO API documentation as generated by PhpDocumentor. Lot’s of revisions and cleanup to do, but the tool is absolutely fabulous (man I miss that show!) and this should help you find you’re way around the API a little more easily:

      http://xpdo.opengeek.com/

      Cheers
        • 28042 ☆ A M B ☆
        • 24,524 Posts
        So can this be used already (experimentally, at any rate) as a database layer for AJAX request backend handlers?
          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
          • 22303 MODX Staff
          • 10,725 Posts
          Absolutely Susan (for MySQL), though it is definitely alpha software (meaning there are probably bugs still). I’m still finishing up writing all the unit tests for the more advanced operations (i.e. complex object relationships, handling cascading saves and deletes appropriately, etc.), but this is being used in a client project to support a custom application for tracking a virtual mountain of user profile and medical data, so it will stabilize very quickly as problems are found. Another reason (besides wanting to get 1.0 in the teams hands ASAP) why I’d like as many people as possible to give this a try and tell me what they think, help me find problems I overlooked while developing in a vacuum, etc.

          Also, you can use the PDO for PHP 4 classes directly as well, without the xPDO wrapper, in place of the DBAPI for traditional DB access as well, with the benefit of using the prepared statements and parameter bindings that can help virtually eliminate the possibility of SQL injection attacks, though I still need to review the PDO for PHP 4.x and 5.0.x implementations that I’m providing, to make sure those do as good a job as the native PDO classes for PHP 5.1+ when handling this.

          Next to be released is a tool for defining your classes and metadata maps from a simple web form, similar to the one for defining a table in phpMyAdmin. This form will then generate a vanilla XML schema that can be processed by a code generator to automatically create your class files and your metadata maps that define the mapping between object fields/properties and the relational database columns.
            • 28042 ☆ A M B ☆
            • 24,524 Posts
            Oooh, new toys! I’ll definitely put these through the wringer; if I can’t make them crash and burn nobody can! grin
              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
              • 32241
              • 1,495 Posts
              Can’t test this out right now, but I will definetely give this a try. Keep it up Jason.

              Thanks...
                Wendy Novianto
                [font=Verdana]PT DJAMOER Technology Media
                [font=Verdana]Xituz Media
                • 22303 MODX Staff
                • 10,725 Posts
                Lot’s of bug fixes this week for XPDO, as well as new features, including MakeForm and MakeTable for XPDO, as well as a new idea for offering a complete OO API similar to the one that will be featured in 1.0 (using XPDO) as an optional add-on for MODx 0.9.5, if nothing else, just to offer PDO and users the change to become familiar with using it with their existing sites. wink

                Sometime Friday, after becoming quite frustrated with creating a custom document + TV publisher, I decided to complete the reverse-engineering portion of XPDO, so I could use it to build a publishing tool. What does this mean? It means you can reverse engineer any database structure (in MySQL only currently) into an XPDO object model schema, which is an XML file that describes your object-relational mapping. Once this is generated, you can then simply forward engineer all of your classes for an instant OO API. I am only now beginning to fathom the possibilities for use in the current and future MODx, but needless to say, you could theoretically communicate with any persistent data you have access to from your web server, and build a single, consistent API for integrating those systems.

                So with this new tool working, I proceeded to reverse-engineer and forward-engineer the current MODx database (from trunk) in a total of under 2 seconds, and then authored a snippet in a matter of minutes to create a form that when submitted will create and publish a new document with various TV values saved. Here’s what it looks like...

                <?php
                if (!function_exists('cleanAlias')) {
                    function cleanAlias($alias) {
                        if(strtoupper($modx->config['etomite_charset'])=='UTF-8') $alias = utf8_decode($alias);
                        $alias = strtr($alias, array(chr(196) => 'Ae', chr(214) => 'Oe', chr(220) => 'Ue', chr(228) => 'ae', chr(246) => 'oe', chr(252) => 'ue', chr(223) => 'ss'));
                    
                        $alias = strip_tags($alias); 
                        //$alias = strtolower($alias); 
                        $alias = preg_replace('/&.+?;/', '', $alias); // kill entities 
                        $alias = preg_replace('/[^\.%A-Za-z0-9 _-]/', '', $alias); 
                        $alias = preg_replace('/\s+/', '-', $alias); 
                        $alias = preg_replace('|-+|', '-', $alias); 
                        $alias = trim($alias); 
                        return $alias;
                    }
                }
                
                $userId= $modx->getLoginUserID() ? $modx->getLoginUserID() * -1 : 0;
                $parentId= isset ($parentId) ? $parentId : $modx->documentIdentifier;
                $templateId= isset ($templateId) ? $templateId : $modx->documentObject['template'];
                
                require_once ($modx->config['base_path'] . 'manager/includes/tmplvars.inc.php');
                require_once ($modx->config['base_path'] . 'manager/includes/tmplvars.commands.inc.php');
                
                include_once ($modx->config['base_path'] . 'xpdo/xpdo.class.php');
                $xpdo= new xPDO('mysql:host=localhost;dbname=mydb', 'mydbuser', 'mydbpassword', 'modx_');
                $xpdo->setPackage('modx09x');
                $xpdo->setLogLevel(XPDO_LOG_LEVEL_ERROR);
                $xpdo->setLogTarget('HTML');
                
                $docgrp= false;
                if ($_SESSION['webDocgroups']) {
                    $docgrp= implode(",", $_SESSION['webDocgroups']);
                }
                
                $tvTable= $xpdo->getTableName('SiteTmplvars');
                $tvtTable= $xpdo->getTableName('SiteTmplvarTemplates');
                $tvaTable= $xpdo->getTableName('SiteTmplvarAccess');
                $sql= "SELECT tv.* FROM {$tvTable} tv ";
                $sql .= "INNER JOIN {$tvtTable} tvtpl ON tvtpl.templateid = :template_id AND tvtpl.tmplvarid = tv.id ";
                $sql .= "LEFT JOIN {$tvaTable} tva ON tva.tmplvarid=tv.id  ";
                $sql .= "WHERE (1='" . $_SESSION['mgrRole'] . "' OR ISNULL(tva.documentgroup)" . ((!$docgrp) ? "" : " OR tva.documentgroup IN ($docgrp)") . ") ORDER BY tv.rank;";
                
                $stmt= $xpdo->prepare($sql);
                $stmt->bindValue(':template_id', $templateId);
                $stmt->execute();
                
                $tvCollection= array ();
                while ($tv= $stmt->fetch(PDO_FETCH_ASSOC)) {
                    $tvCollection['tv' . $tv['name']]= $tv;
                    $xpdo->_log(XPDO_LOG_LEVEL_DEBUG, '$tv var: ' . print_r($tv, true));
                }
                
                if ($_POST) {
                    $inserts= array ();
                    $insertsByName= array ();
                    $xpdo->_log(XPDO_LOG_LEVEL_DEBUG, '$_POST var: ' . print_r($_POST, true));
                    foreach ($_POST as $key => $value) {
                        if (substr($key, 0, 2) === 'tv' && array_key_exists($key, $tvCollection)) {
                            $tvId= $tvCollection[$key]['id'];
                            if (is_array($value)) {
                                $inserts[$tvId]= implode('||', $value);
                                $insertsByName[$key]= $inserts[$tvId];
                            } else {
                                $inserts[$tvId]= $value;
                                $insertsByName[$key]= $inserts[$tvId];
                            }
                        }
                    }
                    $alias= cleanAlias($insertsByName['tvcompany-name']);
                    $xpdo->_log(XPDO_LOG_LEVEL_DEBUG, '$inserts var: ' . var_export($inserts, true));
                    $document= $xpdo->newObject('SiteContent');
                    $document->set('pagetitle', $insertsByName['tvcompany-name']);
                    $document->set('longtitle', $insertsByName['tvcompany-name']);
                    $document->set('description', $insertsByName['tvcompany-name']);
                    $document->set('introtext', $insertsByName['tvelevator-pitch']);
                    $document->set('alias', $alias);
                    $document->set('parent', $parentId);
                    $document->set('createdon', time());
                    $document->set('createdby', $userId);
                    $document->set('published', 1);
                    $document->set('pub_date', time());
                    $document->set('menutitle', $insertsByName['tvcompany-name']);
                    $document->set('template', $templateId);
                    $document->set('content', null);
                    if ($document->save()) {
                        foreach ($inserts as $tvKey => $insert) {
                            $tvContentValue= $xpdo->newObject('SiteTmplvarContentvalues');
                            $tvContentValue->set('tmplvarid', $tvKey);
                            $tvContentValue->set('contentid', $document->getPrimaryKey());
                            $tvContentValue->set('value', $insert);
                            if (!$tvContentValue->save()) {
                                $xpdo->_log(XPDO_LOG_LEVEL_ERROR, "Error saving content value for {$tvKey}: " . print_r($insert, true));
                            }
                            $tvContentValue= null;
                        }
                    } else {
                        $xpdo->_log(XPDO_LOG_LEVEL_ERROR, "Error saving new document " . print_r($document->toArray(), true));
                    }
                }
                
                $output= "\n\n\n<form action=\"[~[*id*]~]\" method=\"post\">\n";
                foreach ($tvCollection as $tvKey => $tv) {
                    if ($tv['type'] == 'richtext' || $tv['type'] == 'htmlarea') {
                        if (is_array($replace_richtexteditor)) {
                            $replace_richtexteditor= array_merge($replace_richtexteditor, array (
                                "tv" . $tv['name']
                            ));
                        } else {
                            $replace_richtexteditor= array (
                                "tv" . $tv['name']
                            );
                        }
                    }
                    $output .= "<label for=\"{$tvKey}\">{$tv['caption']}</label>\n";
                    $output .= renderFormElement($tv['type'], $tv['name'], $tv['default_text'], $tv['elements'], ($tvPBV ? $tvPBV : $tv['value']), ' style="width:300px;"');
                    $output .= "<br />";
                    $output .= "\n\n";
                }
                $output .= "<input type=\"submit\" name=\"xpublish\" value=\"Publish\" />\n";
                $output .= "<input type=\"reset\" name=\"xreset\" value=\"Reset Form\" />\n";
                $output .= "</form>\n\n\n";
                
                return $output;
                ?>


                If you are feeling adventurous and want to try this on your own version of MODx, get the latest XPDO trunk and create a PHP script like this you can run from the command line... you’ll need to modify the first include to the proper path from your PHP script to the xpdo/xpdo/xpdo.class.php file; I run this from a file called /xpdo/xpdo/_model/generator.modx095.php:

                <?php
                $mtime= microtime();
                $mtime= explode(" ", $mtime);
                $mtime= $mtime[1] + $mtime[0];
                $tstart= $mtime;
                include_once (strtr(realpath(dirname(__FILE__)) . '/../xpdo.class.php', '\\', '/'));
                
                $xpdo= new xPDO('mysql:host=localhost;dbname=modx', 'username', 'password', 'modx_');
                $xpdo->setPackage('modx095');
                //$xpdo->setDebug(true);
                
                $manager= & $xpdo->getManager();
                $generator= & $manager->getGenerator();
                $generator->writeSchema(XPDO_CORE_PATH . '_model/modx095.mysql.schema.xml', 'modx095', 'xPDOObject', 'modx_');
                $generator->parseSchema(XPDO_CORE_PATH . '_model/modx095.mysql.schema.xml');
                
                $mtime= microtime();
                $mtime= explode(" ", $mtime);
                $mtime= $mtime[1] + $mtime[0];
                $tend= $mtime;
                $totalTime= ($tend - $tstart);
                $totalTime= sprintf("%2.4f s", $totalTime);
                
                echo "\nExecution time: {$totalTime}\n";
                exit ();
                ?>


                Sorry for the long code examples, but just wanted to share my progress. Let me know if you have thoughts or input on this stuff. More soon...