Completed
Push — master ( 0cf5bd...a3625c )
by mw
11s
created

RecursiveMembersIterator::next()   C

Complexity

Conditions 7
Paths 8

Size

Total Lines 33
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 7

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 33
ccs 21
cts 21
cp 1
rs 6.7272
c 1
b 0
f 0
cc 7
eloc 17
nc 8
nop 0
crap 7
1
<?php
2
3
namespace SMW\Notifications\Iterators;
4
5
use SMW\Store;
6
use SMW\DIProperty;
7
use SMW\DIWikiPage;
8
use SMW\Notifications\PropertyRegistry;
9
use RecursiveIterator;
10
use ArrayIterator;
11
use Iterator;
12
use Hooks;
13
14
/**
15
 * Lazy load members from a `Notifications group member of` or `Notifications to`
16
 * assignment.
17
 *
18
 * @license GNU GPL v2+
19
 * @since 1.0
20
 *
21
 * @author mwjames
22
 */
23
class RecursiveMembersIterator implements RecursiveIterator {
24
25
	/**
26
	 * @var Store
27
	 */
28
	private $store;
29
30
	/**
31
	 * @var array $primaryKey The name of the primary key(s)
32
	 */
33
	protected $groups;
34
35
	/**
36
	 * @var array $current The current iterator value
37
	 */
38
	private $current = [];
39
40
	/**
41
	 * @var integer key 0-indexed number of pages fetched since self::reset()
42
	 */
43
	private $key;
44
45
	/**
46
	 * @var string
47
	 */
48
	private $agentName = '';
49
50
	/**
51
	 * @var boolean
52
	 */
53
	private $notifyAgent = false;
54
55
	/**
56
	 * @var DIWikiPage|null
57
	 */
58
	private $subject = null;
59
60
	/**
61
	 * @var false
62
	 */
63
	private $notificationsTo = false;
64
65
	/**
66
	 * @since 1.0
67
	 *
68
	 * @param Iterator|array $groups
69
	 * @param Store $store
70
	 */
71 12
	public function __construct( $groups, Store $store ) {
72 12
		$this->groups = $groups;
0 ignored issues
show
Documentation Bug introduced by
It seems like $groups can also be of type object<Iterator>. However, the property $groups is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
73 12
		$this->store = $store;
74 12
	}
75
76
	/**
77
	 * @since 1.0
78
	 *
79
	 * @param string $agentName
80
	 */
81 4
	public function setAgentName( $agentName ) {
82 4
		$this->agentName = str_replace( ' ', '_', $agentName );
83 4
	}
84
85
	/**
86
	 * @since 1.0
87
	 *
88
	 * @param boolean $notifyAgent
89
	 */
90 2
	public function notifyAgent( $notifyAgent ) {
91 2
		$this->notifyAgent = (bool)$notifyAgent;
92 2
	}
93
94
	/**
95
	 * @since 1.0
96
	 *
97
	 * @param DIWikiPage|null $subject
98
	 */
99 4
	public function setSubject( DIWikiPage $subject = null ) {
100 4
		$this->subject = $subject;
101 4
	}
102
103
	/**
104
	 * @since 1.0
105
	 *
106
	 * @return array The most recently fetched set of rows from the database
107
	 */
108 10
	public function current() {
109 10
		return $this->current;
110
	}
111
112
	/**
113
	 * @since 1.0
114
	 *
115
	 * @return integer 0-indexed count of the page number fetched
116
	 */
117
	public function key() {
118
		return $this->key;
119
	}
120
121
	/**
122
	 * @since 1.0
123
	 *
124
	 * Reset the iterator to the beginning of the table.
125
	 */
126 4
	public function rewind() {
127 4
		$this->key = -1; // self::next() will turn this into 0
128 4
		$this->current = [];
129 4
		$this->next();
130 4
	}
131
132
	/**
133
	 * @since 1.0
134
	 *
135
	 * @return bool True when the iterator is in a valid state
136
	 */
137 4
	public function valid() {
138 4
		return (bool)$this->current;
139
	}
140
141
	/**
142
	 * @since 1.0
143
	 *
144
	 * @return bool True when this result set has rows
145
	 */
146
	public function hasChildren() {
147
		return $this->current && count( $this->current );
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->current of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
148
	}
149
150
	/**
151
	 * @since 1.0
152
	 *
153
	 * @return RecursiveIterator
154
	 */
155
	public function getChildren() {
156
		return new ChildlessRecursiveIterator( $this->current );
157
	}
158
159
	/**
160
	 * Fetch the next set of rows from the database.
161
	 *
162
	 * @since 1.0
163
	 *
164
	 * {@inheritDoc}
165
	 */
166 11
	public function next() {
167
168
		// Initial state, transform it to a workable state
169 11
		if ( $this->key === -1 ) {
170 4
			$this->initGroups();
171 4
		}
172
173 11
		$group = array_shift( $this->groups );
174
175 11
		$recipients = $this->doFilterMembersByNotificationsToAssignment(
176 11
			$this->subject
177 11
		);
178
179 11
		if ( $group === false || $group === array() || $group === null ) {
180 5
			$this->current = $recipients === array() ? array() : array_values( $recipients );
181 5
			return false;
182
		}
183
184 9
		foreach ( $group as $groupName ) {
185
186 9
			wfDebugLog( 'smw', __METHOD__ . ' groupName: ' . $groupName->getString() );
187
188 9
			$members = $this->store->getPropertySubjects(
189 9
				new DIProperty( PropertyRegistry::NOTIFICATIONS_GROUP_MEMBER_OF ),
190
				$groupName
191 9
			);
192
193 9
			$this->doFilterMembers( $members, $recipients, $groupName );
194 9
		}
195
196 9
		$this->current = array_values( $recipients );
197 9
		$this->key++;
198 9
	}
199
200 11
	private function doFilterMembersByNotificationsToAssignment( $subject ) {
201
202 11
		$recipients = array();
203
204 11
		if ( $subject === null || $this->notificationsTo ) {
205 11
			return $recipients;
206
		}
207
208 4
		$members = $this->store->getPropertyValues(
209 4
			$subject,
210 4
			new DIProperty( PropertyRegistry::NOTIFICATIONS_TO )
211 4
		);
212
213 4
		$this->doFilterMembers( $members, $recipients );
214 4
		$this->notificationsTo = true;
0 ignored issues
show
Documentation Bug introduced by
The property $notificationsTo was declared of type false, but true is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
215
216 4
		return $recipients;
217
	}
218
219 10
	private function doFilterMembers( $members, &$recipients, $groupName = null ) {
220
221 10
		foreach ( $members as $member ) {
222
223 10
			if ( !$this->notifyAgent && $this->agentName === $member->getDBKey() ) {
224 2
				continue;
225
			}
226
227 10
			if ( Hooks::run( 'SMW::Notifications::UserCanReceiveNotification', array( $this->store, $this->subject, $this->agentName, $member, $groupName ) ) === false ) {
228
				continue;
229
			}
230
231
			// Avoids duplicate members in a group but this will not avoid
232
			// duplicates beyond the group because we don't track them otherwise
233
			// we would require an in-memory hash table
234 10
			$recipients[$member->getHash()] = $member->getDBKey();
235 10
		}
236 10
	}
237
238 4
	private function initGroups() {
239
240
		// It might be that we used an Array or CallbackIterator to avoid
241
		// an initial data load, resolve the iterator now to get a list
242
		// of groups
243 4
		if ( $this->groups instanceof Iterator ) {
244 1
			$this->groups = iterator_to_array( $this->groups, false );
245 1
		}
246
247
		// Remove any keys used earlier to filter duplicates
248 4
		$this->groups = array_values( $this->groups );
249 4
	}
250
251
}
252