/lib/_WV2/ArticleType.php
PHP | 450 lines | 216 code | 98 blank | 136 comment | 26 complexity | 44413fc738fb4f28881d71cfac518fa8 MD5 | raw file
- <?php
- /**
- * @defgroup objects Objekt-Abstraktion
- */
- /**
- * Artikeltyp
- *
- * Diese Klasse kapselt einen einzelnen Artikeltyp, der den Redaxo-Artikeln
- * zugewiesen werden kann. Ein Artikeltyp enthält zusätzlich eine Menge von
- * dazugehörigen Metainformationen und legt damit eindeutig fest, welche
- * Metainformationen einem Artikel zugewiesen werden dürfen und welche nicht.
- *
- * Diese Angaben werden nur vom Frontend geprüft, es ist also problemlos
- * möglich, in der Datenbank weitere Metadaten zuzuweisen, auch wenn diese beim
- * nächsten Speichern des Artikels über die Strukturverwaltung der Webseite
- * automatisch wieder entfernt würden.
- *
- * Der Artikeltyp mit der ID _WV2_Metainfoex::DEFAULT_ARTICLE_TYPE hat einen Sonderstatus:
- * Er kann nicht gelöscht werden und wird jedem neuen Artikel automatisch
- * zugewiesen. Außerdem ersetzt er andere Artikeltypen, wenn diese gelöscht
- * werden.
- *
- * Artikeltypen werden nicht wie Metadaten pro Sprache vergeben, sondern nur pro
- * Artikel.
- *
- * @ingroup objects
- */
- class _WV2_ArticleType extends WV_Object {
- const DEFAULT_ID = 1; ///< int die ID des Standard-Artikeltyps
- protected $id; ///< int die ID des Artikeltyps
- protected $name; ///< string der interne Name
- protected $title; ///< string der angezeigte Titel
- protected $metainfos; ///< array Liste von Metainfo-IDs, die diesem Artikeltyp zugeordnet sind (wird lazy geladen)
- protected $origMetainfos; ///< array Liste der vor dem Update zugewiesenen Metainfos (zu Vergleichszwecken bei update())
- protected static $instances = array();
- /**
- * Singleton
- *
- * Gibt eine Instanz der Klasse zurück.
- *
- * @throws WV2_Exception falls die ID nicht gefunden wurde
- * @param mixed $idOrName die ID (int) oder der interne Name (string) des Artikeltyps
- * @return _WV2_ArticleType Instanz des Artikeltyps
- */
- public static function getInstance($idOrName) {
- $id = self::getIDForName($idOrName);
- if (empty(self::$instances[$id])) {
- $callback = array(__CLASS__, '_getInstance');
- $instance = self::getFromCache('metainfoex.attributes.objects', $id, $callback, $id);
- self::$instances[$id] = $instance;
- }
- return self::$instances[$id];
- }
- protected static function _getInstance($id) {
- return new self($id);
- }
- /**
- * Konstruktor
- *
- * Erzeugt ein neues _WV2_ArticleType-Objekt.
- *
- * @throws Exception falls der Artikeltyp nicht gefunden wurde
- * @param mixed $id die ID oder der interne Name (string) des Artikeltyps
- */
- public function __construct($id) {
- $sql = WV_SQLEx::getInstance();
- $data = $sql->safeFetch('*', 'wv2_arttypes', 'id = ?', $id);
- if (!$data) {
- throw new WV2_Exception('Der Artikeltyp #'.$id.' konnte nicht gefunden werden!');
- }
- $this->id = (int) $data['id'];
- $this->name = $data['name'];
- $this->title = $data['title'];
- $this->metainfos = null;
- $this->origMetainfos = null;
- }
- /**
- * ID ermitteln
- *
- * Diese Methode ermittelt für einen internen Namen eines Artikeltyps die
- * dazughörige ID.
- *
- * @throws Exception falls der interne Name nicht gefunden wurde
- * @param string $name der interne Name
- * @return int die gefundene ID
- */
- public static function getIDForName($name) {
- if (sly_Util_String::isInteger($name)) {
- return (int) $name;
- }
- $cache = sly_Core::cache();
- $namespace = 'metainfoex.idmappings';
- $cacheKey = sly_Cache::generateKey('attribute', $name);
- $id = $cache->get($namespace, $cacheKey, -1);
- if ($id >= 0) {
- return (int) $id;
- }
- $id = WV_SQLEx::getInstance()->safeFetch('id', 'wv2_arttypes', 'name = ?', $name);
- if ($id === false) {
- throw new WV2_Exception('Der Artikeltyp "'.$name.'" konnte nicht gefunden werden!');
- }
- $cache->set($namespace, $cacheKey, (int) $id);
- return (int) $id;
- }
- /**
- * Artikeltyp aktualisieren
- *
- * Diese Methode speichert und validiert alle Änderungen, die bisher mit den
- * set*-Methoden auf diesem Objekt durchgeführt wurden, persistent in der
- * Datenbank.
- *
- * @throws Exception falls der interne Name bereits vergeben ist oder ein SQL-Fehler auftrat
- */
- public function update() {
- $sql = WV_SQLEx::getInstance();
- // Auf Eindeutigkeit des Namens prüfen
- if ($sql->count('wv2_arttypes','name = ? AND id <> ?', array($this->name, $this->id)) > 0) {
- throw new WV2_Exception('Dieser interne Name ist bereits vergeben.');
- }
- return self::transactionGuard(array($this, '_update'), null, 'WV2_Exception');
- }
- protected function _update() {
- $sql = WV_SQLEx::getInstance();
- // Daten aktualisieren
- $query = 'UPDATE ~wv2_arttypes SET name = ?, title = ? WHERE id = ?';
- $sql->queryEx($query, array($this->name, $this->title, $this->id), '~');
- // Zugeordnete Metatypen aktualisieren
- $sql->queryEx('DELETE FROM ~wv2_metainfo_type WHERE type_id = ?', $this->id, '~');
- $this->fetchMetainfos();
- if (!empty($this->metainfos)) {
- $records = array();
- $markers = WV_SQLEx::getMarkers(count($this->metainfos), '(?,?)');
- foreach ($this->metainfos as $mid) {
- $records[] = $this->id;
- $records[] = $mid;
- }
- $sql->queryEx('INSERT INTO ~wv2_metainfo_type (type_id,metainfo_id) VALUES '.$markers, $records, '~');
- $records = null;
- $markers = null;
- }
- // Nun löschen wir noch alle Metainfo-Angaben zu allen Artikeln, die nicht
- // mehr zu diesem Artikeltyp gehören. Dazu brauchen wir zuerst alle Artikel,
- // die diesen Artikeltyp besitzen.
- $articles = $sql->getArray('SELECT article_id FROM ~wv2_article_type WHERE type_id = ?', $this->id, '~');
- // Nun können wir diesen Artikeln die nicht mehr erlaubten Metainfos wegnehmen.
- if (!empty($articles)) {
- $articles = implode(',', $articles);
- $query = 'DELETE FROM ~wv2_meta WHERE object_id IN ('.$articles.') AND meta_type = ?';
- $params = array(WV2_Metainfoex::TYPE_ARTICLE);
- if (empty($this->metainfos)) {
- $sql->queryEx($query, $params, '~');
- }
- else {
- $sql->queryEx($query.' AND metainfo_id NOT IN ('.implode(',', $this->metainfos).')', $params, '~');
- }
- // Falls mehr Metainfos diesem Typ zugewiesen wurden, übernehmen wir den Standardwert
- // dieser neuen Metainfos in die dazugehörigen Artikel.
- $newInfos = array_diff($this->metainfos, $this->origMetainfos);
- foreach ($newInfos as $info) {
- $info = _WV2_MetaInfo::getInstance($info, WV2_Metainfoex::TYPE_ARTICLE);
- $sql->queryEx(
- 'INSERT INTO ~wv2_meta '.
- 'SELECT id,clang,?,?,? FROM ~article WHERE id IN ('.$articles.')',
- array($info->getID(), WV2_Metainfoex::TYPE_ARTICLE, $info->getDefault()), '~'
- );
- }
- }
- WV2_Metainfoex::clearDataCache();
- $this->origMetainfos = $this->metainfos;
- return true;
- }
- /**
- * Neuen Artikeltyp erzeugen
- *
- * Diese Methode erzeugt in der Datenbank einen neuen Artikeltyp.
- *
- * @throws Exception falls der interne Name bereits vergeben ist oder ein SQL-Fehler auftrat
- * @param string $name der interne Name
- * @param string $title der anzuzeigende Titel
- * @param array $metainfos eine Liste von Metainfo-IDs
- * @return _WV2_ArticleType der neue Artikeltyp als Objekt
- */
- public static function create($name, $title, $metainfos) {
- $sql = WV_SQLEx::getInstance();
- // Auf Eindeutigkeit des Namens prüfen
- if ($sql->count('wv2_arttypes', 'name = ?', $name) > 0) {
- throw new WV2_Exception('Dieser interne Name ist bereits vergeben.');
- }
- return self::transactionGuard(array(__CLASS__, '_create'), array($name, $title, $metainfos), 'WV2_Exception');
- }
- protected static function _create($name, $title, $metainfos) {
- $sql = WV_SQLEx::getInstance();
- // Daten eintragen
- $query = 'INSERT INTO ~wv2_arttypes (name,title) VALUES (?,?)';
- $sql->queryEx($query, array($name, $title), '~');
- $id = $sql->lastID();
- // Zuordnungen zu den Metainfos erzeugen
- $metainfos = array_unique($metainfos);
- if (!empty($metainfos)) {
- $records = array();
- $markers = WV_SQLEx::getMarkers(count($metainfos), '(?,?)');
- foreach ($metainfos as $mid) {
- $records[] = $id;
- $records[] = (int) $mid;
- }
- if (!empty($records)) {
- $sql->queryEx('INSERT INTO ~wv2_metainfo_type (type_id,metainfo_id) VALUES '.$markers, $records, '~');
- }
- $records = null;
- $markers = null;
- }
- WV2_Metainfoex::clearDataCache();
- return $id;
- }
- /**
- * Artikeltyp löschen
- *
- * Diese Methode löscht einen Artikeltyp. Dabei werden alle Artikel, die ihm
- * zugewiesen waren, nun dem Standard-Artikeltyp zugewiesen. Dabei werden
- * ebenfalls die Metadatum, die nicht zum Standardtyp gehören, entfernt.
- *
- * @throws Exception falls versucht wird, den Standardtyp zu löschen
- */
- public function delete() {
- if ($this->id == self::DEFAULT_ID) {
- throw new WV2_Exception('Der Standard-Artikeltyp kann nicht gelöscht werden!');
- }
- // Welche Metainfos gehörten zu diesem Artikeltypen?
- $infosThisType = WV2_MetaProvider::getMetaInfosForArticleType($this->id);
- $infosDefaultType = WV2_MetaProvider::getMetaInfosForArticleType(self::DEFAULT_ID);
- foreach ($infosDefaultType as $idx => $info) $infosDefaultType[$idx] = $info->getId();
- foreach ($infosThisType as $idx => $info) $infosThisType[$idx] = $info->getId();
- $infosToDelete = array_diff($infosThisType, $infosDefaultType);
- $infosToDelete = array_map('intval', $infosToDelete);
- // Und welche Artikel sind betroffen?
- $articles = array();
- foreach (WV2_MetaProvider::getArticlesByType($this->id) as $article) {
- $articles[] = (int) $article->getId();
- }
- return self::transactionGuard(array($this, '_delete'), array($articles, $infosToDelete), 'WV2_Exception');
- }
- protected function _delete($articles, $infosToDelete) {
- $sql = WV_SQLEx::getInstance();
- // Daten löschen
- $sql->queryEx('DELETE FROM ~wv2_arttypes WHERE id = ?', $this->id, '~');
- $sql->queryEx('DELETE FROM ~wv2_metainfo_type WHERE type_id = ?', $this->id, '~');
- $sql->queryEx('UPDATE ~wv2_article_type SET type_id = ? WHERE type_id = ?', array(self::DEFAULT_ID, $this->id), '~');
- // Metadaten löschen, falls Metainfos und Artikel betroffen sind
- if (!empty($articles) && !empty($infosToDelete)) {
- $sql->queryEx(
- 'DELETE FROM ~wv2_meta WHERE metainfo_id IN ('.implode(',', $infosToDelete).') '.
- 'AND object_id IN ('.implode(',', $articles).') AND meta_type = ?',
- WV2_Metainfoex::TYPE_ARTICLE, '~'
- );
- }
- WV2_Metainfoex::clearDataCache();
- return true;
- }
- protected function fetchMetainfos() {
- if ($this->metainfos === null) {
- $cache = sly_Core::cache();
- $namespace = 'metainfoex.attributes.objects';
- $obj = $cache->get($namespace, $this->id);
- // 1. Prüfen, ob zwischenzeitlich bereits eine Version dieses Objekts
- // in den Cache gelegt wurde, bei der die Metainfos schon geholt
- // wurden.
- // 2. Versuche, das Objekt zu sperren. Wenn das klappt, sind wir die
- // ersten, die die Metainfos holen dürfen und sollen.
- // 3. Wenn alles nicht geklappt hat, warten wir. Wenn wir ein gültiges
- // Objekt bekommen, hat sich das Warten gelohnt. Andernfalls beißen
- // wir in den sauren Apfel und holen die Daten selbst.
- // Unbedingt origMetainfos vergleichen, das metainfos durch einen
- // Setter geändert werden kann, ohne dass die DB aktualisiert wird!
- if ($obj && $obj->origMetainfos !== null) {
- $this->metainfos = $obj->metainfos;
- $this->origMetainfos = $this->metainfos;
- }
- elseif ($cache->lock($namespace, $this->id)) {
- $this->realFetchMetainfos();
- $cache->set($namespace, $this->id, $this);
- $cache->unlock($namespace, $this->id);
- }
- else {
- $obj = $cache->waitForObject($namespace, $this->id);
- if ($obj && $obj->origMetainfos !== null) {
- $this->metainfos = $obj->metainfos;
- $this->origMetainfos = $this->metainfos;
- }
- else {
- $this->realFetchMetainfos();
- }
- }
- }
- }
- protected function realFetchMetainfos() {
- $sql = WV_SQLEx::getInstance();
- $this->metainfos = $sql->getArray('SELECT metainfo_id FROM ~wv2_metainfo_type WHERE type_id = ?', $this->id, '~');
- $this->origMetainfos = $this->metainfos;
- }
- /*
- ************************************************************
- Getter & Setter
- ************************************************************
- */
- /** @name Getter */
- /*@{*/
- /**
- * Getter
- *
- * Diese Methode gibt die gewünschte Eigenschaft ungefiltert zurück.
- *
- * @return mixed die entsprechende Eigenschaft
- */
- public function getID() { return $this->id; }
- public function getName() { return $this->name; }
- public function getTitle() { return $this->title; }
- public function getMetaInfos() {
- $this->fetchMetainfos();
- return $this->metainfos;
- }
- /*@}*/
- /** @name Setter */
- /*@{*/
- /**
- * Setter
- *
- * Diese Methode setzt die Eigenschaft auf einen neuen Wert. Das Prüfen der
- * Eingabe übernimmt erst die update()-Method der jeweiligen Instanz.
- *
- * @param mixed $value der neue Wert der Eigenschaft
- */
- public function setName($value) {
- $this->_set('name', $value, 'string');
- $value = trim($value);
- if (empty($value)) {
- throw new WV_InputException(t('metainfo_emptyarttype'));
- }
- $this->name = $value;
- }
- public function setTitle($value) {
- $this->_set('title', $value, 'string');
- }
- public function setMetaInfos($value) {
- // Wenn der Wert zum ersten Mal gesetzt wird, holen wir uns vorher
- // noch schnell die alte Liste der Metainfos.
- if ($this->metainfos === null) {
- $this->fetchMetainfos();
- }
- $this->metainfos = array_unique(array_map('intval', sly_makeArray($value)));
- }
- /* @} */
- }