Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
35.21% covered (danger)
35.21%
25 / 71
18.75% covered (danger)
18.75%
3 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
PageImportState
35.21% covered (danger)
35.21%
25 / 71
18.75% covered (danger)
18.75%
3 / 16
257.71
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
1
 put
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 get
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 clearManagerGroup
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTopRevision
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 getTimestampId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setWorkflowTimestamp
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setRevisionTimestamp
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
5.07
 recordAssociation
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getImportedId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 createUser
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
 begin
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 commit
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 rollback
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 flushDeferredQueue
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 clearDeferredQueue
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace Flow\Import;
4
5use Flow\Data\ManagerGroup;
6use Flow\DbFactory;
7use Flow\Import\Postprocessor\Postprocessor;
8use Flow\Import\SourceStore\SourceStoreInterface;
9use Flow\Model\AbstractRevision;
10use Flow\Model\PostRevision;
11use Flow\Model\UUID;
12use Flow\Model\Workflow;
13use MediaWiki\Deferred\DeferredUpdates;
14use MediaWiki\User\User;
15use Psr\Log\LoggerInterface;
16use ReflectionProperty;
17use SplQueue;
18use Wikimedia\IPUtils;
19use Wikimedia\Rdbms\IDatabase;
20
21class PageImportState {
22    /**
23     * @var LoggerInterface
24     */
25    public $logger;
26
27    /**
28     * @var Workflow
29     */
30    public $boardWorkflow;
31
32    /**
33     * @var ManagerGroup
34     */
35    protected $storage;
36
37    /**
38     * @var ReflectionProperty
39     */
40    protected $workflowIdProperty;
41
42    /**
43     * @var ReflectionProperty
44     */
45    protected $postIdProperty;
46
47    /**
48     * @var ReflectionProperty
49     */
50    protected $revIdProperty;
51
52    /**
53     * @var ReflectionProperty
54     */
55    protected $lastEditIdProperty;
56
57    /**
58     * @var bool
59     */
60    protected $allowUnknownUsernames;
61
62    /**
63     * @var Postprocessor
64     */
65    public $postprocessor;
66
67    /**
68     * @var SplQueue
69     */
70    protected $deferredQueue;
71
72    /**
73     * @var SourceStoreInterface
74     */
75    private $sourceStore;
76
77    /**
78     * @var \Wikimedia\Rdbms\IMaintainableDatabase
79     */
80    private $dbw;
81
82    public function __construct(
83        Workflow $boardWorkflow,
84        ManagerGroup $storage,
85        SourceStoreInterface $sourceStore,
86        LoggerInterface $logger,
87        DbFactory $dbFactory,
88        Postprocessor $postprocessor,
89        SplQueue $deferredQueue,
90        $allowUnknownUsernames = false
91    ) {
92        $this->storage = $storage;
93        $this->boardWorkflow = $boardWorkflow;
94        $this->sourceStore = $sourceStore;
95        $this->logger = $logger;
96        $this->dbw = $dbFactory->getDB( DB_PRIMARY );
97        $this->postprocessor = $postprocessor;
98        $this->deferredQueue = $deferredQueue;
99        $this->allowUnknownUsernames = $allowUnknownUsernames;
100
101        // Get our workflow UUID property
102        $this->workflowIdProperty = new ReflectionProperty( Workflow::class, 'id' );
103        $this->workflowIdProperty->setAccessible( true );
104
105        // Get our revision UUID properties
106        $this->postIdProperty = new ReflectionProperty( PostRevision::class, 'postId' );
107        $this->postIdProperty->setAccessible( true );
108        $this->revIdProperty = new ReflectionProperty( AbstractRevision::class, 'revId' );
109        $this->revIdProperty->setAccessible( true );
110        $this->lastEditIdProperty = new ReflectionProperty( AbstractRevision::class, 'lastEditId' );
111        $this->lastEditIdProperty->setAccessible( true );
112    }
113
114    /**
115     * @param object|object[] $object
116     * @param array $metadata
117     */
118    public function put( $object, array $metadata ) {
119        $metadata['imported'] = true;
120        if ( is_array( $object ) ) {
121            $this->storage->multiPut( $object, $metadata );
122        } else {
123            $this->storage->put( $object, $metadata );
124        }
125    }
126
127    /**
128     * Gets the given object from storage
129     *
130     * WARNING: Before calling this method, ensure that you follow the rule
131     * given in clearManagerGroup.
132     *
133     * @param string $type Class name to retrieve
134     * @param UUID $id ID of the object to retrieve
135     * @return object|false
136     */
137    public function get( $type, UUID $id ) {
138        return $this->storage->get( $type, $id );
139    }
140
141    /**
142     * Clears information about which objects are loaded, to avoid memory leaks.
143     * This will also:
144     * * Clear the mapper associated with each ObjectManager that has been used.
145     * * Trigger onAfterClear on any listeners.
146     *
147     * WARNING: You can *NOT* call ->get before calling clearManagerGroup, then ->put
148     * after calling clearManagerGroup, on the same object.  This will cause a
149     * duplicate object to be inserted.
150     */
151    public function clearManagerGroup() {
152        $this->storage->clear();
153    }
154
155    /**
156     * Gets the top revision of an item by ID
157     *
158     * @param string $type The type of the object to return (e.g. PostRevision).
159     * @param UUID $id The ID (e.g. post ID, topic ID, etc)
160     * @return object|false The top revision of the requested object, or false if not found.
161     */
162    public function getTopRevision( $type, UUID $id ) {
163        $result = $this->storage->find(
164            $type,
165            [ 'rev_type_id' => $id ],
166            [
167                'sort' => 'rev_id',
168                'order' => 'DESC',
169                'limit' => 1
170            ]
171        );
172
173        if ( is_array( $result ) && count( $result ) ) {
174            return reset( $result );
175        } else {
176            return false;
177        }
178    }
179
180    /**
181     * Creates a UUID object representing a given timestamp.
182     *
183     * @param string $timestamp The timestamp to represent, in a wfTimestamp compatible format.
184     * @return UUID
185     */
186    public function getTimestampId( $timestamp ) {
187        return UUID::create( HistoricalUIDGenerator::historicalTimestampedUID88( $timestamp ) );
188    }
189
190    /**
191     * Update the id of the workflow to match the provided timestamp
192     *
193     * @param Workflow $workflow
194     * @param string $timestamp
195     */
196    public function setWorkflowTimestamp( Workflow $workflow, $timestamp ) {
197        $uid = $this->getTimestampId( $timestamp );
198        $this->workflowIdProperty->setValue( $workflow, $uid );
199    }
200
201    /**
202     * @param AbstractRevision $revision
203     * @param string $timestamp
204     */
205    public function setRevisionTimestamp( AbstractRevision $revision, $timestamp ) {
206        $uid = $this->getTimestampId( $timestamp );
207
208        // We don't set the topic title postId as it was inherited from the workflow.  We only set the
209        // postId for first revisions because further revisions inherit it from the parent which was
210        // set appropriately.
211        if ( $revision instanceof PostRevision && $revision->isFirstRevision(
212            ) && !$revision->isTopicTitle()
213        ) {
214            $this->postIdProperty->setValue( $revision, $uid );
215        }
216
217        if ( $revision->getRevisionId()->equals( $revision->getLastContentEditId() ) ) {
218            $this->lastEditIdProperty->setValue( $revision, $uid );
219        }
220        $this->revIdProperty->setValue( $revision, $uid );
221    }
222
223    /**
224     * Records an association between a created object and its source.
225     *
226     * @param UUID $objectId UUID representing the object that was created.
227     * @param IImportObject $object Output from getObjectKey
228     */
229    public function recordAssociation( UUID $objectId, IImportObject $object ) {
230        $this->sourceStore->setAssociation( $objectId, $object->getObjectKey() );
231    }
232
233    /**
234     * Gets the imported ID for a given object, if any.
235     *
236     * @param IImportObject $object
237     * @return UUID|false
238     */
239    public function getImportedId( IImportObject $object ) {
240        return $this->sourceStore->getImportedId( $object );
241    }
242
243    public function createUser( $name ) {
244        if ( IPUtils::isIPAddress( $name ) ) {
245            return User::newFromName( $name, false );
246        }
247        $user = User::newFromName( $name );
248        if ( !$user ) {
249            throw new ImportException( 'Unable to create user: ' . $name );
250        }
251        if ( $user->getId() == 0 && !$this->allowUnknownUsernames ) {
252            throw new ImportException( 'User does not exist: ' . $name );
253        }
254
255        return $user;
256    }
257
258    public function begin() {
259        $this->flushDeferredQueue();
260        $this->dbw->startAtomic( __CLASS__, IDatabase::ATOMIC_CANCELABLE );
261    }
262
263    public function commit() {
264        $this->dbw->endAtomic( __CLASS__ );
265        $this->sourceStore->save();
266        $this->flushDeferredQueue();
267    }
268
269    public function rollback() {
270        $this->dbw->cancelAtomic( __CLASS__ );
271        $this->sourceStore->rollback();
272        $this->clearDeferredQueue();
273        $this->postprocessor->importAborted();
274    }
275
276    protected function flushDeferredQueue() {
277        while ( !$this->deferredQueue->isEmpty() ) {
278            DeferredUpdates::addCallableUpdate(
279                $this->deferredQueue->dequeue(),
280                DeferredUpdates::PRESEND
281            );
282            DeferredUpdates::tryOpportunisticExecute();
283        }
284    }
285
286    protected function clearDeferredQueue() {
287        while ( !$this->deferredQueue->isEmpty() ) {
288            $this->deferredQueue->dequeue();
289        }
290    }
291}