Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
56.52% covered (warning)
56.52%
39 / 69
50.00% covered (danger)
50.00%
5 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
ChangeTracker
56.52% covered (warning)
56.52%
39 / 69
50.00% covered (danger)
50.00%
5 / 10
57.25
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 retrieve
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRetrieveDeletedCallback
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
 retrieveDeletedCount
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 retrieveDeleted
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 retrieveOrCreate
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 markDeleted
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
2.02
 getUtcDate
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 strToUtcTime
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 index
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
6.01
1<?php
2
3/**
4 * Table Definition for change_tracker
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2010.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2,
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 *
23 * @category VuFind
24 * @package  Db_Table
25 * @author   Demian Katz <demian.katz@villanova.edu>
26 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
27 * @link     https://vufind.org Main Site
28 */
29
30namespace VuFind\Db\Table;
31
32use Laminas\Db\Adapter\Adapter;
33use Laminas\Db\Sql\Expression;
34use VuFind\Db\Row\RowGateway;
35
36/**
37 * Table Definition for change_tracker
38 *
39 * @category VuFind
40 * @package  Db_Table
41 * @author   Demian Katz <demian.katz@villanova.edu>
42 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
43 * @link     https://vufind.org Main Site
44 */
45class ChangeTracker extends Gateway
46{
47    /**
48     * Date/time format for database
49     *
50     * @var string
51     */
52    protected $dateFormat = 'Y-m-d H:i:s';
53
54    /**
55     * Constructor
56     *
57     * @param Adapter       $adapter Database adapter
58     * @param PluginManager $tm      Table manager
59     * @param array         $cfg     Laminas configuration
60     * @param RowGateway    $rowObj  Row prototype object (null for default)
61     * @param string        $table   Name of database table to interface with
62     */
63    public function __construct(
64        Adapter $adapter,
65        PluginManager $tm,
66        $cfg,
67        ?RowGateway $rowObj = null,
68        $table = 'change_tracker'
69    ) {
70        parent::__construct($adapter, $tm, $cfg, $rowObj, $table);
71    }
72
73    /**
74     * Retrieve a row from the database based on primary key; return null if it
75     * is not found.
76     *
77     * @param string $core The Solr core holding the record.
78     * @param string $id   The ID of the record being indexed.
79     *
80     * @return ?\VuFind\Db\Row\ChangeTracker
81     */
82    public function retrieve($core, $id)
83    {
84        return $this->select(['core' => $core, 'id' => $id])->current();
85    }
86
87    /**
88     * Build a callback function for use by the retrieveDeleted* methods.
89     *
90     * @param string $core    The Solr core holding the record.
91     * @param string $from    The beginning date of the range to search.
92     * @param string $until   The end date of the range to search.
93     * @param int    $offset  Record number to retrieve first.
94     * @param int    $limit   Retrieval limit (null for no limit)
95     * @param array  $columns Columns to retrieve (null for all)
96     * @param string $order   Sort order
97     *
98     * @return callable
99     */
100    public function getRetrieveDeletedCallback(
101        $core,
102        $from,
103        $until,
104        $offset = 0,
105        $limit = null,
106        $columns = null,
107        $order = null
108    ) {
109        return function ($select) use (
110            $core,
111            $from,
112            $until,
113            $offset,
114            $limit,
115            $columns,
116            $order
117        ) {
118            if ($columns !== null) {
119                $select->columns($columns);
120            }
121            $select->where
122                ->equalTo('core', $core)
123                ->greaterThanOrEqualTo('deleted', $from)
124                ->lessThanOrEqualTo('deleted', $until);
125            if ($order !== null) {
126                $select->order($order);
127            }
128            if ($offset > 0) {
129                $select->offset($offset);
130            }
131            if ($limit !== null) {
132                $select->limit($limit);
133            }
134        };
135    }
136
137    /**
138     * Retrieve a set of deleted rows from the database.
139     *
140     * @param string $core  The Solr core holding the record.
141     * @param string $from  The beginning date of the range to search.
142     * @param string $until The end date of the range to search.
143     *
144     * @return \Laminas\Db\ResultSet\AbstractResultSet
145     */
146    public function retrieveDeletedCount($core, $from, $until)
147    {
148        $columns = ['count' => new Expression('COUNT(*)')];
149        $callback = $this
150            ->getRetrieveDeletedCallback($core, $from, $until, 0, null, $columns);
151        $select = $this->sql->select();
152        $callback($select);
153        $statement = $this->sql->prepareStatementForSqlObject($select);
154        $result = $statement->execute();
155        return ((array)$result->current())['count'];
156    }
157
158    /**
159     * Retrieve a set of deleted rows from the database.
160     *
161     * @param string $core   The Solr core holding the record.
162     * @param string $from   The beginning date of the range to search.
163     * @param string $until  The end date of the range to search.
164     * @param int    $offset Record number to retrieve first.
165     * @param int    $limit  Retrieval limit (null for no limit)
166     *
167     * @return \Laminas\Db\ResultSet\AbstractResultSet
168     */
169    public function retrieveDeleted(
170        $core,
171        $from,
172        $until,
173        $offset = 0,
174        $limit = null
175    ) {
176        $callback = $this->getRetrieveDeletedCallback(
177            $core,
178            $from,
179            $until,
180            $offset,
181            $limit,
182            null,
183            'deleted'
184        );
185        return $this->select($callback);
186    }
187
188    /**
189     * Retrieve a row from the database based on primary key; create a new
190     * row if no existing match is found.
191     *
192     * @param string $core The Solr core holding the record.
193     * @param string $id   The ID of the record being indexed.
194     *
195     * @return \VuFind\Db\Row\ChangeTracker
196     */
197    public function retrieveOrCreate($core, $id)
198    {
199        $row = $this->retrieve($core, $id);
200        if (empty($row)) {
201            $row = $this->createRow();
202            $row->core = $core;
203            $row->id = $id;
204            $row->first_indexed = $row->last_indexed = $this->getUtcDate();
205        }
206        return $row;
207    }
208
209    /**
210     * Update the change tracker table to indicate that a record has been deleted.
211     *
212     * The method returns the updated/created row when complete.
213     *
214     * @param string $core The Solr core holding the record.
215     * @param string $id   The ID of the record being indexed.
216     *
217     * @return \VuFind\Db\Row\ChangeTracker
218     */
219    public function markDeleted($core, $id)
220    {
221        // Get a row matching the specified details:
222        $row = $this->retrieveOrCreate($core, $id);
223
224        // If the record is already deleted, we don't need to do anything!
225        if (!empty($row->deleted)) {
226            return $row;
227        }
228
229        // Save new value to the object:
230        $row->deleted = $this->getUtcDate();
231        $row->save();
232        return $row;
233    }
234
235    /**
236     * Get a UTC time.
237     *
238     * @param int $ts Timestamp (null for current)
239     *
240     * @return string
241     */
242    protected function getUtcDate($ts = null)
243    {
244        $oldTz = date_default_timezone_get();
245        date_default_timezone_set('UTC');
246        $date = date($this->dateFormat, $ts ?? time());
247        date_default_timezone_set($oldTz);
248        return $date;
249    }
250
251    /**
252     * Convert a string to time in UTC.
253     *
254     * @param string $str String to parse
255     *
256     * @return int
257     */
258    protected function strToUtcTime($str)
259    {
260        $oldTz = date_default_timezone_get();
261        date_default_timezone_set('UTC');
262        $time = strtotime($str);
263        date_default_timezone_set($oldTz);
264        return $time;
265    }
266
267    /**
268     * Update the change_tracker table to reflect that a record has been indexed.
269     * We need to know the date of the last change to the record (independent of
270     * its addition to the index) in order to tell the difference between a
271     * reindex of a previously-encountered record and a genuine change.
272     *
273     * The method returns the updated/created row when complete.
274     *
275     * @param string $core   The Solr core holding the record.
276     * @param string $id     The ID of the record being indexed.
277     * @param int    $change The timestamp of the last record change.
278     *
279     * @return \VuFind\Db\Row\ChangeTracker
280     */
281    public function index($core, $id, $change)
282    {
283        // Get a row matching the specified details:
284        $row = $this->retrieveOrCreate($core, $id);
285
286        // Flag to indicate whether we need to save the contents of $row:
287        $saveNeeded = false;
288
289        // Make sure there is a change date in the row (this will be empty
290        // if we just created a new row):
291        if (empty($row->last_record_change)) {
292            $row->last_record_change = $this->getUtcDate($change);
293            $saveNeeded = true;
294        }
295
296        // Are we restoring a previously deleted record, or was the stored
297        // record change date before current record change date?  Either way,
298        // we need to update the table!
299        if (
300            !empty($row->deleted)
301            || $this->strToUtcTime($row->last_record_change) < $change
302        ) {
303            // Save new values to the object:
304            $row->last_indexed = $this->getUtcDate();
305            $row->last_record_change = $this->getUtcDate($change);
306
307            // If first indexed is null, we're restoring a deleted record, so
308            // we need to treat it as new -- we'll use the current time.
309            if (empty($row->first_indexed)) {
310                $row->first_indexed = $row->last_indexed;
311            }
312
313            // Make sure the record is "undeleted" if necessary:
314            $row->deleted = null;
315
316            $saveNeeded = true;
317        }
318
319        // Save the row if changes were made:
320        if ($saveNeeded) {
321            $row->save();
322        }
323
324        // Send back the row:
325        return $row;
326    }
327}