Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 74
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractMcrCrud
0.00% covered (danger)
0.00%
0 / 74
0.00% covered (danger)
0.00%
0 / 11
552
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 serialize
n/a
0 / 0
n/a
0 / 0
0
 setMainSlotToIdentityJson
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 deserialize
n/a
0 / 0
n/a
0 / 0
0
 create
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 load
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
 update
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
 delete
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
20
 assembleCommentStoreComment
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 assembleSummary
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 getEditSummary
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setEditSummary
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getIdentityStrategy
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\WikispeechSpeechDataCollector\Crud\Mcr;
4
5/**
6 * @file
7 * @ingroup Extensions
8 * @license GPL-2.0-or-later
9 */
10
11use CommentStoreComment;
12use ExternalStoreException;
13use InvalidArgumentException;
14use MediaWiki\Revision\RevisionRecord;
15use MediaWiki\Revision\SlotRecord;
16use MediaWiki\Storage\PageUpdater;
17use MediaWiki\WikispeechSpeechDataCollector\Crud\AbstractCrud;
18use MediaWiki\WikispeechSpeechDataCollector\Crud\CrudContext;
19use MediaWiki\WikispeechSpeechDataCollector\Domain\Persistent;
20
21/**
22 * Multi Content Revision mapping and access for
23 * instances of the corresponding underlying subclass of {@link Persistent}.
24 *
25 * @since 0.1.0
26 */
27abstract class AbstractMcrCrud extends AbstractCrud {
28
29    /** @var string|null Edit summary set when saving wiki page */
30    private $editSummary;
31
32    /** @var AbstractMcrCrudIdentityStrategy */
33    private $identityStrategy;
34
35    /**
36     * @param CrudContext $context
37     * @param AbstractMcrCrudIdentityStrategy $identityStrategy
38     * @since 0.1.0
39     */
40    public function __construct(
41        CrudContext $context,
42        AbstractMcrCrudIdentityStrategy $identityStrategy
43    ) {
44        parent::__construct( $context );
45        $this->identityStrategy = $identityStrategy;
46    }
47
48    /**
49     * Adds the instance to the page in a way it can be deserialized at a later time.
50     *
51     * @param Persistent $instance
52     * @param PageUpdater $pageUpdater
53     * @see AbstractMcrCrud::deserialize()
54     * @since 0.1.0
55     */
56    abstract public function serialize(
57        Persistent $instance,
58        PageUpdater $pageUpdater
59    ): void;
60
61    /**
62     * @param Persistent $instance
63     * @param PageUpdater $pageUpdater
64     * @since 0.1.0
65     */
66    protected function setMainSlotToIdentityJson(
67        Persistent $instance,
68        PageUpdater $pageUpdater
69    ): void {
70        if ( $instance->getIdentity() === null ) {
71            throw new InvalidArgumentException( 'Identity not set' );
72        }
73        $slotRecord = SlotRecord::newUnsaved(
74            SlotRecord::MAIN,
75            $this->identityStrategy->identityJsonContentFactory( $instance->getIdentity() )
76        );
77        $pageUpdater->setSlot( $slotRecord );
78    }
79
80    /**
81     * Deserializes an instance from the content of a page.
82     *
83     * @param Persistent $instance
84     * @param RevisionRecord $revisionRecord
85     * @return bool true if deserialized, false if not found or deserialized to null.
86     * @since 0.1.0
87     */
88    abstract public function deserialize(
89        Persistent $instance,
90        RevisionRecord $revisionRecord
91    ): bool;
92
93    /**
94     * @inheritDoc
95     * @since 0.10.
96     */
97    public function create( Persistent $instance ): void {
98        $page = $this->getIdentityStrategy()->getWikiPage(
99            $instance->getIdentity(),
100            $this->getContext()
101        );
102        $pageUpdater = $page->newPageUpdater( $this->getContext()->getMediawikiUser() );
103        $this->setMainSlotToIdentityJson( $instance, $pageUpdater );
104        $this->serialize( $instance, $pageUpdater );
105        $revisionRecord = $pageUpdater->saveRevision( $this->assembleCommentStoreComment() );
106        if ( !$pageUpdater->wasSuccessful() ) {
107            throw new ExternalStoreException(
108                'Failed to create page: ' . $pageUpdater->getStatus()
109            );
110        }
111    }
112
113    /**
114     * Given a persistent domain object instance with at least identity set,
115     * updates the domain object to correspond data retrieved from the database.
116     *
117     * @see read()
118     * @param Persistent $instance Instance to be loaded. Identity must be set.
119     * @throws InvalidArgumentException If instance identity is not set
120     * @throws ExternalStoreException If the page exists but has no revision record.
121     * @return bool true if found, false if not found.
122     * @since 0.1.0
123     */
124    public function load( Persistent $instance ): bool {
125        if ( $instance->getIdentity() === null ) {
126            throw new InvalidArgumentException( 'Identity not set' );
127        }
128        $page = $this->getIdentityStrategy()->getWikiPage(
129            $instance->getIdentity(),
130            $this->getContext()
131        );
132        if ( !$page->exists() ) {
133            return false;
134        }
135        /** @var $revisionRecord RevisionRecord|bool */
136        $revisionRecord = $this->getContext()->getRevisionStore()->getKnownCurrentRevision(
137            $page->getTitle() );
138        if ( $revisionRecord === false ) {
139            throw new ExternalStoreException(
140                'The page ' . $page->getTitle() . ' exists, but there is no revision record!'
141            );
142        }
143        return $this->deserialize( $instance, $revisionRecord );
144    }
145
146    /**
147     * Given a persistent domain object instance with at least identity set,
148     * updates the database to correspond to the data set in the domain object.
149     *
150     * @param Persistent $instance
151     * @throws InvalidArgumentException If instance identity is not set
152     * @throws ExternalStoreException If the page does not exist, or if the update fails.
153     * @since 0.1.0
154     */
155    public function update( Persistent $instance ): void {
156        if ( $instance->getIdentity() === null ) {
157            throw new InvalidArgumentException( 'Identity not set' );
158        }
159        $page = $this->getIdentityStrategy()->getWikiPage(
160            $instance->getIdentity(),
161            $this->getContext()
162        );
163        if ( !$page->exists() ) {
164            throw new ExternalStoreException( 'Page does not exist' );
165        }
166        $pageUpdater = $page->newPageUpdater( $this->getContext()->getMediawikiUser() );
167        // @todo Should we assert that identity in main slot equals instance identity?
168        $this->serialize( $instance, $pageUpdater );
169        $revisionRecord = $pageUpdater->saveRevision( $this->assembleCommentStoreComment() );
170        if ( !$pageUpdater->wasSuccessful() ) {
171            throw new ExternalStoreException(
172                'Failed to update page: ' . $pageUpdater->getStatus()
173            );
174        }
175    }
176
177    /**
178     * Given an identity,
179     * removes the corresponding persistent domain object from the database.
180     *
181     * @param mixed $identity
182     * @throws InvalidArgumentException If the identity is null.
183     * @throws ExternalStoreException If unable to delete the underlying article.
184     * @since 0.1.0
185     */
186    public function delete( $identity ): void {
187        if ( $identity === null ) {
188            throw new InvalidArgumentException( 'Identity not set' );
189        }
190        $page = $this->getIdentityStrategy()->getWikiPage(
191            $identity,
192            $this->getContext()
193        );
194        if ( !$page->exists() ) {
195            return;
196        }
197        $status = $page->doDeleteArticleReal(
198            $this->assembleSummary(),
199            $this->getContext()->getMediawikiUser()
200        );
201        if ( !$status->isOK() ) {
202            throw new ExternalStoreException(
203                "Failed to delete instance with identity $identity$status"
204            );
205        }
206    }
207
208    /**
209     * @return CommentStoreComment
210     * @since 0.1.0
211     */
212    private function assembleCommentStoreComment(): CommentStoreComment {
213        return CommentStoreComment::newUnsavedComment(
214            $this->assembleSummary()
215        );
216    }
217
218    /**
219     * @return string
220     * @since 0.1.0
221     */
222    private function assembleSummary(): string {
223        // @phan-suppress-next-line PhanTypeMismatchReturnNullable
224        return $this->getEditSummary() === null ? '' : $this->getEditSummary();
225    }
226
227    /**
228     * @return string|null
229     * @since 0.1.0
230     */
231    public function getEditSummary(): ?string {
232        return $this->editSummary;
233    }
234
235    /**
236     * @param string|null $editSummary
237     * @since 0.1.0
238     */
239    public function setEditSummary( ?string $editSummary ): void {
240        $this->editSummary = $editSummary;
241    }
242
243    /**
244     * @return AbstractMcrCrudIdentityStrategy
245     * @since 0.1.0
246     */
247    public function getIdentityStrategy(): AbstractMcrCrudIdentityStrategy {
248        return $this->identityStrategy;
249    }
250
251}