1<?php
2/**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 */
8namespace Piwik\Plugins\TagManager\Dao;
9
10
11use Piwik\Db;
12use Piwik\DbHelper;
13use Piwik\Piwik;
14use Exception;
15use Piwik\Plugins\TagManager\Input\Description;
16use Piwik\Plugins\TagManager\Input\Name;
17
18class ContainerVersionsDao extends BaseDao implements TagManagerDao
19{
20    const REVISION_DRAFT = 0;
21
22    protected $table = 'tagmanager_container_version';
23
24    public function install()
25    {
26        DbHelper::createTable($this->table, "
27                  `idcontainerversion` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
28                  `idcontainer` VARCHAR(8) NOT NULL,
29                  `idsite` int(11) UNSIGNED NOT NULL,
30                  `status` VARCHAR(10) NOT NULL,
31                  `revision` MEDIUMINT UNSIGNED NOT NULL DEFAULT 1,
32                  `name` VARCHAR(" . Name::MAX_LENGTH . ") NOT NULL DEFAULT '',
33                  `description` VARCHAR(" . Description::MAX_LENGTH . ") NOT NULL DEFAULT '',
34                  `created_date` DATETIME NOT NULL,
35                  `updated_date` DATETIME NOT NULL,
36                  `deleted_date` DATETIME NULL,
37                  PRIMARY KEY(`idcontainerversion`), KEY(`idcontainer`), KEY (`idsite`, `idcontainer`)");
38        // we cannot set a unique key on (`idsite`, `idcontainerversion`, `name`) because we soft delete tags and want to make sure names can be used again after deleting an entry
39    }
40
41    private function isNameInUse($idSite, $idContainer, $name, $exceptIdVersion = null)
42    {
43        $sql = sprintf("SELECT idcontainerversion FROM %s WHERE idsite = ? AND idcontainer = ? AND `name` = ? AND status = ?", $this->tablePrefixed);
44        $bind = array($idSite, $idContainer, $name, self::STATUS_ACTIVE);
45
46        if (!empty($exceptIdVersion)) {
47            $sql .= ' AND idcontainerversion != ?';
48            $bind[] = $exceptIdVersion;
49        }
50
51        $idSite = Db::fetchOne($sql, $bind);
52        return !empty($idSite);
53    }
54
55    public function getNextRevisionOfContainer($idSite, $idContainer)
56    {
57        $sql = "SELECT max(revision) FROM " . $this->tablePrefixed . " WHERE idsite = ? and idcontainer = ?";
58        $revision = Db::fetchOne($sql, array($idSite, $idContainer));
59
60        if (empty($revision)) {
61            return 1;
62        }
63
64        return $revision + 1;
65    }
66
67    private function hasDraftVersionAlready($idSite, $idContainer)
68    {
69        $sql = sprintf("SELECT idsite FROM %s WHERE idsite = ? AND idcontainer = ? AND `revision` = 0 AND status = ?", $this->tablePrefixed);
70        $bind = array($idSite, $idContainer, self::STATUS_ACTIVE);
71
72        $idSite = Db::fetchOne($sql, $bind);
73        return !empty($idSite);
74    }
75
76    public function createDraftVersion($idSite, $idContainer, $createdDate)
77    {
78        if ($this->hasDraftVersionAlready($idSite, $idContainer)) {
79            throw new Exception('A draft version for this container already exists');
80        }
81        $values = array(
82            'idcontainer' => $idContainer,
83            'idsite' => $idSite,
84            'status' => self::STATUS_ACTIVE,
85            'created_date' => $createdDate,
86            'updated_date' => $createdDate,
87            'revision' => self::REVISION_DRAFT
88        );
89
90        return $this->insertRecord($values);
91    }
92
93    public function createVersion($idSite, $idContainer, $versionName, $versionDescription, $createdDate)
94    {
95        if ($this->isNameInUse($idSite, $idContainer, $versionName)) {
96            throw new Exception(Piwik::translate('TagManager_ErrorNameDuplicate'));
97        }
98
99        $revision = $this->getNextRevisionOfContainer($idSite, $idContainer);
100
101        $values = array(
102            'idcontainer' => $idContainer,
103            'idsite' => $idSite,
104            'status' => self::STATUS_ACTIVE,
105            'name' => $versionName,
106            'description' => !empty($versionDescription) ? $versionDescription : '',
107            'created_date' => $createdDate,
108            'updated_date' => $createdDate,
109            'revision' => $revision
110        );
111
112        return $this->insertRecord($values);
113    }
114
115    /**
116     * @param int $idSite
117     * @param array $statuses
118     * @return array
119     */
120    public function getVersionsOfContainer($idSite, $idContainer)
121    {
122        $bind = array(self::STATUS_ACTIVE, $idSite, $idContainer);
123
124        $table = $this->tablePrefixed;
125        $versions = Db::fetchAll("SELECT * FROM $table WHERE status = ? AND idsite = ? and idcontainer = ? and revision > 0 order by revision desc", $bind);
126
127        return $this->enrichVersions($versions);
128    }
129
130    /**
131     * @param int $idSite
132     * @param array $statuses
133     * @return array
134     */
135    public function getDraftVersion($idSite, $idContainer)
136    {
137        $bind = array(self::STATUS_ACTIVE, $idSite, $idContainer);
138
139        $table = $this->tablePrefixed;
140        $version = Db::fetchRow("SELECT * FROM $table WHERE status = ? AND idsite = ? and idcontainer = ? and revision = 0 LIMIT 1", $bind);
141
142        return $this->enrichVersion($version);
143    }
144
145    /**
146     * @param int $idSite
147     * @param array $statuses
148     * @return array
149     */
150    public function getVersion($idSite, $idContainer, $idContainerVersion)
151    {
152        $bind = array(self::STATUS_ACTIVE, $idSite, $idContainer, $idContainerVersion);
153
154        $table = $this->tablePrefixed;
155        $version = Db::fetchRow("SELECT * FROM $table WHERE status = ? AND idsite = ? and idcontainer = ? and idcontainerversion = ? LIMIT 1", $bind);
156
157        return $this->enrichVersion($version);
158    }
159
160    public function updateContainerColumns($idSite, $idContainer, $idContainerVersion, $columns)
161    {
162        if (!empty($columns)) {
163            if (isset($columns['description']) && empty($columns['description'])) {
164                $columns['description'] = '';
165            }
166            if (isset($columns['name']) && $this->isNameInUse($idSite, $idContainer, $columns['name'], $idContainerVersion)) {
167                throw new Exception(Piwik::translate('TagManager_ErrorNameDuplicate'));
168            }
169
170            $this->updateEntity($columns, ['idsite' => (int)$idSite, 'idcontainer' => $idContainer, 'idcontainerversion' => (int)$idContainerVersion]);
171        }
172    }
173
174    public function getAllVersions()
175    {
176        $containers = Db::fetchAll('SELECT * FROM ' . $this->tablePrefixed . ' ORDER BY idcontainerversion ASC');
177        return $this->enrichVersions($containers);
178    }
179
180    /**
181     * @param int $idSite
182     * @param string $deletedDate
183     */
184    public function deleteAllVersionsForSite($idSite, $deletedDate)
185    {
186        $table = $this->tablePrefixed;
187
188        $query = "UPDATE $table SET status = ?, deleted_date = ? WHERE idsite = ? and status != ?";
189        $bind = array(self::STATUS_DELETED, $deletedDate, $idSite, self::STATUS_DELETED);
190
191        Db::query($query, $bind);
192    }
193
194    /**
195     * @param int $idSite
196     * @param int $idContainerVersion
197     * @param string $deletedDate
198     */
199    public function deleteVersion($idSite, $idContainerVersion, $deletedDate)
200    {
201        $table = $this->tablePrefixed;
202
203        $query = "UPDATE $table SET status = ?, deleted_date = ? WHERE idsite = ? and idcontainerversion = ? and status != ?";
204        $bind = array(self::STATUS_DELETED, $deletedDate, $idSite, $idContainerVersion, self::STATUS_DELETED);
205
206        Db::query($query, $bind);
207    }
208
209    private function enrichVersions($containers)
210    {
211        if (empty($containers)) {
212            return array();
213        }
214
215        foreach ($containers as $index => $container) {
216            $containers[$index] = $this->enrichVersion($container);
217        }
218
219        return $containers;
220    }
221
222    private function enrichVersion($container)
223    {
224        if (empty($container)) {
225            return $container;
226        }
227
228        $container['idcontainerversion'] = (int) $container['idcontainerversion'];
229        $container['revision'] = (int) $container['revision'];
230        $container['idsite'] = (int) $container['idsite'];
231
232        return $container;
233    }
234}
235
236