Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
31.34% covered (danger)
31.34%
21 / 67
18.75% covered (danger)
18.75%
3 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
PageImportState
31.34% covered (danger)
31.34%
21 / 67
18.75% covered (danger)
18.75%
3 / 16
301.17
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
12 / 12
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
104        // Get our revision UUID properties
105        $this->postIdProperty = new ReflectionProperty( PostRevision::class, 'postId' );
106        $this->revIdProperty = new ReflectionProperty( AbstractRevision::class, 'revId' );
107        $this->lastEditIdProperty = new ReflectionProperty( AbstractRevision::class, 'lastEditId' );
108    }
109
110    /**
111     * @param object|object[] $object
112     * @param array $metadata
113     */
114    public function put( $object, array $metadata ) {
115        $metadata['imported'] = true;
116        if ( is_array( $object ) ) {
117            $this->storage->multiPut( $object, $metadata );
118        } else {
119            $this->storage->put( $object, $metadata );
120        }
121    }
122
123    /**
124     * Gets the given object from storage
125     *
126     * WARNING: Before calling this method, ensure that you follow the rule
127     * given in clearManagerGroup.
128     *
129     * @param string $type Class name to retrieve
130     * @param UUID $id ID of the object to retrieve
131     * @return object|false
132     */
133    public function get( $type, UUID $id ) {
134        return $this->storage->get( $type, $id );
135    }
136
137    /**
138     * Clears information about which objects are loaded, to avoid memory leaks.
139     * This will also:
140     * * Clear the mapper associated with each ObjectManager that has been used.
141     * * Trigger onAfterClear on any listeners.
142     *
143     * WARNING: You can *NOT* call ->get before calling clearManagerGroup, then ->put
144     * after calling clearManagerGroup, on the same object.  This will cause a
145     * duplicate object to be inserted.
146     */
147    public function clearManagerGroup() {
148        $this->storage->clear();
149    }
150
151    /**
152     * Gets the top revision of an item by ID
153     *
154     * @param string $type The type of the object to return (e.g. PostRevision).
155     * @param UUID $id The ID (e.g. post ID, topic ID, etc)
156     * @return object|false The top revision of the requested object, or false if not found.
157     */
158    public function getTopRevision( $type, UUID $id ) {
159        $result = $this->storage->find(
160            $type,
161            [ 'rev_type_id' => $id ],
162            [
163                'sort' => 'rev_id',
164                'order' => 'DESC',
165                'limit' => 1
166            ]
167        );
168
169        if ( is_array( $result ) && count( $result ) ) {
170            return reset( $result );
171        } else {
172            return false;
173        }
174    }
175
176    /**
177     * Creates a UUID object representing a given timestamp.
178     *
179     * @param string $timestamp The timestamp to represent, in a wfTimestamp compatible format.
180     * @return UUID
181     */
182    public function getTimestampId( $timestamp ) {
183        return UUID::create( HistoricalUIDGenerator::historicalTimestampedUID88( $timestamp ) );
184    }
185
186    /**
187     * Update the id of the workflow to match the provided timestamp
188     *
189     * @param Workflow $workflow
190     * @param string $timestamp
191     */
192    public function setWorkflowTimestamp( Workflow $workflow, $timestamp ) {
193        $uid = $this->getTimestampId( $timestamp );
194        $this->workflowIdProperty->setValue( $workflow, $uid );
195    }
196
197    /**
198     * @param AbstractRevision $revision
199     * @param string $timestamp
200     */
201    public function setRevisionTimestamp( AbstractRevision $revision, $timestamp ) {
202        $uid = $this->getTimestampId( $timestamp );
203
204        // We don't set the topic title postId as it was inherited from the workflow.  We only set the
205        // postId for first revisions because further revisions inherit it from the parent which was
206        // set appropriately.
207        if ( $revision instanceof PostRevision && $revision->isFirstRevision()
208            && !$revision->isTopicTitle()
209        ) {
210            $this->postIdProperty->setValue( $revision, $uid );
211        }
212
213        if ( $revision->getRevisionId()->equals( $revision->getLastContentEditId() ) ) {
214            $this->lastEditIdProperty->setValue( $revision, $uid );
215        }
216        $this->revIdProperty->setValue( $revision, $uid );
217    }
218
219    /**
220     * Records an association between a created object and its source.
221     *
222     * @param UUID $objectId UUID representing the object that was created.
223     * @param IImportObject $object Output from getObjectKey
224     */
225    public function recordAssociation( UUID $objectId, IImportObject $object ) {
226        $this->sourceStore->setAssociation( $objectId, $object->getObjectKey() );
227    }
228
229    /**
230     * Gets the imported ID for a given object, if any.
231     *
232     * @param IImportObject $object
233     * @return UUID|false
234     */
235    public function getImportedId( IImportObject $object ) {
236        return $this->sourceStore->getImportedId( $object );
237    }
238
239    public function createUser( $name ) {
240        if ( IPUtils::isIPAddress( $name ) ) {
241            return User::newFromName( $name, false );
242        }
243        $user = User::newFromName( $name );
244        if ( !$user ) {
245            throw new ImportException( 'Unable to create user: ' . $name );
246        }
247        if ( $user->getId() == 0 && !$this->allowUnknownUsernames ) {
248            throw new ImportException( 'User does not exist: ' . $name );
249        }
250
251        return $user;
252    }
253
254    public function begin() {
255        $this->flushDeferredQueue();
256        $this->dbw->startAtomic( __CLASS__, IDatabase::ATOMIC_CANCELABLE );
257    }
258
259    public function commit() {
260        $this->dbw->endAtomic( __CLASS__ );
261        $this->sourceStore->save();
262        $this->flushDeferredQueue();
263    }
264
265    public function rollback() {
266        $this->dbw->cancelAtomic( __CLASS__ );
267        $this->sourceStore->rollback();
268        $this->clearDeferredQueue();
269        $this->postprocessor->importAborted();
270    }
271
272    protected function flushDeferredQueue() {
273        while ( !$this->deferredQueue->isEmpty() ) {
274            DeferredUpdates::addCallableUpdate(
275                $this->deferredQueue->dequeue(),
276                DeferredUpdates::PRESEND
277            );
278            DeferredUpdates::tryOpportunisticExecute();
279        }
280    }
281
282    protected function clearDeferredQueue() {
283        while ( !$this->deferredQueue->isEmpty() ) {
284            $this->deferredQueue->dequeue();
285        }
286    }
287}