Entity.php
<?php
namespace Doc;
class Entity {
protected $node;
protected $table;
protected $lookup;
protected $initData;
protected $loop;
protected $autocreate;
protected $doc;
protected $options;
protected $new = false;
protected $propAttribute = 'prop';
protected $printIfNull = false;
protected $hideIfNull = false;
protected $isEditMode = false;
public function __construct($node,$schemaDoc,$options=[]){
$this->node = $node;
$this->table = $node->table??null;
$this->options = $options;
$new = $this->boolAttribute('new');
$lookupStr = $options['lookup'] ?? $node->lookup ?? '';
$this->new = $lookupStr=='new'||$lookupStr=='new;';
// $node->lookup = $lookupStr;
// var_dump($lookupStr);
// exit;
$this->lookup = $this->decodeDataString($lookupStr);
$this->lookupStr = $lookupStr;
$initDataStr = $options['initData'] ?? $node->getAttribute('data-init') ?? '';
$this->initData = $this->decodeDataString($initDataStr) ?? [];
$this->initDataStr = $initDataStr;
// $node->setAttribute('data-init',$initDataStr);// = $lookupStr;
$this->loop = $this->boolAttribute('loop');
$this->autocreate = $this->boolAttribute('autocreate');
// if ($this->lookupStr=='name:take_action_header;'){
// print_r($this->lookup);
// var_dump($this->autocreate);
// echo "\n";
// exit('good');
// }
$this->printIfNull = $this->boolAttribute('printifnull');
$this->hideIfNull = $this->boolAttribute('hideifnull');
// var_dump($this->printIfNull);
// exit;
$this->doc = $schemaDoc->doc;//$node->ownerDocument;
$this->schemaDoc = $schemaDoc;
$this->prepareNodeWithOptions($node,$options);
}
protected function prepareNodeWithOptions($node,$opt){
$overrides = ['lookup'=>'lookup','initData'=>'data-init','formName'=>'form'];
foreach ($overrides as $key=>$attrName){
// $attrName
if ($opt[$key]??null!=null)$node->setAttribute($attrName, $opt[$key]);
}
}
public function fillSelf($removeAPIData){
if ($this->table==null)exit;
$binds = [];
$whereStr = $this->encodeWhereData($this->lookup,$binds);
$rows = \RDB::find($this->table,$whereStr,$binds);
if ($this->new){
return;
} else if (!$this->loop&&count($rows)>1){
throw new \Exception(" add 'loop' attribute, since there are multiple rows for table '{$this->table}' or refine the 'lookup' attribute");
} else if (!$this->autocreate&&$this->printIfNull&&count($rows)==0){
return;
} else if (count($rows)==0&&$this->hideIfNull){
$this->node->parentNode->removeChild($this->node);
return;
} else if (!$this->autocreate&&count($rows)==0){
throw new \Exception("No entry in '{$this->table}' for lookup '{$this->lookupStr}'");
}
$this->tryAutocreate($rows);
$nodes = [];
$parent = $this->node->parentNode;
$nextSib = $this->node->nextSibling;
$parent->removeChild($this->node);
$entries[] = ['node'=>$this->node,'bean'=>reset($rows)];
for ($i=1;$i<count($rows);$i++){
$entries[] = ['node'=>$this->node->cloneNode(true),'bean'=>next($rows)];
}
foreach ($entries as $entry){
$node = $entry['node'];
$bean = $entry['bean'];
$this->doc->importNode($node,TRUE);
$parent->insertBefore($node,$nextSib);
$q = new \DOMXpath($this->doc);
$props = $q->evaluate('.//*[@'.$this->propAttribute.']',$node);
$this->setProps($node,$props,$bean);
if ($removeAPIData){
$this->cleanEntityAttributes($node);
$this->cleanPropsAttributes($props);
}
}
}
protected function cleanEntityAttributes($node){
$list = ['table', 'printifnull', 'view', 'form', 'hideifnull', 'loop', 'autocreate', 'lookup','data-init','options'];
foreach ($list as $attr){
$node->removeAttribute($attr);
}
}
protected function cleanPropsAttributes($props){
$list = ['prop','format'];
foreach ($props as $p){
foreach ($list as $attr){
$p->removeAttribute($attr);
}
}
}
/**
* Update the node properties with same-named bean properties
*
* @param mixed $node - The root entity node being edited
* @param mixed $propNodes - all the property nodes found inside $node
* @param mixed $bean - The Redbean BEAN object that should modify $node
* @return void
*/
protected function setProps($node,$propNodes,$bean){
$edit = TRUE;
foreach ($propNodes as $prop){
$name = $prop->getAttribute($this->propAttribute);
$value = $bean->$name;
$tag = strtolower($prop->tagName);
if ($value==null)continue;
if ($tag=='a'){
$value = $this->fullUrl($value);
// echo $value;
// exit;
$prop->href = $value;
} else {
$this->setNodeValue($prop,$value);
}
}
if ($edit&&!$this->new){
$node->lookup = "id:".$bean->id;
}
}
public function enableEditMode(){
$xPath = new \DOMXpath($this->doc);
$entities = $xPath->query('//*[@table]');
$inputs = [];
foreach ($entities as $node) {
$class = $node->getAttribute('class');
$list = explode(' ',$class);
$list = array_combine($list,$list);
$list['SchemaEditable'] = 'SchemaEditable';
$node->setAttribute('class',implode(' ',$list));
$viewName = $node->getAttribute('view');
if ($viewName!=null){
$formName = $viewName .= 'Form';
if (!$node->hasAttribute('view'))$node->setAttribute('view',$viewName);
if (!$node->hasAttribute('form'))$node->setAttribute('form',$formName);
// var_dump($formName);
// exit;
}
}
}
/**
* Automatically create a bean from the values set on the html node, if there are no rows
*
*
* @param $rows - the rows retrieved from redbean. If a new bean is created, it will be put into this rows array
* @return void
*/
protected function tryAutocreate(array &$rows){
if (count($rows)==0&&$this->autocreate){
$bean = \RDB::dispense($this->table);
$rows[] = $bean;
$q = new \DOMXpath($this->doc);
$props = $q->evaluate('.//*[@'.$this->propAttribute.']',$this->node);
foreach ($props as $node){
$tag = strtolower($node->tagName);
$propName = $node->getAttribute($this->propAttribute);
if ($tag=='img'){
$bean->$propName = $node->getAttribute('src');
} else {
$bean->$propName = $node->innerHTML;
}
}
// print_r($this->initData);
// print_r($this->lookup);
$initData = array_merge($this->lookup,$this->initData);
// exit;
foreach ($initData as $key=>$value){
$bean->$key=$value;
}
\RDB::store($bean);
}
}
public function nodeToHtml($node){
$outerHTML = $node->ownerDocument->saveXML($node);
return $outerHTML;
}
protected function boolAttribute($attr){
// return true;
// var_dump($this->node->hasAttribute($attr));
$result = $this->node->hasAttribute($attr)&&strtolower($this->node->$attr!='false') ? true : false;
// if ($attr=='hideifnull')echo "woah::";
// var_dump($result);
return $result;
}
protected function getNodeValue($node){
return $node->innerHTML;
}
protected function setNodeValue($node,$newValue){
$tagName = strtolower($node->tagName);
$format = $node->getAttribute('format');
if ($tagName=='input'){
$this->setFormInputValue($node,$newValue);
return;
} else if ($tagName=='img'){
$newValue = $this->fullUrl($newValue);
$node->src = $newValue;
return;
} else if ($tagName=='object'){
$newValue = $this->fullUrl($newValue);
$node->data = $newValue;
} else if ($format=='md'){
$converter = new \League\CommonMark\CommonMarkConverter([
'html_input' => 'strip',
'allow_unsafe_links' => false,
]);
$newValue = $converter->convertToHtml($newValue);
}
$node->innerHTML = $newValue;
}
protected function encodeWhereData($lookupData, &$bindsArray=[],&$insertData=[]){
$qs = [];
foreach ($lookupData as $key=>$value){
if ($value===null)continue;
$qs[] = $key.' = :'.$key;
$bindsArray[':'.$key] = $value;
$insertData[$key] = $value;
}
$where = implode(' AND ',$qs);
return $where;
}
protected function decodeDataString($dataStr){
return \Doc::decodeDataString($dataStr);
}
protected function nodeFromHTML($html,$hideXmlErrors=true){
libxml_use_internal_errors($hideXmlErrors);
$domDoc = new \DomDocument();
$domDoc->registerNodeClass('DOMElement', 'JSLikeHTMLElement');
$domDoc->loadXML($html);
libxml_use_internal_errors(false);
$node = $this->doc->childNodes[1]->childNodes[0]->childNodes[0];
$node->parentNode->removeChild($node);
return $node;
}
protected function cloneNode($node,$doc){
$nd=$doc->createElement($node->nodeName);
foreach($node->attributes as $value)
$nd->setAttribute($value->nodeName,$value->value);
if(!$node->childNodes)
return $nd;
foreach($node->childNodes as $child) {
if($child->nodeName=="#text")
$nd->appendChild($doc->createTextNode($child->nodeValue));
else
$nd->appendChild($this->cloneNode($child,$doc));
}
return $nd;
}
/**
* Return a domain-relative url ('/theurl/') with https?://domain.com prepended
*/
protected function fullUrl($value){
return \Doc::fullUrl($value);
}
}
// public function fillSelf($removeAPIData){
// if ($this->table==null)exit;
// $binds = [];
// $whereStr = $this->encodeWhereData($this->lookup,$binds);
// $rows = \RDB::find($this->table,$whereStr,$binds);
// if (!$this->loop&&count($rows)>1){
// throw new \Exception(" add 'loop' attribute, since there are multiple rows for table '{$this->table}' or refine the 'lookup' attribute");
// } else if (!$this->autocreate&&count($rows)==0){
// throw new \Exception("No entry in '{$this->table}' for lookup '{$this->node->lookup}'");
// }
// // V2
// // clone the node
// // get child entitities
// // replace child entities with a unique placeholder tag or text
// // foreach result,
// // fill all the properties for the cloned node
// // delete the original node
// // append the newly modified node... in the same spot
// if (count($rows)==0&&$this->autocreate){
// $bean = \RDB::dispense($this->table);
// foreach ($this->lookup as $key=>$value){
// $bean->$key = $value;
// }
// foreach ($propNodes as $node){
// $key = $node->prop;
// if (isset($this->lookup[$key]))continue;
// $value = $this->getNodeValue($node);
// $bean->$key = $value;
// }
// \RDB::store($bean);
// $rows[] = $bean;
// }
// $nodesToAdd = [];
// $nodesToMod = [['bean'=>array_shift($rows),'node'=>$this->node]];
// foreach ($rows as $bean){
// // $clone = $this->node->cloneNode(TRUE);
// $clone = $this->cloneNode($this->node,$this->doc);
// $nodesToMod[] = ['bean'=>$bean,'node'=>$clone];
// // $nodesToAdd[] = $clone;
// }
// // print_r($nodesToMod);
// // exit;
// $c = 0;
// foreach ($nodesToMod as $data){
// $bean = $data['bean'] ?? null;
// $node = $data['node'] ?? null;
// $propXpath = new \DOMXPath($node->ownerDocument);
// $propNodes = $propXpath->query('//*[@prop]',$node);
// print_r($propNodes);
// foreach ($propNodes as $propNode){
// $name = $propNode->prop;
// $this->setNodeValue($propNode,$bean->$name);
// }
// print_r($bean->getProperties());
// if ($c>0){
// $nodesToAdd[] = $node;
// }
// $c++;
// }
// $lastNode = $this->node->nextSibling;
// foreach ($nodesToAdd as $node){
// $this->node->parentNode->insertBefore($node,$lastNode);
// $lastNode = $node->nextSibling;
// }
// }