Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds ability to manage plugins for self hosted sites that aren't connected to Jetpack #16675

Merged
merged 33 commits into from
Jun 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
991e257
Add support for self hosted sites in the supportsPluginManagement logic
Jun 11, 2021
3644eab
Add a way support self hosted non Jetpack sites in the JetpackSiteRef
Jun 11, 2021
261609e
Fix supportsPluginManagement logic to prevent a crash
Jun 11, 2021
40034dc
Add a helper method to get a blog using the XMLRPC
Jun 11, 2021
3ebdf9c
Add helper method to exclude siteID from self hosted plugin events
Jun 11, 2021
f20b3c3
Add a protocol to represent a generic plugin management client interface
Jun 11, 2021
d964582
Add PluginServiceRemove methods to a new class that adheres to Plugin…
Jun 11, 2021
a567e9a
Add a self hosted plugin management client
Jun 11, 2021
cebc904
Use the PluginManagementClient instead of the PluginServiceRemote
Jun 11, 2021
cbbabaf
Update PodFile to point to WordPressKit branch
Jun 11, 2021
6217ae2
Remove unneeded enum value
Jun 11, 2021
b593845
Fix ending up in a limbo state when installing
Jun 11, 2021
586bf77
Update RELEASE-NOTES.txt
Jun 11, 2021
bbc354c
Merge branch 'develop' into task/16595-self-hosted-plugins
Jun 11, 2021
9f4c7ff
Update Podfile.lock
Jun 11, 2021
2777a5d
Update RELEASE-NOTES.txt
Jun 11, 2021
2289915
Merge branch 'develop' into task/16595-self-hosted-plugins
Jun 14, 2021
9ada0ef
Fix Podfile.lock updating the wrong pods
Jun 14, 2021
7882ac9
Update BlogBuilder to support more testing options
Jun 14, 2021
b89d3bf
Add Plugin Management Tests
Jun 14, 2021
a34ded9
Update JetpackRef.mock to fix broken tests
Jun 14, 2021
7df619d
Add nil account check to the supportsPluginManagement method
Jun 14, 2021
6f2852d
Add decouple the use of JetpackSiteRef from the PluginManagementClient
Jun 15, 2021
16e0361
Remove Plugin Management code that's been moved to WPKit
Jun 15, 2021
18ef501
Update WPKit commit
Jun 15, 2021
28bf51e
Update SelfHostedPluginManagementClient init
Jun 15, 2021
5931d10
Allow lookup of a plugin based on its slugs prefix
Jun 15, 2021
bf63b89
Use the found plugins ID for network actions
Jun 15, 2021
3c163b8
Add support for getting the PluginDirectoryEntry for self hosted plugins
Jun 15, 2021
426ad9c
Update release notes to ignore the self hosted feature notes from the…
Jun 18, 2021
79c9e73
Point to WPKit 4.36.0-beta.2
Jun 18, 2021
aedc0b5
Merge branch 'develop' into task/16595-self-hosted-plugins
Jun 18, 2021
2fcfc27
Update Podfile.lock to WPKit 4.36.0-beta.2
Jun 18, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ end
def wordpress_kit
pod 'WordPressKit', '~> 4.36.0-beta'
# pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :tag => ''
# pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :branch => 'issues/16599-add-new-reader-post-fields'
# pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :branch => ''
# pod 'WordPressKit', :git => 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', :commit => ''
# pod 'WordPressKit', :path => '../WordPressKit-iOS'
end
Expand Down
8 changes: 4 additions & 4 deletions Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ PODS:
- WordPressKit (~> 4.18-beta)
- WordPressShared (~> 1.12-beta)
- WordPressUI (~> 1.7-beta)
- WordPressKit (4.36.0-beta.1):
- WordPressKit (4.36.0-beta.2):
- Alamofire (~> 4.8.0)
- CocoaLumberjack (~> 3.4)
- NSObject-SafeExpectations (= 0.0.4)
Expand Down Expand Up @@ -565,7 +565,6 @@ DEPENDENCIES:
SPEC REPOS:
https://github.com/wordpress-mobile/cocoapods-specs.git:
- WordPressAuthenticator
- WordPressKit
trunk:
- 1PasswordExtension
- Alamofire
Expand Down Expand Up @@ -605,6 +604,7 @@ SPEC REPOS:
- UIDeviceIdentifier
- WordPress-Aztec-iOS
- WordPress-Editor-iOS
- WordPressKit
- WordPressMocks
- WordPressShared
- WordPressUI
Expand Down Expand Up @@ -810,7 +810,7 @@ SPEC CHECKSUMS:
WordPress-Aztec-iOS: 870c93297849072aadfc2223e284094e73023e82
WordPress-Editor-iOS: 068b32d02870464ff3cb9e3172e74234e13ed88c
WordPressAuthenticator: 4ccd7f41bae37247883922ad92a14f18cc759bf3
WordPressKit: 5f75eae2ad44a39a98391306394ced9f3bbf899e
WordPressKit: f9ce5b4aa9854facc409dfd2bcca62e2733f6323
WordPressMocks: dfac50a938ac74dddf5f7cce5a9110126408dd19
WordPressShared: 4fd83a8f572dfd8209fa6ca5c891194b29d15961
WordPressUI: cf3b3ef744663811a8d8a9844f42386e1826f512
Expand All @@ -826,6 +826,6 @@ SPEC CHECKSUMS:
ZendeskSupportSDK: 3a8e508ab1d9dd22dc038df6c694466414e037ba
ZIPFoundation: e27423c004a5a1410c15933407747374e7c6cb6e

