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.
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...