isset returns false for null and the null values are not compared,
that treats them as non-default, even the default is also null.
Bug: T291748
Follow-Up: I6e61c11d8aed27b4b559e74849e0056e5eef3638
Change-Id: I0a0932b403098967c261eee3dc0e7d5da3c4fffb
After the hooks were removed we are finally ready to stop
reading user options from primary before writing them on save.
The new save hooks only work on modified options, so options
saving code can be significantly simplified.
Change-Id: I48df616c9f35d9a0b2801ada1b7dbef0bd4ad058
This reverts commit 98878c08ba.
reason for revert: had some weird and unwanted side effects
Bug: T294265
Change-Id: I53c2175498af5b37096505dae011e65cebf029aa
Use the isValueEqual function from
I572446faa8d40801a9adc3aee4b26d97c18000a1 to remove more bool values,
if the default options and the user option differs in real type.
Bug: T291748
Change-Id: I6e61c11d8aed27b4b559e74849e0056e5eef3638
Prior to this change UserOptionsManager::saveOptions was
@internal, since it was not updating user_touched or calling
the UserSaveSettings hook, which some extensions might
have been expecting. This method however was already used
to save the user options.
This patch makes the UserOptionsManager::saveOptions
feature complete and removes the @internal tag.
Bug: T284354
Change-Id: Iadb723b78da04d02dad9abfbfcf13fa25a43a115
This allows hook callers to compare before and after the save
Bug: T287397
Depends-On: I20098ae076b282296670d1116e14bbd29ea76b11
Change-Id: I4d09008bc2bc10afc3742b74564e5ef90ecfe5bf
See T286521#7207425 for my theory on why deadlock
is happening. This is a stop-gap solution since the
ticket is a UBN, not a long-term fix.
This is also a partial revert of
Ibed2789f5260b725fd806b4470631aa30d814ce6.
The good: shouldn't deadlock.
The bad: it will be possible (but very unlikely) that
options save would undo previously saved options.
The long-term solution: we shouldn't need to re-fetch
all the options again before storing them. We now know
which options have changed. But the UserOptionsSave hook
requires all current options to be passed, and then the
hook can unset some of the options. Before implementing
the long-term solution, we need to change the hook
to only need modified options, not all the options.
This will be done as a followup.
Bug: T286521
Change-Id: Ie6203be29ab23bc7ef9e26db421365a4f075c78f
* The comparisons with the default or old DB values often failed due to
type mismatches. Do the comparisons stringwise, and convert booleans
to the string representation used by the DB.
* Avoid duplicate comparisons by combining most of the logic into a
single loop. Build the keys to delete while traversing the new options
array.
* Remove the superseded comment about why there is a up_property
condition in the DELETE.
Bug: T280220
Change-Id: I572446faa8d40801a9adc3aee4b26d97c18000a1
== Status quo ==
When saving user preferences, we want to lock the rows to avoid
accidentally overriding a concurrent options update. So usually
what extensions do is:
$value = $mngr->getOption( 'option', ..., READ_LOCKING );
if ( $value !== 'new_value' ) {
$mngr->setOption( 'option', 'new_value' );
$mngr->saveOptions()
}
Previously for extra caution we've ignored all caches in options
manager if >= READ_LOCKING flags were passed. This resulted in
re-reading all the options multiple times. At worst, 3 times:
1. If READ_NORMAL read was made for update - that's once,
2. On setOption, one more read, forcefully from primary
3. On saveOptions, one more read from primary, non-locking,
to figure out which option keys need to be deleted.
Also, READ_LOCKING was not used where it clearly had to be used,
for example right before the update. This was trying to fix any
kind of error on part of the manager clients, unsuccessfully so.
== New approach ==
1. Cache modified user options separately from originals and merge
them on demand. This means when refetching originals with LOCKING
we don't wipe out all modifications made to the cache with setOption.
Extra bonus - we no longer need to load all options to set an option.
2. Split the originals cache into 2 layers - one for stuff that
comes from DB directly, and one with applied normalizations and
whatever hooks modify. This let's us avoid refetching DB options
after we save them, but still let's the hooks execute on newly set
options after they're saved.
3. Cache options with all query flags. This is a bit controversial, but
ideally LOCKING flags will be applied on options fetch right before
they are saved. We have to re-read options with LOCKING in saveOptions
to avoid races, but if the caller did 'getOption( ..., LOCKING),
setOption(), save()' we will not need to re-select with LOCKING again.
Bug: T280220
Change-Id: Ibed2789f5260b725fd806b4470631aa30d814ce6
With If71008195f58 and I8f52f21a now you can have multiselect field
without "options" set. During deploy of these patches, I encountered
errors (so much that it stopped deployment) that it would try to set the
options when they didn't exist.
Tested in mwdebug1002 and this fixes the bug.
Bug: T58633
Bug: T278650
Change-Id: I0864c7400b11811fce8da0247bf5f6d262e28ae4
Instead of implementing the same thing (fetch all default options,
and if the option that we are looking for is in that array, return that
array entry, and if not, return null) in a bunch of places (DefaultOptionsLookup and StaticUserOptionsLookup do this directly,
UserOptionsManager just delegates to DefaultOptionsLookup) put
this implementation in the parent class
Change-Id: If66f806bc4839f6c1143c5e0adda726c6ed04aae
The parsing of the timecorrection useroption was split over multiple
classes. Combine into a single class and add some testcases.
Change-Id: I2cadac00e46dff2bc7d81ac2f294ea2ae4e72f47
This includes fixing some mistakes, as well as removing
redundant text that doesn't add new information, either because
it literally repeats what the code already says, or is actually
duplicated.
Change-Id: I3a8dd8ce57192deda8916cc444c87d7ab1a36515
These were never meant to be part of the public interface and should not
ever have been marked with @since. They're only useful for constructing
the respective objects, which no outside users should be doing.
Change-Id: I86e01272d46fc72af32172d8a12b9180971d4613
This is going to fix the bug, but it's not going far enough, READ_LATEST
needs to be bumped to READ_EXCLUSIVE.
The problem is that the options mananger cache serves dual purpose:
on option lookup it's a cache, while it also holds the modifications
made to options before saving. We need to require the options fetched
with READ_EXCLUSIVE before we are going to save them, and not discard
the options cache if they were already read with READ_EXCLUSIVE. Relying
on the callers to use correct query flags seems very prone to errors.
Before this was handled with User::getInstanceForUpdate. I wonder if we
should establish a similar pattern and remove the query flags from the
individual methods paramaters, but establish that UserOptionsLookup
service, responsible for reading-only, will use replica DB, while
UserOptionsManager will use master and do locking reads. The usage
pattern would then be - if you only need to read the options, use
lookup. If you have an intention of modifying the options - grab
and instance of the manager, and go into master by default. Thoughts?
Bug: T255842
Change-Id: I399ab0da8880320fd9d5f725ead8a62026cd7b7d
This method was recently added and was to result in the deprecation
of a few places where User objects were being passed to the factory.
This has now been reconsidered and this patch reverts to the
previous behaviour. It is largely a revert of Ie1bed9e9537cabc836992ccfa7fb127885ea3e11
Bug: T238466
Depends-On: Idc9f33fd5ab55bde88cc306ca63adead286380a8
Change-Id: I3653559704ccfd9bca0946f5a865be93bdf5ceb6
- Don't care about $queryFlags for anons since nothing can be
stored in the database for anons.
- For loading the options - discard the old cache in case higher
query flags are used. This means that 'setOption' has to by default
reload the options to ensure changing the options start from LATEST.
This codepath shouldn't be executed in reality cause we should
be already loading the user with READ_LATEST if we want to update
the options. Where that was not done - it was probably a bug.
Also, expose optional $queryFlags parameter for UserOptionsLookup
methods. Otherwise there's no way to read from master using public API.
Bug: T248527
Change-Id: Id7b9868ecdfba89bfafd4618365fe520ec59fcfe
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
Since the hook interfaces are not yet released and adding a parameter
to the hook is b/c, I have just added a parameter without introducing
a new version of the hook interface
Bug: T253149
Change-Id: Iac6c4b706ddbc7b0c9fb0b40eba05bd3530b1fdf
This is not really a manager, since it only supports
reading defaul options. Inroduced in 1.35 and not yet
used outside of core, so no deprecations are needed.
Change-Id: If67af54574d9c2e44ed85d1bdd098afd0a21e8f9
Add a new setUser() method to PreferencesFactory so that a User
object doesn't have to be passed around so much. This is how
GlobalPreferencesFactory has done it, and so after this change
that code can be removed from GlobalPreferences.
Bug: T238466
Change-Id: Ie1bed9e9537cabc836992ccfa7fb127885ea3e11
This converts user options management to a separate
service for use in DI context.
User options are accessed quite early on in installation
process and full-on options management depends on the
database. Prior we have protected from accessing the DB
by setting a hacky $wgUser with 0 id, and relying on the
implementation that it doesn't go into the database to
get the default user options. Now we can't really do that
since DBLoadBalancer is required to instantiate the options
manager. Instead, we redefine the options manager with
a DefaultOptionsManager, that only provides access to
default options and doesn't require DB access.
UserOptionsManager uses PreferencesFactory, however
injecting it will produce a cyclic dependency. The problem
is that we separate options to different kinds, which are
inferred from the PreferencesFactory declaration for those
options (e.g. if it's a radio button in the UI declaration,
the option is of multiselect kind). This is plain wrong,
the dependency should be wise versa. This will be addressed
separately, since it's requires larger refactoring. For now
the PreferencesFactory is obtained on demand. This will be
addressed in a followup.
Bug: T248527
Change-Id: I74917c5eaec184d188911a319895b941ed55ee87