Commit graph

32 commits

Author SHA1 Message Date
Umherirrender
7e24705bcd Exclude null values for flag UserOptionsManager::EXCLUDE_DEFAULTS
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
2021-11-01 20:44:13 +01:00
Petr Pchelko
164ec5cb29 UserOptions: remove deprecated hooks.
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
2021-10-26 12:55:01 +00:00
Ppchelko
8ff180948c Reapply "Update user_touched after saving user options."
This reverts commit 4d19d06455.

Reason for revert: CenralAuth tests fixed

Bug: T284354
Depends-On: I9d681baeca0df4808335e7bececfd114cdad2f0e
Change-Id: I483b8f61dcfae70c5a50399391b361cf5310ae24
2021-10-25 20:28:07 +00:00
Alexander Vorwerk
4d19d06455 Revert: "Update user_touched after saving user options."
This reverts commit 98878c08ba.

reason for revert: had some weird and unwanted side effects

Bug: T294265
Change-Id: I53c2175498af5b37096505dae011e65cebf029aa
2021-10-25 16:33:10 +00:00
jenkins-bot
2bd3e49169 Merge "Update user_touched after saving user options." 2021-10-25 14:48:26 +00:00
Umherirrender
6bda73219a Remove more defaults for flag UserOptionsManager::EXCLUDE_DEFAULTS
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
2021-10-02 11:45:57 +02:00
Petr Pchelko
98878c08ba Update user_touched after saving user options.
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
2021-09-27 20:35:27 +00:00
TChin
c9861afba6 Adjust method signature of onSaveUserOptions
This allows hook callers to compare before and after the save

Bug: T287397
Depends-On: I20098ae076b282296670d1116e14bbd29ea76b11
Change-Id: I4d09008bc2bc10afc3742b74564e5ef90ecfe5bf
2021-07-26 21:18:21 +00:00
libraryupgrader
5357695270 build: Updating dependencies
composer:
* mediawiki/mediawiki-codesniffer: 36.0.0 → 37.0.0
  The following sniffs now pass and were enabled:
  * Generic.ControlStructures.InlineControlStructure
  * MediaWiki.PHPUnit.AssertCount.NotUsed

npm:
* svgo: 2.3.0 → 2.3.1
  * https://npmjs.com/advisories/1754 (CVE-2021-33587)

Change-Id: I2a9bbee2fecbf7259876d335f565ece4b3622426
2021-07-22 03:36:05 +00:00
Petr Pchelko
b27d33cc5c Introduce new hooks for UserOptionsManager
Bug: T286576
Change-Id: Ib960ec594d376e086da868d216dd85c8ea6bba14
2021-07-21 06:20:54 -07:00
Petr Pchelko
d3d6a32f48 Don't try to delete non-existent rows when saving options.
Bug: T286521
Change-Id: I1342b0d426eeac3eb4a1fb75072672028ef716f3
2021-07-14 18:27:42 -07:00
Petr Pchelko
98bb3df816 Do not lock user_preferences before updating
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
2021-07-12 18:56:33 -07:00
Tim Starling
5e94e98168 In UserOptionsManager::saveOptions(), don't update unchanged rows
* 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
2021-06-24 02:53:23 +00:00
Petr Pchelko
73bcc40836 Improvements to user preferences fetching/saving
== 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
2021-06-23 23:56:52 +00:00
Amir Sarabadani
cf242ceea0 user: Accept options-messages for multiselect user options
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
2021-06-03 02:01:58 +02:00
DannyS712
90f66a0362 Make UserOptionsLookup::getDefaultOption() not abstract
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
2021-05-12 21:07:14 +00:00
jenkins-bot
41d7fa917f Merge "Split TimeCorrection parser into separate class" 2021-05-07 18:43:08 +00:00
Derk-Jan Hartman
65c955746c Split TimeCorrection parser into separate class
The parsing of the timecorrection useroption was split over multiple
classes. Combine into a single class and add some testcases.

Change-Id: I2cadac00e46dff2bc7d81ac2f294ea2ae4e72f47
2021-05-07 10:43:09 -07:00
James D. Forrester
df5eb22f83 Replace uses of DB_MASTER with DB_PRIMARY
Just an auto-replace from codesniffer for now.

Change-Id: I5240dc9ac5929d291b0ef1c743ea2bfd3f428266
2021-04-29 09:24:31 -07:00
vladshapik
9cc797695b Hard deprecate User ::isIP, ::getOptions
Bug: T275602
Change-Id: Id4be13751ca0a900e51214c1855a4624077a5a62
2021-04-26 16:10:24 +00:00
Petr Pchelko
e914622fe5 UserOptionsManager: don't differentiate anons caches.
Bug: T263911
Change-Id: I698f53e74202964738355561521f5fd889f8ae9b
2020-12-15 12:38:18 -06:00
Thiemo Kreuz
b0130ca649 Update a lot of unspecific "array" types in PHPDocs
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
2020-10-28 11:01:33 +01:00
Aryeh Gregor
a24e8a06b5 Mark CONSTRUCTOR_OPTIONS as internal
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
2020-08-21 00:18:45 -04:00
Petr Pchelko
2bc021e245 UserOptionsManager: fix options reset.
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
2020-08-07 06:38:00 +00:00
jenkins-bot
056a7838de Merge "UserOptionsManager: take into account $queryFlags when caching" 2020-06-12 17:33:27 +00:00
Sam Wilson
5e0fd6d664 Remove PreferencesFactory::setUser()
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
2020-06-08 00:27:04 +00:00
Petr Pchelko
ff50d815a5 UserOptionsManager: take into account $queryFlags when caching
- 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
2020-06-01 09:42:45 -07:00
Tim Starling
68c433bd23 Hooks::run() call site migration
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
2020-05-30 14:23:28 +00:00
Petr Pchelko
82bf390ed5 Add $originalOptions parameter to UserSaveOptions hook
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
2020-05-27 08:32:40 -07:00
Petr Pchelko
2436bb635e Rename DefaultOptionsManager to DefaultOptionsLookup
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
2020-05-11 12:19:13 -07:00
Sam Wilson
36defc20eb Add PreferencesFactory::setUser()
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
2020-05-06 09:04:08 +08:00
Petr Pchelko
788331c48a Introduce UserOptionsManager and DefaultOptionsManager
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
2020-04-28 15:42:43 -07:00