Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 69 |
|
0.00% |
0 / 4 |
CRAP | |
0.00% |
0 / 1 |
ActionFormatter | |
0.00% |
0 / 69 |
|
0.00% |
0 / 4 |
240 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
getActionMessage | |
0.00% |
0 / 45 |
|
0.00% |
0 / 1 |
42 | |||
getActionText | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
getRoot | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | namespace Flow\Log; |
4 | |
5 | use Flow\Collection\PostCollection; |
6 | use Flow\Container; |
7 | use Flow\Conversion\Utils; |
8 | use Flow\Data\ManagerGroup; |
9 | use Flow\Model\UUID; |
10 | use Flow\Repository\TreeRepository; |
11 | use Flow\RevisionActionPermissions; |
12 | use Flow\Templating; |
13 | use Flow\UrlGenerator; |
14 | use LogEntry; |
15 | use LogFormatter; |
16 | use LogPage; |
17 | use MediaWiki\Html\Html; |
18 | use MediaWiki\Message\Message; |
19 | |
20 | class ActionFormatter extends LogFormatter { |
21 | /** |
22 | * @var UUID[] |
23 | */ |
24 | private static $uuids = []; |
25 | |
26 | /** |
27 | * @var RevisionActionPermissions |
28 | */ |
29 | protected $permissions; |
30 | |
31 | /** |
32 | * @var Templating |
33 | */ |
34 | protected $templating; |
35 | |
36 | /** |
37 | * @param LogEntry $entry |
38 | */ |
39 | public function __construct( LogEntry $entry ) { |
40 | parent::__construct( $entry ); |
41 | |
42 | $this->permissions = Container::get( 'permissions' ); |
43 | $this->templating = Container::get( 'templating' ); |
44 | |
45 | $params = $this->entry->getParameters(); |
46 | // serialized topicId or postId can be stored |
47 | foreach ( $params as $value ) { |
48 | if ( $value instanceof UUID ) { |
49 | static::$uuids[$value->getAlphadecimal()] = $value; |
50 | } |
51 | } |
52 | } |
53 | |
54 | /** |
55 | * Formats an activity log entry. |
56 | * |
57 | * @return string The log entry |
58 | */ |
59 | protected function getActionMessage() { |
60 | /* |
61 | * At this point, all log entries will already have been created & we've |
62 | * gathered all uuids in constructor: we can now batch-load all of them. |
63 | * We won't directly be using that batch-loaded data (nothing will even |
64 | * be returned) but it'll ensure that everything we need will be |
65 | * retrieved from cache/storage efficiently & waiting in memory for when |
66 | * we request it again. |
67 | */ |
68 | static $loaded = false; |
69 | if ( !$loaded ) { |
70 | /** @var ManagerGroup $storage */ |
71 | $storage = Container::get( 'storage' ); |
72 | /** @var TreeRepository $treeRepository */ |
73 | $treeRepository = Container::get( 'repository.tree' ); |
74 | |
75 | $query = new LogQuery( $storage, $treeRepository ); |
76 | $query->loadMetadataBatch( static::$uuids ); |
77 | $loaded = true; |
78 | } |
79 | |
80 | $root = $this->getRoot(); |
81 | if ( !$root ) { |
82 | // failed to load required data |
83 | return ''; |
84 | } |
85 | |
86 | $type = $this->entry->getType(); |
87 | $action = $this->entry->getSubtype(); |
88 | $title = $this->entry->getTarget(); |
89 | $params = $this->entry->getParameters(); |
90 | |
91 | if ( isset( $params['postId'] ) ) { |
92 | /** @var UrlGenerator $urlGenerator */ |
93 | $urlGenerator = Container::get( 'url_generator' ); |
94 | |
95 | // generate link that highlights the post |
96 | $anchor = $urlGenerator->postLink( |
97 | $title, |
98 | UUID::create( $params['topicId'] ), |
99 | UUID::create( $params['postId'] ) |
100 | ); |
101 | $title = $anchor->resolveTitle(); |
102 | } |
103 | |
104 | $rootLastRevision = $root->getLastRevision(); |
105 | |
106 | // Give grep a chance to find the usages: |
107 | // A few of the -topic-title-not-visible are not reachable with the current |
108 | // config (since people looking at the suppression log can see suppressed |
109 | // content), but are included to make it less brittle. |
110 | // logentry-delete-flow-delete-post, logentry-delete-flow-delete-post-topic-title-not-visible, |
111 | // logentry-delete-flow-restore-post, logentry-delete-flow-restore-post-topic-title-not-visible, |
112 | // logentry-suppress-flow-restore-post, logentry-suppress-flow-restore-post-topic-title-not-visible, |
113 | // logentry-suppress-flow-suppress-post, logentry-suppress-flow-suppress-post-topic-title-not-visible, |
114 | // logentry-delete-flow-delete-topic, logentry-delete-flow-delete-topic-topic-title-not-visible, |
115 | // logentry-delete-flow-restore-topic, logentry-delete-flow-restore-topic-topic-title-not-visible, |
116 | // logentry-suppress-flow-restore-topic, logentry-suppress-flow-restore-topic-topic-title-not-visible, |
117 | // logentry-suppress-flow-suppress-topic, logentry-suppress-flow-suppress-topic-topic-title-not-visible, |
118 | // logentry-lock-flow-lock-topic, logentry-lock-flow-lock-topic-topic-title-not-visible |
119 | // logentry-lock-flow-restore-topic, logentry-lock-flow-restore-topic-topic-title-not-visible, |
120 | $messageKey = "logentry-$type-$action"; |
121 | $isTopicTitleVisible = $this->permissions->isAllowed( $rootLastRevision, 'view-topic-title' ); |
122 | |
123 | if ( !$isTopicTitleVisible ) { |
124 | $messageKey .= '-topic-title-not-visible'; |
125 | } |
126 | |
127 | $message = $this->msg( $messageKey ) |
128 | ->params( [ |
129 | Message::rawParam( $this->getPerformerElement() ), |
130 | $this->entry->getPerformerIdentity()->getName(), |
131 | ] ); |
132 | |
133 | if ( $isTopicTitleVisible ) { |
134 | $message->params( [ |
135 | $title, // Title of topic |
136 | $title->getFullURL(), // Full URL of topic, with highlighted post if applicable |
137 | ] ); |
138 | |
139 | $message->plaintextParams( $this->templating->getContent( $rootLastRevision, 'topic-title-plaintext' ) ); |
140 | } |
141 | |
142 | $message->params( $root->getWorkflow()->getOwnerTitle() ); // board title object |
143 | |
144 | $message->parse(); |
145 | |
146 | return Html::rawElement( |
147 | 'span', |
148 | [ 'class' => 'plainlinks' ], |
149 | $message->parse() |
150 | ); |
151 | } |
152 | |
153 | /** |
154 | * The native LogFormatter::getActionText provides no clean way of handling |
155 | * the Flow action text in a plain text format (e.g. as used by CheckUser) |
156 | * |
157 | * @return string |
158 | */ |
159 | public function getActionText() { |
160 | if ( $this->canView( LogPage::DELETED_ACTION ) ) { |
161 | $text = $this->getActionMessage(); |
162 | return $this->plaintext ? Utils::htmlToPlaintext( $text ) : $text; |
163 | } else { |
164 | return parent::getActionText(); |
165 | } |
166 | } |
167 | |
168 | /** |
169 | * @return PostCollection|bool |
170 | */ |
171 | protected function getRoot() { |
172 | $params = $this->entry->getParameters(); |
173 | |
174 | try { |
175 | if ( !isset( $params['topicId'] ) ) { |
176 | // failed finding the expected data in storage |
177 | wfWarn( __METHOD__ . ': Failed to locate topicId in log_params for: ' . serialize( $params ) . |
178 | ' (forgot to run FlowFixLog.php?)' ); |
179 | return false; |
180 | } |
181 | |
182 | $uuid = UUID::create( $params['topicId'] ); |
183 | $collection = PostCollection::newFromId( $uuid ); |
184 | |
185 | // see if this post is valid |
186 | $collection->getLastRevision(); |
187 | return $collection; |
188 | } catch ( \Exception $e ) { |
189 | // failed finding the expected data in storage |
190 | wfWarn( __METHOD__ . ': Failed to locate root for: ' . serialize( $params ) . |
191 | ' (potentially storage issue)' ); |
192 | return false; |
193 | } |
194 | } |
195 | } |