PODFILE CHECKSUM: 2ef2682060b199ad5dd4a47a0846fdb741d13cbd
PODFILE CHECKSUM: 556e084a166bd2cd6ff33a3e6d07e2865c7e368b

COCOAPODS: 1.10.1
1 change: 1 addition & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
17.7
-----
* [**] (Don't apply to Jetpack app): Self hosted sites that do not use Jetpack can now manage (install, uninstall, activate, and deactivate) their plugins [#16675]

* [*] Upgraded the Zendesk SDK to version 5.3.0

Expand Down
24 changes: 21 additions & 3 deletions WordPress/Classes/Models/Blog.m
Original file line number Diff line number Diff line change
Expand Up @@ -628,11 +628,22 @@ - (BOOL)supportsPluginManagement
BOOL hasRequiredJetpack = [self hasRequiredJetpackVersion:@"5.6"];

BOOL isTransferrable = self.isHostedAtWPcom
&& self.hasBusinessPlan
&& self.siteVisibility != SiteVisibilityPrivate
&& self.hasBusinessPlan
&& self.siteVisibility != SiteVisibilityPrivate
&& self.isAdmin;

BOOL supports = isTransferrable || hasRequiredJetpack;

// If the site is not hosted on WP.com we can still manage plugins directly using the WP.org rest API
// Reference: https://make.wordpress.org/core/2020/07/16/new-and-modified-rest-api-endpoints-in-wordpress-5-5/
if(!supports && !self.account){
supports = !self.isHostedAtWPcom
&& self.wordPressOrgRestApi
&& [self hasRequiredWordPressVersion:@"5.5"]
&& self.isAdmin;
}

return isTransferrable || hasRequiredJetpack;
return supports;
}

- (BOOL)supportsStories
Expand Down Expand Up @@ -853,6 +864,13 @@ - (BOOL)hasRequiredJetpackVersion:(NSString *)requiredJetpackVersion
&& [self.jetpack.version compare:requiredJetpackVersion options:NSNumericSearch] != NSOrderedAscending;
}

