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;
        // }
    // }