PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/_WV2/ArticleType.php

https://bitbucket.org/webvariants/extended-meta-infos
PHP | 450 lines | 216 code | 98 blank | 136 comment | 26 complexity | 44413fc738fb4f28881d71cfac518fa8 MD5 | raw file
  1. <?php
  2. /**
  3. * @defgroup objects Objekt-Abstraktion
  4. */
  5. /**
  6. * Artikeltyp
  7. *
  8. * Diese Klasse kapselt einen einzelnen Artikeltyp, der den Redaxo-Artikeln
  9. * zugewiesen werden kann. Ein Artikeltyp enthält zusätzlich eine Menge von
  10. * dazugehörigen Metainformationen und legt damit eindeutig fest, welche
  11. * Metainformationen einem Artikel zugewiesen werden dürfen und welche nicht.
  12. *
  13. * Diese Angaben werden nur vom Frontend geprüft, es ist also problemlos
  14. * möglich, in der Datenbank weitere Metadaten zuzuweisen, auch wenn diese beim
  15. * nächsten Speichern des Artikels über die Strukturverwaltung der Webseite
  16. * automatisch wieder entfernt würden.
  17. *
  18. * Der Artikeltyp mit der ID _WV2_Metainfoex::DEFAULT_ARTICLE_TYPE hat einen Sonderstatus:
  19. * Er kann nicht gelöscht werden und wird jedem neuen Artikel automatisch
  20. * zugewiesen. Außerdem ersetzt er andere Artikeltypen, wenn diese gelöscht
  21. * werden.
  22. *
  23. * Artikeltypen werden nicht wie Metadaten pro Sprache vergeben, sondern nur pro
  24. * Artikel.
  25. *
  26. * @ingroup objects
  27. */
  28. class _WV2_ArticleType extends WV_Object {
  29. const DEFAULT_ID = 1; ///< int die ID des Standard-Artikeltyps
  30. protected $id; ///< int die ID des Artikeltyps
  31. protected $name; ///< string der interne Name
  32. protected $title; ///< string der angezeigte Titel
  33. protected $metainfos; ///< array Liste von Metainfo-IDs, die diesem Artikeltyp zugeordnet sind (wird lazy geladen)
  34. protected $origMetainfos; ///< array Liste der vor dem Update zugewiesenen Metainfos (zu Vergleichszwecken bei update())
  35. protected static $instances = array();
  36. /**
  37. * Singleton
  38. *
  39. * Gibt eine Instanz der Klasse zurück.
  40. *
  41. * @throws WV2_Exception falls die ID nicht gefunden wurde
  42. * @param mixed $idOrName die ID (int) oder der interne Name (string) des Artikeltyps
  43. * @return _WV2_ArticleType Instanz des Artikeltyps
  44. */
  45. public static function getInstance($idOrName) {
  46. $id = self::getIDForName($idOrName);
  47. if (empty(self::$instances[$id])) {
  48. $callback = array(__CLASS__, '_getInstance');
  49. $instance = self::getFromCache('metainfoex.attributes.objects', $id, $callback, $id);
  50. self::$instances[$id] = $instance;
  51. }
  52. return self::$instances[$id];
  53. }
  54. protected static function _getInstance($id) {
  55. return new self($id);
  56. }
  57. /**
  58. * Konstruktor
  59. *
  60. * Erzeugt ein neues _WV2_ArticleType-Objekt.
  61. *
  62. * @throws Exception falls der Artikeltyp nicht gefunden wurde
  63. * @param mixed $id die ID oder der interne Name (string) des Artikeltyps
  64. */
  65. public function __construct($id) {
  66. $sql = WV_SQLEx::getInstance();
  67. $data = $sql->safeFetch('*', 'wv2_arttypes', 'id = ?', $id);
  68. if (!$data) {
  69. throw new WV2_Exception('Der Artikeltyp #'.$id.' konnte nicht gefunden werden!');
  70. }
  71. $this->id = (int) $data['id'];
  72. $this->name = $data['name'];
  73. $this->title = $data['title'];
  74. $this->metainfos = null;
  75. $this->origMetainfos = null;
  76. }
  77. /**
  78. * ID ermitteln
  79. *
  80. * Diese Methode ermittelt für einen internen Namen eines Artikeltyps die
  81. * dazughörige ID.
  82. *
  83. * @throws Exception falls der interne Name nicht gefunden wurde
  84. * @param string $name der interne Name
  85. * @return int die gefundene ID
  86. */
  87. public static function getIDForName($name) {
  88. if (sly_Util_String::isInteger($name)) {
  89. return (int) $name;
  90. }
  91. $cache = sly_Core::cache();
  92. $namespace = 'metainfoex.idmappings';
  93. $cacheKey = sly_Cache::generateKey('attribute', $name);
  94. $id = $cache->get($namespace, $cacheKey, -1);
  95. if ($id >= 0) {
  96. return (int) $id;
  97. }
  98. $id = WV_SQLEx::getInstance()->safeFetch('id', 'wv2_arttypes', 'name = ?', $name);
  99. if ($id === false) {
  100. throw new WV2_Exception('Der Artikeltyp "'.$name.'" konnte nicht gefunden werden!');
  101. }
  102. $cache->set($namespace, $cacheKey, (int) $id);
  103. return (int) $id;
  104. }
  105. /**
  106. * Artikeltyp aktualisieren
  107. *
  108. * Diese Methode speichert und validiert alle Änderungen, die bisher mit den
  109. * set*-Methoden auf diesem Objekt durchgeführt wurden, persistent in der
  110. * Datenbank.
  111. *
  112. * @throws Exception falls der interne Name bereits vergeben ist oder ein SQL-Fehler auftrat
  113. */
  114. public function update() {
  115. $sql = WV_SQLEx::getInstance();
  116. // Auf Eindeutigkeit des Namens prüfen
  117. if ($sql->count('wv2_arttypes','name = ? AND id <> ?', array($this->name, $this->id)) > 0) {
  118. throw new WV2_Exception('Dieser interne Name ist bereits vergeben.');
  119. }
  120. return self::transactionGuard(array($this, '_update'), null, 'WV2_Exception');
  121. }
  122. protected function _update() {
  123. $sql = WV_SQLEx::getInstance();
  124. // Daten aktualisieren
  125. $query = 'UPDATE ~wv2_arttypes SET name = ?, title = ? WHERE id = ?';
  126. $sql->queryEx($query, array($this->name, $this->title, $this->id), '~');
  127. // Zugeordnete Metatypen aktualisieren
  128. $sql->queryEx('DELETE FROM ~wv2_metainfo_type WHERE type_id = ?', $this->id, '~');
  129. $this->fetchMetainfos();
  130. if (!empty($this->metainfos)) {
  131. $records = array();
  132. $markers = WV_SQLEx::getMarkers(count($this->metainfos), '(?,?)');
  133. foreach ($this->metainfos as $mid) {
  134. $records[] = $this->id;
  135. $records[] = $mid;
  136. }
  137. $sql->queryEx('INSERT INTO ~wv2_metainfo_type (type_id,metainfo_id) VALUES '.$markers, $records, '~');
  138. $records = null;
  139. $markers = null;
  140. }
  141. // Nun löschen wir noch alle Metainfo-Angaben zu allen Artikeln, die nicht
  142. // mehr zu diesem Artikeltyp gehören. Dazu brauchen wir zuerst alle Artikel,
  143. // die diesen Artikeltyp besitzen.
  144. $articles = $sql->getArray('SELECT article_id FROM ~wv2_article_type WHERE type_id = ?', $this->id, '~');
  145. // Nun können wir diesen Artikeln die nicht mehr erlaubten Metainfos wegnehmen.
  146. if (!empty($articles)) {
  147. $articles = implode(',', $articles);
  148. $query = 'DELETE FROM ~wv2_meta WHERE object_id IN ('.$articles.') AND meta_type = ?';
  149. $params = array(WV2_Metainfoex::TYPE_ARTICLE);
  150. if (empty($this->metainfos)) {
  151. $sql->queryEx($query, $params, '~');
  152. }
  153. else {
  154. $sql->queryEx($query.' AND metainfo_id NOT IN ('.implode(',', $this->metainfos).')', $params, '~');
  155. }
  156. // Falls mehr Metainfos diesem Typ zugewiesen wurden, übernehmen wir den Standardwert
  157. // dieser neuen Metainfos in die dazugehörigen Artikel.
  158. $newInfos = array_diff($this->metainfos, $this->origMetainfos);
  159. foreach ($newInfos as $info) {
  160. $info = _WV2_MetaInfo::getInstance($info, WV2_Metainfoex::TYPE_ARTICLE);
  161. $sql->queryEx(
  162. 'INSERT INTO ~wv2_meta '.
  163. 'SELECT id,clang,?,?,? FROM ~article WHERE id IN ('.$articles.')',
  164. array($info->getID(), WV2_Metainfoex::TYPE_ARTICLE, $info->getDefault()), '~'
  165. );
  166. }
  167. }
  168. WV2_Metainfoex::clearDataCache();
  169. $this->origMetainfos = $this->metainfos;
  170. return true;
  171. }
  172. /**
  173. * Neuen Artikeltyp erzeugen
  174. *
  175. * Diese Methode erzeugt in der Datenbank einen neuen Artikeltyp.
  176. *
  177. * @throws Exception falls der interne Name bereits vergeben ist oder ein SQL-Fehler auftrat
  178. * @param string $name der interne Name
  179. * @param string $title der anzuzeigende Titel
  180. * @param array $metainfos eine Liste von Metainfo-IDs
  181. * @return _WV2_ArticleType der neue Artikeltyp als Objekt
  182. */
  183. public static function create($name, $title, $metainfos) {
  184. $sql = WV_SQLEx::getInstance();
  185. // Auf Eindeutigkeit des Namens prüfen
  186. if ($sql->count('wv2_arttypes', 'name = ?', $name) > 0) {
  187. throw new WV2_Exception('Dieser interne Name ist bereits vergeben.');
  188. }
  189. return self::transactionGuard(array(__CLASS__, '_create'), array($name, $title, $metainfos), 'WV2_Exception');
  190. }
  191. protected static function _create($name, $title, $metainfos) {
  192. $sql = WV_SQLEx::getInstance();
  193. // Daten eintragen
  194. $query = 'INSERT INTO ~wv2_arttypes (name,title) VALUES (?,?)';
  195. $sql->queryEx($query, array($name, $title), '~');
  196. $id = $sql->lastID();
  197. // Zuordnungen zu den Metainfos erzeugen
  198. $metainfos = array_unique($metainfos);
  199. if (!empty($metainfos)) {
  200. $records = array();
  201. $markers = WV_SQLEx::getMarkers(count($metainfos), '(?,?)');
  202. foreach ($metainfos as $mid) {
  203. $records[] = $id;
  204. $records[] = (int) $mid;
  205. }
  206. if (!empty($records)) {
  207. $sql->queryEx('INSERT INTO ~wv2_metainfo_type (type_id,metainfo_id) VALUES '.$markers, $records, '~');
  208. }
  209. $records = null;
  210. $markers = null;
  211. }
  212. WV2_Metainfoex::clearDataCache();
  213. return $id;
  214. }
  215. /**
  216. * Artikeltyp löschen
  217. *
  218. * Diese Methode löscht einen Artikeltyp. Dabei werden alle Artikel, die ihm
  219. * zugewiesen waren, nun dem Standard-Artikeltyp zugewiesen. Dabei werden
  220. * ebenfalls die Metadatum, die nicht zum Standardtyp gehören, entfernt.
  221. *
  222. * @throws Exception falls versucht wird, den Standardtyp zu löschen
  223. */
  224. public function delete() {
  225. if ($this->id == self::DEFAULT_ID) {
  226. throw new WV2_Exception('Der Standard-Artikeltyp kann nicht gelöscht werden!');
  227. }
  228. // Welche Metainfos gehörten zu diesem Artikeltypen?
  229. $infosThisType = WV2_MetaProvider::getMetaInfosForArticleType($this->id);
  230. $infosDefaultType = WV2_MetaProvider::getMetaInfosForArticleType(self::DEFAULT_ID);
  231. foreach ($infosDefaultType as $idx => $info) $infosDefaultType[$idx] = $info->getId();
  232. foreach ($infosThisType as $idx => $info) $infosThisType[$idx] = $info->getId();
  233. $infosToDelete = array_diff($infosThisType, $infosDefaultType);
  234. $infosToDelete = array_map('intval', $infosToDelete);
  235. // Und welche Artikel sind betroffen?
  236. $articles = array();
  237. foreach (WV2_MetaProvider::getArticlesByType($this->id) as $article) {
  238. $articles[] = (int) $article->getId();
  239. }
  240. return self::transactionGuard(array($this, '_delete'), array($articles, $infosToDelete), 'WV2_Exception');
  241. }
  242. protected function _delete($articles, $infosToDelete) {
  243. $sql = WV_SQLEx::getInstance();
  244. // Daten löschen
  245. $sql->queryEx('DELETE FROM ~wv2_arttypes WHERE id = ?', $this->id, '~');
  246. $sql->queryEx('DELETE FROM ~wv2_metainfo_type WHERE type_id = ?', $this->id, '~');
  247. $sql->queryEx('UPDATE ~wv2_article_type SET type_id = ? WHERE type_id = ?', array(self::DEFAULT_ID, $this->id), '~');
  248. // Metadaten löschen, falls Metainfos und Artikel betroffen sind
  249. if (!empty($articles) && !empty($infosToDelete)) {
  250. $sql->queryEx(
  251. 'DELETE FROM ~wv2_meta WHERE metainfo_id IN ('.implode(',', $infosToDelete).') '.
  252. 'AND object_id IN ('.implode(',', $articles).') AND meta_type = ?',
  253. WV2_Metainfoex::TYPE_ARTICLE, '~'
  254. );
  255. }
  256. WV2_Metainfoex::clearDataCache();
  257. return true;
  258. }
  259. protected function fetchMetainfos() {
  260. if ($this->metainfos === null) {
  261. $cache = sly_Core::cache();
  262. $namespace = 'metainfoex.attributes.objects';
  263. $obj = $cache->get($namespace, $this->id);
  264. // 1. Prüfen, ob zwischenzeitlich bereits eine Version dieses Objekts
  265. // in den Cache gelegt wurde, bei der die Metainfos schon geholt
  266. // wurden.
  267. // 2. Versuche, das Objekt zu sperren. Wenn das klappt, sind wir die
  268. // ersten, die die Metainfos holen dürfen und sollen.
  269. // 3. Wenn alles nicht geklappt hat, warten wir. Wenn wir ein gültiges
  270. // Objekt bekommen, hat sich das Warten gelohnt. Andernfalls beißen
  271. // wir in den sauren Apfel und holen die Daten selbst.
  272. // Unbedingt origMetainfos vergleichen, das metainfos durch einen
  273. // Setter geändert werden kann, ohne dass die DB aktualisiert wird!
  274. if ($obj && $obj->origMetainfos !== null) {
  275. $this->metainfos = $obj->metainfos;
  276. $this->origMetainfos = $this->metainfos;
  277. }
  278. elseif ($cache->lock($namespace, $this->id)) {
  279. $this->realFetchMetainfos();
  280. $cache->set($namespace, $this->id, $this);
  281. $cache->unlock($namespace, $this->id);
  282. }
  283. else {
  284. $obj = $cache->waitForObject($namespace, $this->id);
  285. if ($obj && $obj->origMetainfos !== null) {
  286. $this->metainfos = $obj->metainfos;
  287. $this->origMetainfos = $this->metainfos;
  288. }
  289. else {
  290. $this->realFetchMetainfos();
  291. }
  292. }
  293. }
  294. }
  295. protected function realFetchMetainfos() {
  296. $sql = WV_SQLEx::getInstance();
  297. $this->metainfos = $sql->getArray('SELECT metainfo_id FROM ~wv2_metainfo_type WHERE type_id = ?', $this->id, '~');
  298. $this->origMetainfos = $this->metainfos;
  299. }
  300. /*
  301. ************************************************************
  302. Getter & Setter
  303. ************************************************************
  304. */
  305. /** @name Getter */
  306. /*@{*/
  307. /**
  308. * Getter
  309. *
  310. * Diese Methode gibt die gewünschte Eigenschaft ungefiltert zurück.
  311. *
  312. * @return mixed die entsprechende Eigenschaft
  313. */
  314. public function getID() { return $this->id; }
  315. public function getName() { return $this->name; }
  316. public function getTitle() { return $this->title; }
  317. public function getMetaInfos() {
  318. $this->fetchMetainfos();
  319. return $this->metainfos;
  320. }
  321. /*@}*/
  322. /** @name Setter */
  323. /*@{*/
  324. /**
  325. * Setter
  326. *
  327. * Diese Methode setzt die Eigenschaft auf einen neuen Wert. Das Prüfen der
  328. * Eingabe übernimmt erst die update()-Method der jeweiligen Instanz.
  329. *
  330. * @param mixed $value der neue Wert der Eigenschaft
  331. */
  332. public function setName($value) {
  333. $this->_set('name', $value, 'string');
  334. $value = trim($value);
  335. if (empty($value)) {
  336. throw new WV_InputException(t('metainfo_emptyarttype'));
  337. }
  338. $this->name = $value;
  339. }
  340. public function setTitle($value) {
  341. $this->_set('title', $value, 'string');
  342. }
  343. public function setMetaInfos($value) {
  344. // Wenn der Wert zum ersten Mal gesetzt wird, holen wir uns vorher
  345. // noch schnell die alte Liste der Metainfos.
  346. if ($this->metainfos === null) {
  347. $this->fetchMetainfos();
  348. }
  349. $this->metainfos = array_unique(array_map('intval', sly_makeArray($value)));
  350. }
  351. /* @} */
  352. }