/// Checks the blogs installed WordPress version is more than or equal to the requiredVersion
/// @param requiredVersion The minimum version to check for
- (BOOL)hasRequiredWordPressVersion:(NSString *)requiredVersion
{
return [self.version compare:requiredVersion options:NSNumericSearch] != NSOrderedAscending;
leandroalonso marked this conversation as resolved.
Show resolved Hide resolved
}

#pragma mark - Private Methods

- (id)getOptionValue:(NSString *)name
Expand Down
55 changes: 46 additions & 9 deletions WordPress/Classes/Models/JetpackSiteRef.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,53 @@ struct JetpackSiteRef: Hashable, Codable {
let homeURL: String

private var hasBackup = false

private var hasPaidPlan = false

// Self Hosted Non Jetpack Support
// Ideally this would be a different "ref" object but the JetpackSiteRef
// is so coupled into the plugin management that the amount of changes and work needed to change
// would be very large. This is a workaround for that.
let isSelfHostedWithoutJetpack: Bool
leandroalonso marked this conversation as resolved.
Show resolved Hide resolved

/// The XMLRPC path for the site, only applies to self hosted sites with no Jetpack connected
var xmlRPC: String? = nil

init?(blog: Blog) {
guard let username = blog.account?.username,
let siteID = blog.dotComID as? Int,
let homeURL = blog.homeURL as String? else {

// Init for self hosted and no Jetpack
if blog.account == nil, !blog.isHostedAtWPcom {
guard
let username = blog.username,
let homeURL = blog.homeURL as String?,
let xmlRPC = blog.xmlrpc
else {
return nil
}

self.isSelfHostedWithoutJetpack = true
self.username = username
self.siteID = Constants.selfHostedSiteID
self.homeURL = homeURL
self.xmlRPC = xmlRPC
}

// Init for normal Jetpack connected sites
else {
guard
let username = blog.account?.username,
let siteID = blog.dotComID as? Int,
let homeURL = blog.homeURL as String?
else {
return nil
}

self.isSelfHostedWithoutJetpack = false
self.siteID = siteID
self.username = username
self.homeURL = homeURL
self.hasBackup = blog.isBackupsAllowed()
self.hasPaidPlan = blog.hasPaidPlan
}
self.siteID = siteID
self.username = username
self.homeURL = homeURL
self.hasBackup = blog.isBackupsAllowed()
self.hasPaidPlan = blog.hasPaidPlan
}

public func hash(into hasher: inout Hasher) {
Expand All @@ -48,4 +81,8 @@ struct JetpackSiteRef: Hashable, Codable {
func shouldShowActivityLogFilter() -> Bool {
hasBackup || hasPaidPlan
}

struct Constants {
static let selfHostedSiteID = -1
}
}
2 changes: 1 addition & 1 deletion WordPress/Classes/Models/Plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Foundation

struct Plugin: Equatable {
let state: PluginState
let directoryEntry: PluginDirectoryEntry?
var directoryEntry: PluginDirectoryEntry?

var id: String {
return state.id
Expand Down
11 changes: 10 additions & 1 deletion WordPress/Classes/Services/BlogService+JetpackConvenience.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
extension BlogService {
static func blog(with site: JetpackSiteRef, context: NSManagedObjectContext = ContextManager.shared.mainContext) -> Blog? {
let service = BlogService(managedObjectContext: context)
return service.blog(byBlogId: site.siteID as NSNumber, andUsername: site.username)

let blog: Blog?

if site.isSelfHostedWithoutJetpack, let xmlRPC = site.xmlRPC {
blog = service.findBlog(withXmlrpc: xmlRPC, andUsername: site.username)
} else {
blog = service.blog(byBlogId: site.siteID as NSNumber, andUsername: site.username)
}

return blog
}
}
77 changes: 46 additions & 31 deletions WordPress/Classes/Stores/PluginStore.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation
import WordPressFlux
import WordPressKit

enum PluginAction: Action {
case activate(id: String, site: JetpackSiteRef)
Expand Down Expand Up @@ -340,7 +341,7 @@ extension PluginStore {
}

func getPlugin(slug: String, site: JetpackSiteRef) -> Plugin? {
return getPlugins(site: site)?.plugins.first(where: { $0.state.slug == slug })
return getPlugins(site: site)?.plugins.first(where: { $0.state.slug.hasPrefix(slug) })
}

func getFeaturedPlugins() -> [PluginDirectoryEntry]? {
Expand Down Expand Up @@ -414,11 +415,10 @@ private extension PluginStore {
plugin.active = true
}

WPAppAnalytics.track(.pluginActivated, withBlogID: site.siteID as NSNumber)
track(.pluginActivated, with: site)

remote(site: site)?.activatePlugin(
pluginID: pluginID,
siteID: site.siteID,
pluginID: plugin.state.id,
success: {},
failure: { [weak self] (error) in
let message = String(format: NSLocalizedString("Error activating %@.", comment: "There was an error activating a plugin, placeholder is the plugin name"), plugin.name)
Expand All @@ -437,11 +437,10 @@ private extension PluginStore {
plugin.active = false
}

WPAppAnalytics.track(.pluginDeactivated, withBlogID: site.siteID as NSNumber)
track(.pluginDeactivated, with: site)

remote(site: site)?.deactivatePlugin(
pluginID: pluginID,
siteID: site.siteID,
pluginID: plugin.state.id,
success: {},
failure: { [weak self] (error) in
let message = String(format: NSLocalizedString("Error deactivating %@.", comment: "There was an error deactivating a plugin, placeholder is the plugin name"), plugin.name)
Expand All @@ -460,11 +459,10 @@ private extension PluginStore {
plugin.autoupdate = true
}

WPAppAnalytics.track(.pluginAutoupdateEnabled, withBlogID: site.siteID as NSNumber)
track(.pluginAutoupdateEnabled, with: site)

remote(site: site)?.enableAutoupdates(
pluginID: pluginID,
siteID: site.siteID,
pluginID: plugin.state.id,
success: {},
failure: { [weak self] (error) in
let message = String(format: NSLocalizedString("Error enabling autoupdates for %@.", comment: "There was an error enabling autoupdates for a plugin, placeholder is the plugin name"), plugin.name)
Expand All @@ -483,11 +481,10 @@ private extension PluginStore {
plugin.autoupdate = false
}

WPAppAnalytics.track(.pluginAutoupdateDisabled, withBlogID: site.siteID as NSNumber)
track(.pluginAutoupdateDisabled, with: site)

remote(site: site)?.disableAutoupdates(
pluginID: pluginID,
siteID: site.siteID,
pluginID: plugin.state.id,
success: {},
failure: { [weak self] (error) in
let message = String(format: NSLocalizedString("Error disabling autoupdates for %@.", comment: "There was an error disabling autoupdates for a plugin, placeholder is the plugin name"), plugin.name)
Expand All @@ -499,15 +496,14 @@ private extension PluginStore {
}

func activateAndEnableAutoupdatesPlugin(pluginID: String, site: JetpackSiteRef) {
guard getPlugin(id: pluginID, site: site) != nil else {
guard let plugin = getPlugin(id: pluginID, site: site) else {
return
}
state.modifyPlugin(id: pluginID, site: site) { plugin in
plugin.autoupdate = true
plugin.active = true
}
remote(site: site)?.activateAndEnableAutoupdated(pluginID: pluginID,
siteID: site.siteID,
remote(site: site)?.activateAndEnableAutoupdates(pluginID: plugin.state.id,
success: {},
failure: { [weak self] error in
self?.state.modifyPlugin(id: pluginID, site: site) { plugin in
Expand All @@ -524,15 +520,13 @@ private extension PluginStore {
}

state.updatesInProgress[site, default: Set()].insert(plugin.slug)
WPAppAnalytics.track(.pluginInstalled, withBlogID: site.siteID as NSNumber)

track(.pluginInstalled, with: site)
remote.install(
pluginSlug: plugin.slug,
siteID: site.siteID,
success: { [weak self] installedPlugin in
self?.transaction { state in
state.upsertPlugin(id: installedPlugin.id, site: site, newPlugin: installedPlugin)
state.updatesInProgress[site]?.remove(installedPlugin.slug)
state.updatesInProgress[site]?.remove(installedPlugin.id)
}

let message = String(format: NSLocalizedString("Successfully installed %@.", comment: "Notice displayed after installing a plug-in."), installedPlugin.name)
Expand All @@ -559,10 +553,10 @@ private extension PluginStore {
plugin.updateState = .updating(version)
})
}
WPAppAnalytics.track(.pluginUpdated, withBlogID: site.siteID as NSNumber)
track(.pluginUpdated, with: site)

remote(site: site)?.updatePlugin(
pluginID: pluginID,
siteID: site.siteID,
pluginID: plugin.state.id,
success: { [weak self] (plugin) in
self?.transaction({ (state) in
state.modifyPlugin(id: pluginID, site: site, change: { (updatedPlugin) in
Expand Down Expand Up @@ -590,7 +584,7 @@ private extension PluginStore {
return
}
state.plugins[site]?.plugins.remove(at: index)
WPAppAnalytics.track(.pluginRemoved, withBlogID: site.siteID as NSNumber)
track(.pluginRemoved, with: site)

guard let remote = self.remote(site: site) else {
return
Expand All @@ -604,15 +598,13 @@ private extension PluginStore {

let remove = {
remote.remove(
pluginID: pluginID,
siteID: site.siteID,
pluginID: plugin.state.id,
success: {},
failure: failure)
}

if plugin.state.active {
remote.deactivatePlugin(pluginID: pluginID,
siteID: site.siteID,
remote.deactivatePlugin(pluginID: plugin.state.id,
success: remove,
failure: failure)
} else {
Expand Down Expand Up @@ -650,7 +642,6 @@ private extension PluginStore {
}
state.fetching[site] = true
remote.getPlugins(
siteID: site.siteID,
success: { [actionDispatcher] (plugins) in
actionDispatcher.dispatch(PluginAction.receivePlugins(site: site, plugins: plugins))
},
Expand Down Expand Up @@ -796,12 +787,36 @@ private extension PluginStore {
ActionDispatcher.dispatch(NoticeAction.post(Notice(title: message)))
}

func remote(site: JetpackSiteRef) -> PluginServiceRemote? {
func remote(site: JetpackSiteRef) -> PluginManagementClient? {
guard site.isSelfHostedWithoutJetpack else {
return jetpackRemoteClient(site: site)
}

return selfHostedRemoteClient(site: site)
}

private func jetpackRemoteClient(site: JetpackSiteRef) -> PluginManagementClient? {
guard let token = CredentialsService().getOAuthToken(site: site) else {
return nil
}

let api = WordPressComRestApi.defaultApi(oAuthToken: token, userAgent: WPUserAgent.wordPress())
let pluginRemote = PluginServiceRemote(wordPressComRestApi: api)

return JetpackPluginManagementClient(with: site.siteID, remote: pluginRemote)
}

private func selfHostedRemoteClient(site: JetpackSiteRef) -> PluginManagementClient? {
guard let remote = BlogService.blog(with: site)?.wordPressOrgRestApi else {
return nil
}

return SelfHostedPluginManagementClient(with: remote)
}

func track(_ statName: WPAnalyticsStat, with site: JetpackSiteRef) {
let siteID: NSNumber? = (site.isSelfHostedWithoutJetpack ? nil : site.siteID) as NSNumber?

return PluginServiceRemote(wordPressComRestApi: api)
WPAppAnalytics.track(statName, withBlogID: siteID)
}
}
Loading