14namespace Depage\XmlDb;
16use Depage\XmlDb\Exceptions\XmlDbException;
46 $this->pdo->setAttribute(\PDO::ATTR_ORACLE_NULLS, \PDO::NULL_NATURAL);
52 $this->dbNs =
new XmlNs(
'db',
'http://cms.depagecms.net/ns/database');
65 public function __get($property)
67 if (property_exists($this, $property)) {
68 return $this->$property;
81 if (!isset($this->docIds[$docIdOrName])) {
82 if ((
int) $docIdOrName > 0) {
83 $id = (int) $docIdOrName;
86 $query = $this->pdo->prepare(
87 "SELECT docs.name AS docname
88 FROM {$this->tableDocs} AS docs
89 WHERE docs.id = :doc_id"
95 $result = $query->fetchObject();
96 if ($result ===
false) {
101 $name = $result->docname;
103 $name = $docIdOrName;
106 if (!isset($docList[$name])) {
111 $id = $docList[$name]->getDocId();
114 $this->docIds[$id] = $id;
115 $this->docIds[$name] = $id;
118 return $this->docIds[$docIdOrName];
139 $where[] =
'name = :name';
140 $queryParam[
'name'] = $name;
143 $where[] =
'type = :type';
144 $queryParam[
'type'] = $type;
146 if (count($where) > 0) {
147 $namequery =
"WHERE " . implode(
" AND ", $where);
151 $query = $this->pdo->prepare(
155 docs.rootid AS rootid,
157 FROM {$this->tableDocs} AS docs
159 ORDER BY docs.name ASC"
162 $query->execute($queryParam);
164 while ($doc = $query->fetchObject()) {
165 $docs[$doc->name] =
new Document($this, $doc->id);
180 if ($docId = $this->
docExists($docIdOrName)) {
196 $query = $this->pdo->prepare(
197 "SELECT xml.id_doc AS id_doc
198 FROM {$this->tableXml} AS xml
199 WHERE xml.id = :nodeId"
205 $result = $query->fetchObject();
207 if ($result && $docId = $this->
docExists($result->id_doc)) {
221 if ($doc = $this->
getDoc($docIdOrName)) {
222 return $doc->getDocInfo();
235 public function getDocXml($docIdOrName, $addIdAttribute =
true)
239 if ($docId = $this->
docExists($docIdOrName)) {
241 $xml = $doc->getXml($addIdAttribute);
258 if ($docId = $this->
docExists($docIdOrName)) {
260 $xml = $doc->getSubdocByXpath($xpath, $addIdAttribute);
280 if (isset($ids[0])) {
281 $subdoc = $this->
getDocByNodeId($ids[0])->getSubdocByNodeId($ids[0], $addIdAttribute);
305 $xpathElements = $parser->parseXpathElements($xpath);
306 $levels = count($xpathElements) - 1;
308 foreach ($xpathElements as $level => $element) {
310 $tableSql[] =
"SELECT l$levels.id FROM";
311 if ($element->divider ==
'/') {
312 $condSql[] =
"l0.id_parent IS NULL";
315 $tableSql[] =
'INNER JOIN';
316 if ($element->divider ==
'/' && !$fallback) {
317 $parentLevel = $level - 1;
318 $condSql[] =
"l$level.id_parent = l$parentLevel.id";
324 $tableSql[] =
"{$this->tableXml} AS l$level";
325 $op = $parser->getConditionOperator($element->namespace, $element->name);
326 $condSql[] =
"l$level.name $op :name$level";
327 $params[
"name$level"] = $parser->translateName($element->namespace, $element->name);
329 $hasPosition = $parser->parsePosition($element->condition);
333 } elseif ($element->condition !=
'') {
334 if ($attributes = $parser->parseAttributes($element->condition)) {
337 foreach ($attributes as $i => $attribute) {
338 [$name, $operator, $value, $bool] = $attribute;
341 $attributeCond .= $parser->cleanOperator($bool);
344 if ($name ==
'db:id') {
345 $attributeOperator = $parser->cleanOperator($operator);
346 $attributeCond .=
" l$level.id {$parser->cleanOperator($operator)} :attr{$level}n{$i} ";
347 $params[
"attr{$level}n{$i}"] = $value;
348 } elseif ($operator ==
'=' || $operator ==
'') {
349 $attributeCond .=
" l$level.value REGEXP :attr{$level}n{$i} ";
350 $regExValue = (is_null($value)) ?
'.*' : $value;
351 $params[
"attr{$level}n{$i}"] =
"(^| )$name=\"$regExValue\"( |$)";
357 if (!empty($attributeCond)) {
358 $condSql[] = $attributeCond;
365 if ($level == 0 && !is_null($docId)) {
366 $condSql[] =
"l$level.id_doc = :docid$level";
367 $params[
"docid$level"] = $docId;
373 if ($xpathElements) {
375 $tableSql[0] =
"SELECT DISTINCT l$level.id_doc FROM";
378 if (!empty($condSql)) {
379 $where =
' WHERE (' . implode(
') AND (', $condSql) .
')';
382 $sql = implode(
' ', $tableSql) .
"$where ORDER BY l$level.id_parent, l$level.pos";
384 $query = $this->pdo->prepare($sql);
385 $query->execute($params);
388 foreach ($query->fetchAll() as $result) {
395 $ids[] = $doc->getDocId();
400 if (is_null($docId)) {
416 foreach ($docs as $doc) {
417 $domXpath = new \DomXpath($this->
getDoc($doc)->getXml());
418 $list = $domXpath->query($xpath);
419 foreach ($list as $item) {
420 $ids[] = $item->attributes->getNamedItem(
'id')->nodeValue;
439 $nodeIds = array_values(array_unique(array_map(
'intval', $nodeIds)));
442 if (empty($nodeIds)) {
446 $query = $this->pdo->prepare(
447 "SELECT xml.id, xml.id_doc, xml.type, xml.value
448 FROM {$this->tableXml} AS xml
449 WHERE xml.id IN (" . implode(
", ", $nodeIds) .
")",
453 foreach ($query->fetchAll(\PDO::FETCH_OBJ) as $row) {
454 $nodeData[(int) $row->id] = [
455 'doc_id' => (
int) $row->id_doc,
456 'attributes' => ($row->type ==
'ELEMENT_NODE')
478 $nodeIds = array_values(array_unique(array_map(
'intval', $nodeIds)));
481 if (empty($nodeIds)) {
486 $query = $this->pdo->prepare(
487 Helpers\TreeSql::descendants($this->tableXml,
"id IN (" . implode(
", ", $nodeIds) .
")", $pad) .
"
488 SELECT tree.root_id, xml.value
489 FROM tree INNER JOIN {$this->tableXml} AS xml ON xml.id = tree.id
490 WHERE xml.type = 'TEXT_NODE'
491 ORDER BY tree.root_id ASC, tree.sortkey ASC",
495 foreach ($query->fetchAll(\PDO::FETCH_OBJ) as $row) {
496 $rootId = (int) $row->root_id;
498 $texts[$rootId] = ($texts[$rootId] ??
'') . $row->value;
514 public function createDoc($doctype =
'Depage\XmlDb\XmlDoctypes\Base', $docName =
null)
516 if (is_null($docName)) {
518 $docName =
'_' . substr($doctype, strrpos($doctype,
"\\") + 1) .
'_' . sha1(uniqid(dechex(mt_rand(256, 4095))));
520 if (!is_string($docName) || $this->
docExists($docName)) {
521 throw new XmlDbException(
"Invalid or duplicate document name \"$docName\"");
524 $query = $this->pdo->prepare(
525 "INSERT {$this->tableDocs} SET name = :name, type = :type, ns = '', entities = ''"
532 $docId = $this->pdo->lastInsertId();
533 $document =
new Document($this, $docId);
548 $original = $this->
getDoc($docNameOrId);
550 if ($original !==
false) {
551 $info = $original->getDocInfo();
552 $xml = $original->getXml(
false);
554 $copy = $this->
createDoc($info->type, $newName);
570 if ($docId !==
false) {
571 $query = $this->pdo->prepare(
573 FROM {$this->tableDocs}
580 $this->cache->delete(
Helpers\CacheKey::forDocument($this->tableDocs, $docId));
582 $this->docIds = array_filter(
584 function ($id) use ($docId) {
585 return $id != $docId;
604 $schema = new \Depage\Db\Schema($this->pdo);
606 $pdoPrefix = $this->pdo->prefix;
611 if ($tableName ==
'_auth_user') {
612 return $pdoPrefix . $tableName;
619 $this->pdo->exec(
'SET FOREIGN_KEY_CHECKS=0;');
622 $schema->loadGlob(__DIR__ .
'/Sql/*.sql');
625 $this->pdo->exec(
'SET FOREIGN_KEY_CHECKS=1;');
632 $this->pdo->query(
"DELETE FROM `{$this->tableDocs}`;");
648 $this->alteringTransaction =
true;
662 if ($this->transactions == 0) {
667 $this->transactions++;
683 $this->transactions--;
685 if ($this->transactions == 0) {
689 $this->alteringTransaction =
false;
706 if ($this->transactions == 0) {
710 $this->transactions--;
712 if ($this->transactions == 0) {
714 $this->alteringTransaction =
false;
731 $this->transactions = 0;
732 $this->alteringTransaction =
false;
739 $this->pdo->beginTransaction();
743 $this->pdo->commit();
749 if ($this->pdo->inTransaction()) {
750 $this->pdo->rollBack();
758 if ($this->pdo->inTransaction()) {
759 $this->pdo->exec(
'SAVEPOINT xmldb_sp_' . (
int) $level);
764 if ($this->pdo->inTransaction()) {
765 $this->pdo->exec(
'RELEASE SAVEPOINT xmldb_sp_' . (
int) $level);
770 if ($this->pdo->inTransaction()) {
771 $this->pdo->exec(
'ROLLBACK TO SAVEPOINT xmldb_sp_' . (
int) $level);
static parseAttributeString($value)
parses a stored attribute string into a name => value map; used by getAttributes and by the batch val...
clearTables()
Removes SQL tables.
endTransaction()
closes the innermost open transaction level
getDocuments($name="", $type="")
gets available documents in database
rollBackTransaction()
rolls back to the start of the innermost open transaction level, keeping outer transaction levels (if...
createDoc($doctype='Depage\XmlDb\XmlDoctypes\Base', $docName=null)
CreateDoc.
docExists($docIdOrName)
gets the doc-id of a xml-document by name or id and checks if the document exists
__get($property)
Get properties (basically read-only).
getNodeIdsByXpath($xpath, $docId=null)
gets node_ids by xpath
getDocByNodeId($nodeId)
Get xmlDb\document.
getDocXml($docIdOrName, $addIdAttribute=true)
gets a document as a DOMDocument
getDocXmlXpath($docIdOrName, $xpath, $addIdAttribute=true)
gets the first node of a document that matches an xpath query
duplicateDoc($docNameOrId, $newName=null)
duplicateDoc
updateSchema()
updateSchema
beginTransactionNonAltering()
opens a transaction that only reads the documents
rollbackToSavepoint($level)
getDoc($docIdOrName)
Get xmlDb\document.
getSubdocByXpath($xpath, $addIdAttribute=true)
gets document by xpath.
__construct($tablePrefix, $pdo, $cache, $options=[])
creates an instance of the xmlDb
getNodeIdsByXpathDom($xpath, $docs=[])
getIndexNodeData($nodeIds)
loads the document id and the attributes of the given nodes with a single query (used by the Indexer ...
getIndexTextContent($nodeIds)
concatenates the descendant text nodes of the given nodes with a single query, in document order; tha...
rollBack()
resets the transaction state completely and rolls back the database transaction; use to recover after...
getDocInfo($docIdOrName)
getDocInfo
beginTransactionAltering()
opens a transaction that changes the documents
A namespace prefix and the URI it is bound to.
Parses XPath strings into XpathElement objects.
const SORTKEY_PAD
number of decimal digits used for the position part of the recursive CTE sortkey; limits the tree dep...
Interface of a read only accessor for xml documents.