1<?php 2 3namespace Sabre\DAV\PropertyStorage\Backend; 4 5use Sabre\DAV\PropFind; 6use Sabre\DAV\PropPatch; 7use Sabre\DAV\Xml\Property\Complex; 8 9/** 10 * PropertyStorage PDO backend. 11 * 12 * This backend class uses a PDO-enabled database to store webdav properties. 13 * Both sqlite and mysql have been tested. 14 * 15 * The database structure can be found in the examples/sql/ directory. 16 * 17 * @copyright Copyright (C) 2007-2015 fruux GmbH. (https://fruux.com/) 18 * @author Evert Pot (http://evertpot.com/) 19 * @license http://sabre.io/license/ Modified BSD License 20 */ 21class PDO implements BackendInterface { 22 23 /** 24 * Value is stored as string. 25 */ 26 const VT_STRING = 1; 27 28 /** 29 * Value is stored as XML fragment. 30 */ 31 const VT_XML = 2; 32 33 /** 34 * Value is stored as a property object. 35 */ 36 const VT_OBJECT = 3; 37 38 /** 39 * PDO 40 * 41 * @var \PDO 42 */ 43 protected $pdo; 44 45 /** 46 * Creates the PDO property storage engine 47 * 48 * @param \PDO $pdo 49 */ 50 function __construct(\PDO $pdo) { 51 52 $this->pdo = $pdo; 53 54 } 55 56 /** 57 * Fetches properties for a path. 58 * 59 * This method received a PropFind object, which contains all the 60 * information about the properties that need to be fetched. 61 * 62 * Ususually you would just want to call 'get404Properties' on this object, 63 * as this will give you the _exact_ list of properties that need to be 64 * fetched, and haven't yet. 65 * 66 * However, you can also support the 'allprops' property here. In that 67 * case, you should check for $propFind->isAllProps(). 68 * 69 * @param string $path 70 * @param PropFind $propFind 71 * @return void 72 */ 73 function propFind($path, PropFind $propFind) { 74 75 if (!$propFind->isAllProps() && count($propFind->get404Properties()) === 0) { 76 return; 77 } 78 79 $query = 'SELECT name, value, valuetype FROM propertystorage WHERE path = ?'; 80 $stmt = $this->pdo->prepare($query); 81 $stmt->execute([$path]); 82 83 while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { 84 switch ($row['valuetype']) { 85 case null : 86 case self::VT_STRING : 87 $propFind->set($row['name'], $row['value']); 88 break; 89 case self::VT_XML : 90 $propFind->set($row['name'], new Complex($row['value'])); 91 break; 92 case self::VT_OBJECT : 93 $propFind->set($row['name'], unserialize($row['value'])); 94 break; 95 } 96 } 97 98 } 99 100 /** 101 * Updates properties for a path 102 * 103 * This method received a PropPatch object, which contains all the 104 * information about the update. 105 * 106 * Usually you would want to call 'handleRemaining' on this object, to get; 107 * a list of all properties that need to be stored. 108 * 109 * @param string $path 110 * @param PropPatch $propPatch 111 * @return void 112 */ 113 function propPatch($path, PropPatch $propPatch) { 114 115 $propPatch->handleRemaining(function($properties) use ($path) { 116 117 $updateStmt = $this->pdo->prepare("REPLACE INTO propertystorage (path, name, valuetype, value) VALUES (?, ?, ?, ?)"); 118 $deleteStmt = $this->pdo->prepare("DELETE FROM propertystorage WHERE path = ? AND name = ?"); 119 120 foreach ($properties as $name => $value) { 121 122 if (!is_null($value)) { 123 if (is_scalar($value)) { 124 $valueType = self::VT_STRING; 125 } elseif ($value instanceof Complex) { 126 $valueType = self::VT_XML; 127 $value = $value->getXml(); 128 } else { 129 $valueType = self::VT_OBJECT; 130 $value = serialize($value); 131 } 132 $updateStmt->execute([$path, $name, $valueType, $value]); 133 } else { 134 $deleteStmt->execute([$path, $name]); 135 } 136 137 } 138 139 return true; 140 141 }); 142 143 } 144 145 /** 146 * This method is called after a node is deleted. 147 * 148 * This allows a backend to clean up all associated properties. 149 * 150 * The delete method will get called once for the deletion of an entire 151 * tree. 152 * 153 * @param string $path 154 * @return void 155 */ 156 function delete($path) { 157 158 $stmt = $this->pdo->prepare("DELETE FROM propertystorage WHERE path = ? OR path LIKE ? ESCAPE '='"); 159 $childPath = strtr( 160 $path, 161 [ 162 '=' => '==', 163 '%' => '=%', 164 '_' => '=_' 165 ] 166 ) . '/%'; 167 168 $stmt->execute([$path, $childPath]); 169 170 } 171 172 /** 173 * This method is called after a successful MOVE 174 * 175 * This should be used to migrate all properties from one path to another. 176 * Note that entire collections may be moved, so ensure that all properties 177 * for children are also moved along. 178 * 179 * @param string $source 180 * @param string $destination 181 * @return void 182 */ 183 function move($source, $destination) { 184 185 // I don't know a way to write this all in a single sql query that's 186 // also compatible across db engines, so we're letting PHP do all the 187 // updates. Much slower, but it should still be pretty fast in most 188 // cases. 189 $select = $this->pdo->prepare('SELECT id, path FROM propertystorage WHERE path = ? OR path LIKE ?'); 190 $select->execute([$source, $source . '/%']); 191 192 $update = $this->pdo->prepare('UPDATE propertystorage SET path = ? WHERE id = ?'); 193 while ($row = $select->fetch(\PDO::FETCH_ASSOC)) { 194 195 // Sanity check. SQL may select too many records, such as records 196 // with different cases. 197 if ($row['path'] !== $source && strpos($row['path'], $source . '/') !== 0) continue; 198 199 $trailingPart = substr($row['path'], strlen($source) + 1); 200 $newPath = $destination; 201 if ($trailingPart) { 202 $newPath .= '/' . $trailingPart; 203 } 204 $update->execute([$newPath, $row['id']]); 205 206 } 207 208 } 209 210} 211